From b9acf24dca58751db2726a3620a7ec7316c8c127 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 21 Feb 2020 09:45:11 +0100 Subject: [PATCH] Make String.aes a stdlib + add more string functions This means moving the FATE operations to StringInternal and adding to/from_list (and Char.to/from_int + Char.to_upper/lower). --- priv/stdlib/String.aes | 86 +++++++++++++++++++++++++++ src/aeso_ast_infer_types.erl | 24 ++++++-- src/aeso_ast_to_fcode.erl | 8 ++- src/aeso_fcode_to_fate.erl | 22 +++++-- test/aeso_calldata_tests.erl | 2 +- test/aeso_compiler_tests.erl | 6 +- test/contracts/bytes_to_x.aes | 2 +- test/contracts/funargs.aes | 2 +- test/contracts/more_strings.aes | 14 +++++ test/contracts/state_handling.aes | 1 + test/contracts/strings.aes | 1 + test/contracts/unapplied_builtins.aes | 1 + 12 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 priv/stdlib/String.aes create mode 100644 test/contracts/more_strings.aes diff --git a/priv/stdlib/String.aes b/priv/stdlib/String.aes new file mode 100644 index 0000000..db5242f --- /dev/null +++ b/priv/stdlib/String.aes @@ -0,0 +1,86 @@ +include "List.aes" +namespace String = + function sha3(s : string) : hash = StringInternal.sha3(s) + function sha256(s : string) : hash = StringInternal.sha256(s) + function blake2b(s : string) : hash = StringInternal.blake2b(s) + + function length(s : string) : int = StringInternal.length(s) + function concat(s1 : string, s2 : string) : string = StringInternal.concat(s1, s2) + + function from_list(cs : list(char)) : string = StringInternal.from_list(cs) + function to_list(s : string) : list(char) = StringInternal.to_list(s) + + function split(i : int, s : string) : string * string = + let cs = StringInternal.to_list(s) + (StringInternal.from_list(List.take(i, cs)), StringInternal.from_list(List.drop(i, cs))) + + function at(ix : int, s : string) = + switch(List.drop(ix - 1, StringInternal.to_list(s))) + [] => None + x :: _ => Some(x) + + function tokens(s : string, pat : string) = + let pat_len = StringInternal.length(pat) + tokens_(StringInternal.to_list(pat), StringInternal.to_list(s), []) + + function to_upper(s : string) = + StringInternal.from_list([ Char.to_upper(c) | c <- StringInternal.to_list(s) ]) + + function to_lower(s : string) = + StringInternal.from_list([ Char.to_lower(c) | c <- StringInternal.to_list(s) ]) + + function contains(str : string, substr : string) : option(int) = + let last_ix = StringInternal.length(str) - (StringInternal.length(substr) - 1) + contains_(1, last_ix, StringInternal.to_list(str), StringInternal.to_list(substr)) + + function + to_int : (string, int) => option(int) + to_int(s, 10) = to_int_(List.reverse(StringInternal.to_list(s)), ch_to_int_10, 0, 1, 10) + to_int(s, 16) = to_int_(List.reverse(StringInternal.to_list(s)), ch_to_int_16, 0, 1, 16) + + function + tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))] + tokens_(pat, str, acc) = + switch(is_prefix(pat, str)) + Some(str') => + StringInternal.from_list(List.reverse(acc)) :: tokens_(pat, str', []) + None => + let c :: cs = str + tokens_(pat, cs, c :: acc) + + function contains_(ix, lix, str, substr) = + if(ix > lix) None + else + switch(is_prefix(substr, str)) + None => + let _ :: str = str + contains_(ix + 1, lix, str, substr) + Some(_) => + Some(ix) + + function + is_prefix([], ys) = Some(ys) + is_prefix(_, []) = None + is_prefix(x :: xs, y :: ys) = + if(x == y) is_prefix(xs, ys) + else None + + function + to_int_([], _, x, _, _) = Some(x) + to_int_(i :: is, c, x, t, f) = + switch(c(i)) + None => None + Some(i) => to_int_(is, c, x + t * i, t * f, f) + + function ch_to_int_10(c) = + let c = Char.to_int(c) + if(c >= 48 && c =< 57) Some(c - 48) + else None + + function ch_to_int_16(c) = + let c = Char.to_int(c) + if(c >= 48 && c =< 57) Some(c - 48) + elif(c >= 65 && c =< 70) Some(c - 55) + elif(c >= 97 && c =< 102) Some(c - 87) + else None + diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index c050226..16962d5 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -364,6 +364,7 @@ is_private(Ann) -> proplists:get_value(private, Ann, false). global_env() -> Ann = [{origin, system}], Int = {id, Ann, "int"}, + Char = {id, Ann, "char"}, Bool = {id, Ann, "bool"}, String = {id, Ann, "string"}, Address = {id, Ann, "address"}, @@ -586,11 +587,21 @@ global_env() -> %% Strings StringScope = #scope { funs = MkDefs( - [{"length", Fun1(String, Int)}, - {"concat", Fun([String, String], String)}, - {"sha3", Fun1(String, Hash)}, - {"sha256", Fun1(String, Hash)}, - {"blake2b", Fun1(String, Hash)}]) }, + [{"length", Fun1(String, Int)}, + {"concat", Fun([String, String], String)}, + {"to_list", Fun1(String, List(Char))}, + {"from_list", Fun1(List(Char), String)}, + {"sha3", Fun1(String, Hash)}, + {"sha256", Fun1(String, Hash)}, + {"blake2b", Fun1(String, Hash)}]) }, + + %% Chars + CharScope = #scope + { funs = MkDefs( + [{"to_int", Fun1(Char, Int)}, + {"from_int", Fun1(Int, Option(Char))}, + {"to_upper", Fun1(Char, Char)}, + {"to_lower", Fun1(Char, Char)}]) }, %% Bits BitsScope = #scope @@ -634,7 +645,8 @@ global_env() -> , ["Auth"] => AuthScope , ["Crypto"] => CryptoScope , ["MCL_BLS12_381"] => MCL_BLS12_381_Scope - , ["String"] => StringScope + , ["StringInternal"] => StringScope + , ["Char"] => CharScope , ["Bits"] => BitsScope , ["Bytes"] => BytesScope , ["Int"] => IntScope diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 0abfb49..df6b957 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -246,8 +246,10 @@ builtins() -> {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, {"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1}, {"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]}, + {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, + {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, + {["Char"], [{"to_int", 1}, {"from_int", 1}, {"to_lower", 1}, {"to_upper", 1}]}, {["Auth"], [{"tx_hash", none}, {"tx", none}]}, - {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, @@ -1040,7 +1042,9 @@ stmts_to_fcode(Env, [Expr | Stmts]) -> op_builtins() -> [map_from_list, map_to_list, map_delete, map_member, map_size, - string_length, string_concat, string_sha3, string_sha256, string_blake2b, + stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list, + stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, + char_to_int, char_from_int, char_to_lower, char_to_upper, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_difference, int_to_str, address_to_str, crypto_verify_sig, address_to_contract, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index a5a5139..8206c3f 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -573,8 +573,14 @@ op_to_scode(map_to_list) -> aeb_fate_ops:map_to_list(?a, ?a); op_to_scode(map_delete) -> aeb_fate_ops:map_delete(?a, ?a, ?a); op_to_scode(map_member) -> aeb_fate_ops:map_member(?a, ?a, ?a); op_to_scode(map_size) -> aeb_fate_ops:map_size_(?a, ?a); -op_to_scode(string_length) -> aeb_fate_ops:str_length(?a, ?a); -op_to_scode(string_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a); +op_to_scode(stringinternal_length) -> aeb_fate_ops:str_length(?a, ?a); +op_to_scode(stringinternal_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a); +op_to_scode(stringinternal_to_list) -> aeb_fate_ops:str_to_list(?a, ?a); +op_to_scode(stringinternal_from_list) -> aeb_fate_ops:str_from_list(?a, ?a); +op_to_scode(char_to_int) -> aeb_fate_ops:char_to_int(?a, ?a); +op_to_scode(char_from_int) -> aeb_fate_ops:char_from_int(?a, ?a); +op_to_scode(char_to_lower) -> aeb_fate_ops:char_to_lower(?a, ?a); +op_to_scode(char_to_upper) -> aeb_fate_ops:char_to_upper(?a, ?a); op_to_scode(bits_set) -> aeb_fate_ops:bits_set(?a, ?a, ?a); op_to_scode(bits_clear) -> aeb_fate_ops:bits_clear(?a, ?a, ?a); op_to_scode(bits_test) -> aeb_fate_ops:bits_test(?a, ?a, ?a); @@ -593,9 +599,9 @@ op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a, op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); -op_to_scode(string_sha3) -> aeb_fate_ops:sha3(?a, ?a); -op_to_scode(string_sha256) -> aeb_fate_ops:sha256(?a, ?a); -op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); +op_to_scode(stringinternal_sha3) -> aeb_fate_ops:sha3(?a, ?a); +op_to_scode(stringinternal_sha256) -> aeb_fate_ops:sha256(?a, ?a); +op_to_scode(stringinternal_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); op_to_scode(mcl_bls12_381_g1_neg) -> aeb_fate_ops:bls12_381_g1_neg(?a, ?a); op_to_scode(mcl_bls12_381_g1_norm) -> aeb_fate_ops:bls12_381_g1_norm(?a, ?a); op_to_scode(mcl_bls12_381_g1_valid) -> aeb_fate_ops:bls12_381_g1_valid(?a, ?a); @@ -919,6 +925,12 @@ attributes(I) -> {'BLS12_381_INT_TO_FP', A, B} -> Pure(A, [B]); {'BLS12_381_FR_TO_INT', A, B} -> Pure(A, [B]); {'BLS12_381_FP_TO_INT', A, B} -> Pure(A, [B]); + {'STRING_TO_LIST', A, B} -> Pure(A, [B]); + {'STRING_FROM_LIST', A, B} -> Pure(A, [B]); + {'CHAR_TO_INT', A, B} -> Pure(A, [B]); + {'CHAR_FROM_INT', A, B} -> Pure(A, [B]); + {'CHAR_TO_UPPER', A, B} -> Pure(A, [B]); + {'CHAR_TO_LOWER', A, B} -> Pure(A, [B]); {'ABORT', A} -> Impure(pc, A); {'EXIT', A} -> Impure(pc, A); 'NOP' -> Pure(none, []) diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 46e0be9..ff14259 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -142,4 +142,4 @@ compilable_contracts() -> not_yet_compilable(fate) -> []; not_yet_compilable(aevm) -> - []. + ["funargs", "strings"]. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index ee5991a..97ad893 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -168,11 +168,13 @@ compilable_contracts() -> "pairing_crypto", "qualified_constructor", "let_patterns", - "lhs_matching" + "lhs_matching", + "more_strings" ]. not_yet_compilable(fate) -> []; -not_yet_compilable(aevm) -> ["pairing_crypto", "aens_update", "basic_auth_tx"]. +not_yet_compilable(aevm) -> ["pairing_crypto", "aens_update", "basic_auth_tx", "more_strings", + "unapplied_builtins", "bytes_to_x", "state_handling"]. %% Contracts that should produce type errors diff --git a/test/contracts/bytes_to_x.aes b/test/contracts/bytes_to_x.aes index 6054301..6ab4852 100644 --- a/test/contracts/bytes_to_x.aes +++ b/test/contracts/bytes_to_x.aes @@ -1,4 +1,4 @@ - +include "String.aes" contract BytesToX = entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b) diff --git a/test/contracts/funargs.aes b/test/contracts/funargs.aes index b63edff..450c158 100644 --- a/test/contracts/funargs.aes +++ b/test/contracts/funargs.aes @@ -1,4 +1,4 @@ - +include "String.aes" contract FunctionArguments = entrypoint sum(n : int, m: int) = diff --git a/test/contracts/more_strings.aes b/test/contracts/more_strings.aes new file mode 100644 index 0000000..9579423 --- /dev/null +++ b/test/contracts/more_strings.aes @@ -0,0 +1,14 @@ +include "String.aes" +contract StringX = + entrypoint test() = + let s1 = "a string" + let s2 = "another string" + let s = String.concat(s1, s2) + String.sha256(s) + String.length(s1) + String.from_list(String.to_list(s)) + String.split(4, s1) + String.at(2, s2) + String.tokens(s, ",") + String.to_upper(s1) + String.to_lower(s2) diff --git a/test/contracts/state_handling.aes b/test/contracts/state_handling.aes index e4311d6..00c2fcb 100644 --- a/test/contracts/state_handling.aes +++ b/test/contracts/state_handling.aes @@ -1,3 +1,4 @@ +include "String.aes" contract Remote = record rstate = { i : int, s : string, m : map(int, int) } diff --git a/test/contracts/strings.aes b/test/contracts/strings.aes index cbde027..752266b 100644 --- a/test/contracts/strings.aes +++ b/test/contracts/strings.aes @@ -1,3 +1,4 @@ +include "String.aes" contract Strings = entrypoint str_len(s) = String.length(s) entrypoint str_concat(s1, s2) = String.concat(s1, s2) diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes index 3d3bb39..678b085 100644 --- a/test/contracts/unapplied_builtins.aes +++ b/test/contracts/unapplied_builtins.aes @@ -7,6 +7,7 @@ // AENS.transfer // AENS.revoke // Oracle.extend +include "String.aes" contract UnappliedBuiltins = entrypoint main() = () type o = oracle(int, int)