Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add remaining String SDK Functions #588

Merged
merged 3 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,207 @@ stringTrimLeft str =
stringTrimRight : String -> String
stringTrimRight str =
String.trimRight str


{-|

Test: String/fromChar
expected = "a"

-}
stringFromCharTest : TestContext -> String
stringFromCharTest ctx =
test ctx
(String.fromChar 'a')


{-|

Test: String/cons
expected = "abc"

-}
stringConsTest : TestContext -> String
stringConsTest ctx =
test ctx
(String.cons 'a' "bc")


{-|

Test: String/uncons
input = "abc", expected = Just ('a', "bc")
input = "a", expected = Just ('a', "")
input = "", expected = Nothing

-}
stringUnconsTest : String -> Maybe ( Char, String )
stringUnconsTest input =
String.uncons input


{-|

Test: String/toList
expected = ['a', 'b', 'c']

-}
stringToListTest : TestContext -> List Char
stringToListTest ctx =
test ctx
(String.toList "abc")


{-|

Test: String/fromList
expected = "abc"

-}
stringFromListTest : TestContext -> String
michelchan marked this conversation as resolved.
Show resolved Hide resolved
stringFromListTest ctx =
test ctx
(String.fromList [ 'a', 'b', 'c' ])


{-|

Test: String/fromList
expected = ""

-}
stringFromListEmptyTest : TestContext -> String
stringFromListEmptyTest ctx =
test ctx
(String.fromList [])


{-|

Test: String/pad
pad 5 ' ' "1" == " 1 "
pad 5 ' ' "11" == " 11 "
pad 5 ' ' "121" == " 121 "
pad 5 ' ' "1234" == " 1234"
pad 5 ' ' "12345" == "12345"
pad 5 ' ' "123456" == "123456"
pad 0 ' ' "123" == "123"
pad -5 ' ' "123" == "123"
pad 5 ' ' "" == " "

-}
stringPadTest : Int -> String -> String
stringPadTest size input =
String.pad size ' ' input


{-|

Test: String/map
expected = "a.b.c"

-}
stringMapTest : TestContext -> String
stringMapTest ctx =
test ctx
String.map
(\c ->
if c == '/' then
'.'

else
c
)
"a/b/c"


{-|

Test: String/filter
expected = "bc"

-}
stringFilterTest : TestContext -> String
stringFilterTest ctx =
test ctx
(String.filter (\c -> c /= 'a') "abc")


{-|

Test: String/foldl
input = UPPERCASE, expected = True
input = lowercase, expected = False
input = camelCase, expected = False

-}
stringFoldlTest : String -> Bool
stringFoldlTest input =
String.foldl (\char acc -> acc && (char >= 'A' && char <= 'Z')) True input


{-|

Test: String/foldl
input = "time", expected = "emit"

-}
stringFoldlTest2 : String -> String
stringFoldlTest2 input =
String.foldl String.cons "" input


{-|

Test: String/foldr
input = "Hello, World", expected = 2
input = "HELLO, WORLD", expected = 10

-}
stringFoldrTest : String -> Int
stringFoldrTest input =
String.foldr
(\char count ->
if char >= 'A' && char <= 'Z' then
count + 1

else
count
)
0
input


{-|

Test: String/foldr
input = "time", expected = "time"

-}
stringFoldrTest2 : String -> String
stringFoldrTest2 input =
String.foldr String.cons "" input


{-|

Test: String/any
input = "scala", expected = True
input = "elm", expected = False

-}
stringAnyTest : String -> Bool
stringAnyTest input =
String.any (\c -> c == 'a') input


