From c2a28677695ea72de925a141d56a7917ab241ca1 Mon Sep 17 00:00:00 2001 From: Jason Steving Date: Tue, 14 Nov 2023 18:37:39 -0800 Subject: [PATCH] Add `strings` and `StringBuilder` to Stdlib Modules Now Claro programs have access to a wide variety of string util functionality as well as access to java.lang.StringBuilder for efficient concatenation. --- .../com/claro/claro_build_rules_internal.bzl | 2 + src/java/com/claro/stdlib/claro/strings/BUILD | 17 ++ .../stdlib/claro/strings/string_builder/BUILD | 17 ++ .../string_builder.claro_internal | 34 +++ .../string_builder.claro_module_api | 8 + .../claro/strings/string_builder/test.claro | 7 + .../claro/strings/strings.claro_internal | 284 ++++++++++++++++++ .../claro/strings/strings.claro_module_api | 64 ++++ .../com/claro/stdlib/claro/strings/test.claro | 32 ++ 9 files changed, 465 insertions(+) create mode 100644 src/java/com/claro/stdlib/claro/strings/BUILD create mode 100644 src/java/com/claro/stdlib/claro/strings/string_builder/BUILD create mode 100644 src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_internal create mode 100644 src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_module_api create mode 100644 src/java/com/claro/stdlib/claro/strings/string_builder/test.claro create mode 100644 src/java/com/claro/stdlib/claro/strings/strings.claro_internal create mode 100644 src/java/com/claro/stdlib/claro/strings/strings.claro_module_api create mode 100644 src/java/com/claro/stdlib/claro/strings/test.claro diff --git a/src/java/com/claro/claro_build_rules_internal.bzl b/src/java/com/claro/claro_build_rules_internal.bzl index afa843de..fbe78c6a 100644 --- a/src/java/com/claro/claro_build_rules_internal.bzl +++ b/src/java/com/claro/claro_build_rules_internal.bzl @@ -27,6 +27,8 @@ CLARO_STDLIB_MODULES = { "maps": "@claro-lang//src/java/com/claro/stdlib/claro/maps:maps", "sets": "@claro-lang//src/java/com/claro/stdlib/claro/sets:sets", "std": "@claro-lang//src/java/com/claro/stdlib/claro:std", + "strings": "@claro-lang//src/java/com/claro/stdlib/claro/strings:strings", + "StringBuilder": "@claro-lang//src/java/com/claro/stdlib/claro/strings/string_builder:string_builder", } # Part of Claro's stdlib is going to be opt-in rather than bundled into your build by default. The intention here is to # enable Claro to build smaller executables in cases where certain lesser used parts of the stdlib are not actually diff --git a/src/java/com/claro/stdlib/claro/strings/BUILD b/src/java/com/claro/stdlib/claro/strings/BUILD new file mode 100644 index 00000000..f8defdf1 --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/BUILD @@ -0,0 +1,17 @@ +load( + "//src/java/com/claro:claro_build_rules_internal.bzl", + "bootstrapped_claro_module_internal", + "claro_binary", +) + +bootstrapped_claro_module_internal( + name = "strings", + module_api_file = "strings.claro_module_api", + srcs = ["strings.claro_internal"], + visibility = ["//visibility:public"], +) + +claro_binary( + name = "test_strings", + main_file = "test.claro", +) diff --git a/src/java/com/claro/stdlib/claro/strings/string_builder/BUILD b/src/java/com/claro/stdlib/claro/strings/string_builder/BUILD new file mode 100644 index 00000000..a9058d3b --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/string_builder/BUILD @@ -0,0 +1,17 @@ +load( + "//src/java/com/claro:claro_build_rules_internal.bzl", + "bootstrapped_claro_module_internal", + "claro_binary", +) + +bootstrapped_claro_module_internal( + name = "string_builder", + module_api_file = "string_builder.claro_module_api", + srcs = ["string_builder.claro_internal"], + visibility = ["//visibility:public"], +) + +claro_binary( + name = "test_string_builder", + main_file = "test.claro", +) \ No newline at end of file diff --git a/src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_internal b/src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_internal new file mode 100644 index 00000000..86e58be0 --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_internal @@ -0,0 +1,34 @@ + +alias SBJavaType : $java_type("java.lang.StringBuilder") + +newtype StringBuilder : SBJavaType + +provider create() -> StringBuilder { + var res: SBJavaType; + + $$BEGIN_JAVA + res = new java.lang.StringBuilder(); + $$END_JAVA + + return internalCreate(res); +} +function internalCreate(javaType: SBJavaType) -> StringBuilder { + return StringBuilder(javaType); +} + +# Add anything to the end of the StringBuilder. It will be converted to its string representation automatically. +function add(sb: StringBuilder, toAdd: T) -> StringBuilder { + $$BEGIN_JAVA + sb.wrappedValue.append(toAdd); + $$END_JAVA + return sb; +} +function build(sb: StringBuilder) -> string { + var res: string; + + $$BEGIN_JAVA + res = sb.wrappedValue.toString(); + $$END_JAVA + + return res; +} \ No newline at end of file diff --git a/src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_module_api b/src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_module_api new file mode 100644 index 00000000..41fc2714 --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/string_builder/string_builder.claro_module_api @@ -0,0 +1,8 @@ + +opaque newtype StringBuilder + +provider create() -> StringBuilder; + +# Add anything to the end of the StringBuilder. It will be converted to its string representation automatically. +function add(sb: StringBuilder, toAdd: T) -> StringBuilder; +function build(sb: StringBuilder) -> string; diff --git a/src/java/com/claro/stdlib/claro/strings/string_builder/test.claro b/src/java/com/claro/stdlib/claro/strings/string_builder/test.claro new file mode 100644 index 00000000..cf16f9ae --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/string_builder/test.claro @@ -0,0 +1,7 @@ + +var sb = StringBuilder::create(); +StringBuilder::add(sb, "Hello") + |> StringBuilder::add(^, ", ") + |> StringBuilder::add(^, "from this StringBuilder!") + |> StringBuilder::build(^) + |> print(^); diff --git a/src/java/com/claro/stdlib/claro/strings/strings.claro_internal b/src/java/com/claro/stdlib/claro/strings/strings.claro_internal new file mode 100644 index 00000000..ca50874d --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/strings.claro_internal @@ -0,0 +1,284 @@ + +function charAt(s: string, i: int) -> string { + var res: string; + $$BEGIN_JAVA + res = String.valueOf(s.charAt(i)); + $$END_JAVA + return res; +} + +function commonPrefix(s: string, other: string) -> string { + var res: string; + $$BEGIN_JAVA + res = com.google.common.base.Strings.commonPrefix(s, other); + $$END_JAVA + return res; +} +function commonSuffix(s: string, other: string) -> string { + var res: string; + $$BEGIN_JAVA + res = com.google.common.base.Strings.commonSuffix(s, other); + $$END_JAVA + return res; +} + +function compareTo(s: string, other: string) -> int { + var res: int; + $$BEGIN_JAVA + res = s.compareTo(other); + $$END_JAVA + return res; +} +function compareToIgnoreCase(s: string, other: string) -> int { + var res: int; + $$BEGIN_JAVA + res = s.compareToIgnoreCase(other); + $$END_JAVA + return res; +} + +function concat(s: string, other: string) -> string { + var res: string; + $$BEGIN_JAVA + res = s.concat(other); + $$END_JAVA + return res; +} + +function contains(s: string, other: string) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.contains(other); + $$END_JAVA + return res; +} + +function endsWith(s: string, other: string) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.endsWith(other); + $$END_JAVA + return res; +} + +function equalsIgnoreCase(s: string, other: string) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.equalsIgnoreCase(other); + $$END_JAVA + return res; +} + +function indexOf(s: string, of: string) -> oneof { + var res: oneof; + $$BEGIN_JAVA + res = s.indexOf(of); + $$END_JAVA + if (res == -1) { + return NOT_FOUND; + } + return res; +} +function indexOfFromIndex(s: string, of: string, from: int) -> oneof { + var res: oneof; + $$BEGIN_JAVA + res = s.indexOf(of, from); + $$END_JAVA + if (res == -1) { + return NOT_FOUND; + } + return res; +} + +function isEmpty(s: string) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.isEmpty(); + $$END_JAVA + return res; +} + +function join(delimiter: string, parts: [string]) -> string { + var res: string; + $$BEGIN_JAVA + res = String.join(delimiter, parts); + $$END_JAVA + return res; +} + +function lastIndexOf(s: string, of: string) -> oneof { + var res: oneof; + $$BEGIN_JAVA + res = s.lastIndexOf(of); + $$END_JAVA + if (res == -1) { + return NOT_FOUND; + } + return res; +} +function lastIndexOfFromIndex(s: string, of: string, from: int) -> oneof { + var res: oneof; + $$BEGIN_JAVA + res = s.lastIndexOf(of, from); + $$END_JAVA + if (res == -1) { + return NOT_FOUND; + } + return res; +} + +function matches(s: string, regex: string) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.matches(regex); + $$END_JAVA + return res; +} + +function padEnd(s: string, minLength: int) -> string { + var res: string; + $$BEGIN_JAVA + res = com.google.common.base.Strings.padEnd(s, minLength, ' '); + $$END_JAVA + return res; +} +function padStart(s: string, minLength: int) -> string { + var res: string; + $$BEGIN_JAVA + res = com.google.common.base.Strings.padStart(s, minLength, ' '); + $$END_JAVA + return res; +} + +function regionMatches(s: string, s_offset: int, other: string, o_offset: int, regionLen: int) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.regionMatches(s_offset, other, o_offset, regionLen); + $$END_JAVA + return res; +} +function regionMatchesIgnoreCase(ignoreCase: boolean, s: string, s_offset: int, other: string, o_offset: int, regionLen: int) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.regionMatches(ignoreCase, s_offset, other, o_offset, regionLen); + $$END_JAVA + return res; +} + +function repeated(s: string, count: int) -> string { + var res: string; + $$BEGIN_JAVA + res = com.google.common.base.Strings.repeat(s, count); + $$END_JAVA + return res; +} + +function replace(s: string, target: string, replacement: string) -> string { + var res: string; + $$BEGIN_JAVA + res = s.replace(target, replacement); + $$END_JAVA + return res; +} +function replaceAll(s: string, regex: string, replacement: string) -> string { + var res: string; + $$BEGIN_JAVA + res = s.replaceAll(regex, replacement); + $$END_JAVA + return res; +} +function replaceFirst(s: string, regex: string, replacement: string) -> string { + var res: string; + $$BEGIN_JAVA + res = s.replaceFirst(regex, replacement); + $$END_JAVA + return res; +} + +function split(s: string, regex: string) -> [string] { + var res: [string]; + alias ListType: [string] + $$BEGIN_JAVA + $$TYPES + res = ClaroList.initializeList($$CLARO_TYPE(ListType), s.split(regex)); + $$END_JAVA + return res; +} +function splitWithLimit(s: string, regex: string, limit: int) -> [string] { + var res: [string]; + alias ListType: [string] + $$BEGIN_JAVA + $$TYPES + res = ClaroList.initializeList($$CLARO_TYPE(ListType), s.split(regex, limit)); + $$END_JAVA + return res; +} +function splitChars(s: string) -> [string] { + var res: [string]; + alias ListType : [string] + $$BEGIN_JAVA + $$TYPES + res = new ClaroList($$CLARO_TYPE(ListType), s.length()); + for (int i = 0; i < s.length(); i++) { + res.add(String.valueOf(s.charAt(i))); + } + $$END_JAVA + return res; +} + +function startsWith(s: string, other: string) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.startsWith(other); + $$END_JAVA + return res; +} +function startsWithFromIndex(s: string, other: string, from: int) -> boolean { + var res: boolean; + $$BEGIN_JAVA + res = s.startsWith(other, from); + $$END_JAVA + return res; +} + +function substring(s: string, beginInclusive: int, endExclusive: int) -> string { + var res: string; + $$BEGIN_JAVA + res = s.substring(beginInclusive, endExclusive); + $$END_JAVA + return res; +} +function suffix(s: string, beginInclusive: int) -> string { + var res: string; + $$BEGIN_JAVA + res = s.substring(beginInclusive); + $$END_JAVA + return res; +} + +function toLowerCase(s: string) -> string { + var res: string; + $$BEGIN_JAVA + res = s.toLowerCase(); + $$END_JAVA + return res; +} +function toUpperCase(s: string) -> string { + var res: string; + $$BEGIN_JAVA + res = s.toUpperCase(); + $$END_JAVA + return res; +} + +function trim(s: string) -> string { + var res: string; + $$BEGIN_JAVA + res = s.trim(); + $$END_JAVA + return res; +} + +function valueOf(t: T) -> string { + return "{t}"; +} diff --git a/src/java/com/claro/stdlib/claro/strings/strings.claro_module_api b/src/java/com/claro/stdlib/claro/strings/strings.claro_module_api new file mode 100644 index 00000000..9265b81f --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/strings.claro_module_api @@ -0,0 +1,64 @@ +# Simply exposing the functionality documented at the links below to Claro programs: +# - https://docs.oracle.com/javase/8/docs/api/java/lang/String.html +# - https://guava.dev/releases/31.1-jre/api/docs/com/google/common/base/Strings.html +# TODO(steving) Make this api safe so that nothing can throw a runtime exception. + +atom NOT_FOUND + +# TODO(steving) Revisit this once Claro has support for chars. +function charAt(s: string, i: int) -> string; + +function commonPrefix(s: string, other: string) -> string; +function commonSuffix(s: string, other: string) -> string; + +function compareTo(s: string, other: string) -> int; +function compareToIgnoreCase(s: string, other: string) -> int; + +function concat(s: string, other: string) -> string; + +function contains(s: string, other: string) -> boolean; + +function endsWith(s: string, other: string) -> boolean; + +function equalsIgnoreCase(s: string, other: string) -> boolean; + +function indexOf(s: string, of: string) -> oneof; +function indexOfFromIndex(s: string, of: string, from: int) -> oneof; + +function isEmpty(s: string) -> boolean; + +function join(delimiter: string, parts: [string]) -> string; + +function lastIndexOf(s: string, of: string) -> oneof; +function lastIndexOfFromIndex(s: string, of: string, from: int) -> oneof; + +function matches(s: string, regex: string) -> boolean; + +# TODO(steving) Revisit this once Claro has support for chars. Should support configurable padding char. +function padEnd(s: string, minLength: int) -> string; +function padStart(s: string, minLength: int) -> string; + +function regionMatches(s: string, s_offset: int, other: string, o_offset: int, regionLen: int) -> boolean; +function regionMatchesIgnoreCase(ignoreCase: boolean, s: string, s_offset: int, other: string, o_offset: int, regionLen: int) -> boolean; + +function repeated(s: string, count: int) -> string; + +function replace(s: string, target: string, replacement: string) -> string; +function replaceAll(s: string, regex: string, replacement: string) -> string; +function replaceFirst(s: string, regex: string, replacement: string) -> string; + +function split(s: string, regex: string) -> [string]; +function splitWithLimit(s: string, regex: string, limit: int) -> [string]; +# TODO(steving) Revisit this once Claro has support for chars. +function splitChars(s: string) -> [string]; + +function startsWith(s: string, other: string) -> boolean; +function startsWithFromIndex(s: string, other: string, from: int) -> boolean; + +function substring(s: string, beginInclusive: int, endExclusive: int) -> string; +function suffix(s: string, beginInclusive: int) -> string; + +function toLowerCase(s: string) -> string; +function toUpperCase(s: string) -> string; + +function trim(s: string) -> string; \ No newline at end of file diff --git a/src/java/com/claro/stdlib/claro/strings/test.claro b/src/java/com/claro/stdlib/claro/strings/test.claro new file mode 100644 index 00000000..12be7741 --- /dev/null +++ b/src/java/com/claro/stdlib/claro/strings/test.claro @@ -0,0 +1,32 @@ + +var hello = "Hello, world!"; +var c = strings::charAt(hello, 1); +print("The 2nd char of {hello} is: {c}"); + +var i = strings::indexOf(hello, "l"); +print("The index of 'l' in {hello} is: {i}"); +i = strings::indexOf(hello, "y"); +print("The index of 'y' in {hello} is: {i}"); +i = strings::indexOfFromIndex(hello, "o", 5); +print("The index of 'o' in {hello} starting from index 5 is: {i}"); + +var replaced = strings::replace(hello, "world", "replaced"); +print("The result of replacing 'world' is: {replaced}"); + +var commonPrefix = strings::commonPrefix(hello, replaced); +print("The common prefix between:\n\t\"{hello}\"\n\t\tAND\n\t\"{replaced}\"\n\t\tIS\n\t\"{commonPrefix}\""); +var commonSuffix = strings::commonSuffix(hello, replaced); +print("The common suffix between:\n\t\"{hello}\"\n\t\tAND\n\t\"{replaced}\"\n\t\tIS\n\t\"{commonSuffix}\""); + +var parts = strings::split(hello, ","); +print("The split parts are: \"{parts[0]}\" and \"{parts[1]}\""); + +var chars = strings::splitChars(hello); +print("The chars are: {chars}"); + +var upperCase = strings::toUpperCase(hello); +print("In all caps: {upperCase}"); + +strings::padStart("DONE!\n", 10) + |> strings::repeated(^, 5) + |> print(^);