Repository: microsoft/regorus (language: python)

Issue:
Strings::split incompatibility for empty delimiters
OPA and Regorus evaluate split differently when empty delimiters are
used.

In particular, the OPA implementation seems to follow the Go
strings.Split definition ("If sep is empty, Split splits after each
UTF-8 sequence"). In comparison, Regorus uses str::split, which also
returns the beginning and end of the string.

For example:
```
$ opa eval 'split("test", "")'
{
  "result": [
    {
      "expressions": [
        {
          "value": [
            "t",
            "e",
            "s",
            "t"
          ],
          "text": "split(\"test\", \"\")",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ]
    }
  ]
}
```

vs.

```
$ regorus eval 'split("test", "")'
{
  "result": [
    {
      "expressions": [
        {
          "value": [
            "",
            "t",
            "e",
            "s",
            "t",
            ""
          ],
          "text": "split(\"test\", \"\")",
          "location": {
            "row": 1,
            "col": 1
          }
        }
      ]
    }
  ]
}
```



Discussion / hints:

Fix patch:
diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs
index 611d0028..546a4a2b 100644
--- a/src/builtins/strings.rs
+++ b/src/builtins/strings.rs
@@ -146,11 +146,18 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
     let s = ensure_string(name, &params[0], &args[0])?;
     let delimiter = ensure_string(name, &params[1], &args[1])?;
 
-    Ok(Value::from_array(
+    // Handle https://github.com/microsoft/regorus/issues/291
+    let parts: Vec<Value> = if delimiter.as_ref() == "" {
+        // If delimiter is "", str::split returns a leading and trailing "" whereas Golang's split doesn't.
+        // Therefore avoid str::split and instead return each char as a Value::String.
+        s.chars().map(|c| Value::from(c.to_string())).collect()
+    } else {
         s.split(delimiter.as_ref())
             .map(|s| Value::String(s.into()))
-            .collect(),
-    ))
+            .collect()
+    };
+
+    Ok(Value::from(parts))
 }
 
 fn to_string(v: &Value, unescape: bool) -> String {


Test patch:
diff --git a/tests/interpreter/cases/builtins/strings/split.yaml b/tests/interpreter/cases/builtins/strings/split.yaml
new file mode 100644
index 00000000..a71444a3
--- /dev/null
+++ b/tests/interpreter/cases/builtins/strings/split.yaml
@@ -0,0 +1,17 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+cases:
+  - note: empty separator
+    data: {}
+    modules: []
+    query: "x := split(\"test\", \"\")"
+    want_result:
+      x: ["t", "e", "s", "t"]
+      
+  - note: empty separator, empty string
+    data: {}
+    modules: []
+    query: "x := split(\"\", \"\")"
+    want_result:
+      x: []