{-|

Test: String/all
input = "aaa", expected = True
input = "abc", expected = False

-}
stringAllTest : String -> Bool
stringAllTest input =
String.all (\c -> c == 'a') input
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,49 @@ object EvaluatorMDMTests extends MorphirBaseSpec {
testEvaluation("toInt")("StringTests", "stringToIntTest1")(Data.Optional.Some(Data.Int(25))),
testEvaluation("toInt")("StringTests", "stringToIntTest2")(Data.Optional.None(Concept.Int32)),
testEvaluation("isEmpty")("StringTests", "stringIsEmptyTest1")(Data.Boolean(true)),
testEvaluation("isEmpty")("StringTests", "stringIsEmptyTest2")(Data.Boolean(false))
testEvaluation("isEmpty")("StringTests", "stringIsEmptyTest2")(Data.Boolean(false)),
testEvaluation("fromChar")("StringTests", "stringFromCharTest")(Data.String("a")),
testEvaluation("cons")("StringTests", "stringConsTest")(Data.String("abc")),
testEval("uncons")("StringTests", "stringUnconsTest", "abc")(Data.Optional.Some(Data.Tuple(
Data.Char('a'),
Data.String("bc")
))),
testEval("unconsSingleChar")("StringTests", "stringUnconsTest", "a")(Data.Optional.Some(Data.Tuple(
Data.Char('a'),
Data.String("")
))),
testEval("unconsEmpty")("StringTests", "stringUnconsTest", "")(Data.Optional.None(Concept.Tuple(
List(Concept.Char, Concept.String)
))),
testEvaluation("toList")("StringTests", "stringToListTest")(Data.List(
Data.Char('a'),
Data.Char('b'),
Data.Char('c')
)),
testEvaluation("fromList")("StringTests", "stringFromListTest")(Data.String("abc")),
testEvaluation("fromListEmpty")("StringTests", "stringFromListEmptyTest")(Data.String("")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(5, "1"))(Data.String(" 1 ")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(5, "11"))(Data.String(" 11 ")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(5, "121"))(Data.String(" 121 ")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(5, "1234"))(Data.String(" 1234")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(5, "12345"))(Data.String("12345")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(5, "123456"))(Data.String("123456")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(0, "123"))(Data.String("123")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(-5, "123"))(Data.String("123")),
testEvalMultiple("pad")("StringTests", "stringPadTest", List(5, ""))(Data.String(" ")),
testEvaluation("map")("StringTests", "stringMapTest")(Data.String("a.b.c")),
testEvaluation("filter")("StringTests", "stringFilterTest")(Data.String("bc")),
testEval("foldl")("StringTests", "stringFoldlTest", "UPPERCASE")(Data.Boolean(true)),
testEval("foldl")("StringTests", "stringFoldlTest", "lowercase")(Data.Boolean(false)),
testEval("foldl")("StringTests", "stringFoldlTest", "camelCase")(Data.Boolean(false)),
testEval("foldl")("StringTests", "stringFoldlTest2", "time")(Data.String("emit")),
testEval("foldr")("StringTests", "stringFoldrTest", "Hello, World")(Data.Int(2)),
testEval("foldr")("StringTests", "stringFoldrTest", "HELLO, WORLD")(Data.Int(10)),
testEval("foldr")("StringTests", "stringFoldrTest2", "time")(Data.String("time")),
testEval("any")("StringTests", "stringAnyTest", "scala")(Data.Boolean(true)),
testEval("any")("StringTests", "stringAnyTest", "elm")(Data.Boolean(false)),
testEval("all")("StringTests", "stringAllTest", "aaa")(Data.Boolean(true)),
testEval("all")("StringTests", "stringAllTest", "abc")(Data.Boolean(false))
),
suite("References To user Defined Members")(
testEvaluation("Reference to value")("userDefinedReferenceTests", "userDefinedReferenceValueTest")(Data.Int(5)),
Expand Down
14 changes: 13 additions & 1 deletion morphir/src/org/finos/morphir/runtime/NativeSDK.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,19 @@ object NativeSDK {
NativeFunctionAdapter.Fun1(sdk.StringSDK.toUpper),
NativeFunctionAdapter.Fun1(sdk.StringSDK.trim),
NativeFunctionAdapter.Fun1(sdk.StringSDK.trimLeft),
NativeFunctionAdapter.Fun1(StringSDK.trimRight)
NativeFunctionAdapter.Fun1(sdk.StringSDK.trimRight),
NativeFunctionAdapter.Fun1(sdk.StringSDK.fromChar),
NativeFunctionAdapter.Fun2(sdk.StringSDK.cons),
NativeFunctionAdapter.Fun1(sdk.StringSDK.uncons),
NativeFunctionAdapter.Fun1(sdk.StringSDK.toList),
NativeFunctionAdapter.Fun1(sdk.StringSDK.fromList),
NativeFunctionAdapter.Fun3(sdk.StringSDK.pad),
NativeFunctionAdapter.Fun2(sdk.StringSDK.map),
NativeFunctionAdapter.Fun2(sdk.StringSDK.filter),
NativeFunctionAdapter.Fun3(sdk.StringSDK.foldl),
NativeFunctionAdapter.Fun3(sdk.StringSDK.foldr),
NativeFunctionAdapter.Fun2(sdk.StringSDK.any),
NativeFunctionAdapter.Fun2(sdk.StringSDK.all)
)
}

