diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs index 0204554..ee8a42b 100644 --- a/src/builtins/strings.rs +++ b/src/builtins/strings.rs @@ -5,7 +5,7 @@ use crate::ast::Expr; use crate::builtins; use crate::builtins::utils::{ensure_args_count, ensure_string, ensure_string_collection}; use crate::lexer::Span; -use crate::value::Value; +use crate::value::{Float, Value}; use std::collections::HashMap; @@ -14,6 +14,12 @@ use anyhow::Result; pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("concat", concat); m.insert("contains", contains); + m.insert("endswith", endswith); + m.insert("indexof", indexof); + m.insert("indexof_n", indexof_n); + m.insert("lower", lower); + m.insert("startswith", startswith); + m.insert("upper", upper); } fn concat(span: &Span, params: &[Expr], args: &[Value]) -> Result { @@ -31,3 +37,63 @@ fn contains(span: &Span, params: &[Expr], args: &[Value]) -> Result { let s2 = ensure_string(name, ¶ms[1], &args[1])?; Ok(Value::Bool(s1.contains(&s2))) } + +fn endswith(span: &Span, params: &[Expr], args: &[Value]) -> Result { + let name = "endswith"; + ensure_args_count(span, name, params, args, 2)?; + let s1 = ensure_string(name, ¶ms[0], &args[0])?; + let s2 = ensure_string(name, ¶ms[1], &args[1])?; + Ok(Value::Bool(s1.ends_with(&s2))) +} + +fn indexof(span: &Span, params: &[Expr], args: &[Value]) -> Result { + let name = "indexof"; + ensure_args_count(span, name, params, args, 2)?; + let s1 = ensure_string(name, ¶ms[0], &args[0])?; + let s2 = ensure_string(name, ¶ms[1], &args[1])?; + Ok(Value::from_float(match s1.find(&s2) { + Some(pos) => pos as i64, + _ => -1, + } as Float)) +} + +fn indexof_n(span: &Span, params: &[Expr], args: &[Value]) -> Result { + let name = "indexof_n"; + ensure_args_count(span, name, params, args, 2)?; + let s1 = ensure_string(name, ¶ms[0], &args[0])?; + let s2 = ensure_string(name, ¶ms[1], &args[1])?; + + let mut positions = vec![]; + let mut idx = 0; + while idx < s1.len() { + if let Some(pos) = s1.find(&s2) { + positions.push(Value::from_float(pos as Float)); + idx = pos + 1; + } else { + break; + } + } + Ok(Value::from_array(positions)) +} + +fn lower(span: &Span, params: &[Expr], args: &[Value]) -> Result { + let name = "lower"; + ensure_args_count(span, name, params, args, 1)?; + let s = ensure_string(name, ¶ms[0], &args[0])?; + Ok(Value::String(s.to_lowercase())) +} + +fn startswith(span: &Span, params: &[Expr], args: &[Value]) -> Result { + let name = "startswith"; + ensure_args_count(span, name, params, args, 2)?; + let s1 = ensure_string(name, ¶ms[0], &args[0])?; + let s2 = ensure_string(name, ¶ms[1], &args[1])?; + Ok(Value::Bool(s1.starts_with(&s2))) +} + +fn upper(span: &Span, params: &[Expr], args: &[Value]) -> Result { + let name = "upper"; + ensure_args_count(span, name, params, args, 1)?; + let s = ensure_string(name, ¶ms[0], &args[0])?; + Ok(Value::String(s.to_uppercase())) +} diff --git a/tests/interpreter/cases/builtins/strings/concat.yaml b/tests/interpreter/cases/builtins/strings/concat.yaml index 67d6f2d..ddcd383 100644 --- a/tests/interpreter/cases/builtins/strings/concat.yaml +++ b/tests/interpreter/cases/builtins/strings/concat.yaml @@ -9,16 +9,16 @@ cases: package test # Arrays - a1 = concat(", ", ["Hello", "world"]) + a1 = concat(", ", ["Hello", "world"]) a2 = concat("", ["Hello", "world"]) # empty delimiter a3 = concat(",", []) # empty array - a4 = concat("", []) # empty array and delimiter - + a4 = concat("", []) # empty array and delimiter + # Sets s1 = concat(", ", {"world", "Hello"}) s2 = concat("", {"world", "Hello"}) # empty delimiter s3 = concat(",", set()) # empty set - s4 = concat("", set()) # empty set and delimiter + s4 = concat("", set()) # empty set and delimiter query: data.test want_result: @@ -40,7 +40,7 @@ cases: y = concat(x, []) query: data.test want_result: {} - + - note: undefined-collection data: {} modules: @@ -50,7 +50,7 @@ cases: y = concat(",", x) query: data.test want_result: {} - + - note: invalid-null-delimiter data: {} modules: ["package test\nx=concat(null, [])"] @@ -74,7 +74,7 @@ cases: modules: ["package test\nx=concat([], [])"] query: data.test error: "`concat` expects string argument." - + - note: invalid-set-delimiter data: {} modules: ["package test\nx=concat(set(), [])"] @@ -86,4 +86,34 @@ cases: modules: ["package test\nx=concat({}, [])"] query: data.test error: "`concat` expects string argument." - + + + - note: invalid-null-collection + data: {} + modules: ["package test\nx=concat(\"\", null)"] + query: data.test + error: "`concat` expects array/set of strings." + + - note: invalid-bool-collection + data: {} + modules: ["package test\nx=concat(\"\", true)"] + query: data.test + error: "`concat` expects array/set of strings." + + - note: invalid-number-collection + data: {} + modules: ["package test\nx=concat(\"\", 1)"] + query: data.test + error: "`concat` expects array/set of strings." + + - note: invalid-string-collection + data: {} + modules: ["package test\nx=concat(\"\", \"\")"] + query: data.test + error: "`concat` expects array/set of strings." + + - note: invalid-object-collection + data: {} + modules: ["package test\nx=concat(\"\", {})"] + query: data.test + error: "`concat` expects array/set of strings." diff --git a/tests/interpreter/cases/builtins/strings/endswith.yaml b/tests/interpreter/cases/builtins/strings/endswith.yaml new file mode 100644 index 0000000..2d8c1d9 --- /dev/null +++ b/tests/interpreter/cases/builtins/strings/endswith.yaml @@ -0,0 +1,116 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +cases: + - note: all + data: {} + modules: + - | + package test + + v1 = endswith("Hello, world", "ld") + v2 = endswith("Hello world", "") # empty substring + v3 = endswith("", "ld") # empty string + v4 = endswith("", "") # empty substring and string + + query: data.test + want_result: + v1: true + v2: true + v3: false + v4: true + + - note: undefined-string + data: {} + modules: + - | + package test + x { false } + y = endswith(x, "") + query: data.test + want_result: {} + + - note: undefined-substring + data: {} + modules: + - | + package test + x { false } + y = endswith(",", x) + query: data.test + want_result: {} + + - note: invalid-null-string + data: {} + modules: ["package test\nx=endswith(null, ``)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-bool-string + data: {} + modules: ["package test\nx=endswith(true, ``)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-number-string + data: {} + modules: ["package test\nx=endswith(1, ``)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-array-string + data: {} + modules: ["package test\nx=endswith([], ``)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-set-string + data: {} + modules: ["package test\nx=endswith(set(), ``)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-object-string + data: {} + modules: ["package test\nx=endswith({}, ``)"] + query: data.test + error: "`endswith` expects string argument." + + + + + - note: invalid-null-substring + data: {} + modules: ["package test\nx=endswith(``, null)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-bool-substring + data: {} + modules: ["package test\nx=endswith(``, true)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-number-substring + data: {} + modules: ["package test\nx=endswith(``, 1)"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-array-substring + data: {} + modules: ["package test\nx=endswith(``, [])"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-set-substring + data: {} + modules: ["package test\nx=endswith(``, set())"] + query: data.test + error: "`endswith` expects string argument." + + - note: invalid-object-substring + data: {} + modules: ["package test\nx=endswith(``, {})"] + query: data.test + error: "`endswith` expects string argument."