Expand Down
97 changes: 96 additions & 1 deletion morphir/src/org/finos/morphir/runtime/sdk/StringSDK.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import org.finos.morphir.runtime.RTValue
import org.finos.morphir.runtime.RTValue as RT
import org.finos.morphir.runtime.RTValue.Primitive.String as RTString
import org.finos.morphir.runtime.RTValue.{coerceList, coerceString}

import org.finos.morphir.ir.Type
import scala.collection.StringOps

object StringSDK {
Expand Down Expand Up @@ -155,4 +155,99 @@ object StringSDK {
val result = str.value.stripTrailing()
RTString(result)
}

val fromChar = DynamicNativeFunction1("fromChar") {
(context: NativeContext) => (char: RT.Primitive.Char) =>
RTString(char.value.toString)
}

val cons = DynamicNativeFunction2("cons") {
(context: NativeContext) => (char: RT.Primitive.Char, str: RTString) =>
RTString(char.value.toString + str.value)
}

val uncons = DynamicNativeFunction1("uncons") {
(context: NativeContext) => (str: RTString) =>
str.value match {
case "" => MaybeSDK.optionToMaybe(None)
case _ =>
MaybeSDK.optionToMaybe(Some(RT.Tuple(RT.Primitive.Char(str.value.head), RTString(str.value.tail))))
}
}

val toList = DynamicNativeFunction1("toList") {
(context: NativeContext) => (str: RTString) =>
val result = str.value.toList.map(c => RT.Primitive.Char(c))
RT.List(result)
}

val fromList = DynamicNativeFunction1("fromList") {
(context: NativeContext) => (list: RT.List) =>
val result = list.elements.map(c => RTValue.coerceChar(c).value).mkString
RTString(result)
}

val pad = DynamicNativeFunction3("pad") {
(context: NativeContext) => (n: RT.Primitive.Int, ch: RT.Primitive.Char, str: RTString) =>
val totalExtra = n.valueAsInt - str.value.length
val half = totalExtra / 2.0
val (leftPadding, rightPadding) = (
ch.value.toString * Math.ceil(half).toInt,
ch.value.toString * Math.floor(half).toInt
)
val result = leftPadding ++ str.value ++ rightPadding
RTString(result)
}

val map = DynamicNativeFunction2("map") {
(context: NativeContext) => (f: RTValue.Function, str: RTString) =>
val result = str.value.map { c =>
val res = context.evaluator.handleApplyResult(Type.UType.Unit(()), f, RT.Primitive.Char(c))
RTValue.coerceChar(res).value
}
RTString(result)
}

val filter = DynamicNativeFunction2("filter") {
(context: NativeContext) => (f: RTValue.Function, str: RTString) =>
val result = str.value.filter { c =>
val res = context.evaluator.handleApplyResult(Type.UType.Unit(()), f, RT.Primitive.Char(c))
RTValue.coerceBoolean(res).value
}
RTString(result)
}

val foldl = DynamicNativeFunction3("foldl") {
(context: NativeContext) => (f: RTValue.Function, acc: RTValue, str: RTString) =>
val result = str.value.foldLeft(acc) { (acc, c) =>
context.evaluator.handleApplyResult2(Type.UType.Unit(()), f, RT.Primitive.Char(c), acc)
}
result
}

val foldr = DynamicNativeFunction3("foldr") {
(context: NativeContext) => (f: RTValue.Function, acc: RTValue, str: RTString) =>
val result = str.value.foldRight(acc) { (c, acc) =>
context.evaluator.handleApplyResult2(Type.UType.Unit(()), f, RT.Primitive.Char(c), acc)
}
result
}

val any = DynamicNativeFunction2("any") {
(context: NativeContext) => (f: RTValue.Function, str: RTString) =>
val result = str.value.exists { c =>
val res = context.evaluator.handleApplyResult(Type.UType.Unit(()), f, RT.Primitive.Char(c))
RTValue.coerceBoolean(res).value
}
RT.Primitive.Boolean(result)
}

val all = DynamicNativeFunction2("all") {
(context: NativeContext) => (f: RTValue.Function, str: RTString) =>
val result = str.value.forall { c =>
val res = context.evaluator.handleApplyResult(Type.UType.Unit(()), f, RT.Primitive.Char(c))
RTValue.coerceBoolean(res).value
}
RT.Primitive.Boolean(result)
}
}