From 89e26e37a27638716b2f953297c720f348116824 Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi Date: Mon, 27 Feb 2023 19:10:06 -0800 Subject: [PATCH] string concat (WIP) Signed-off-by: Anand Krishnamoorthi --- src/builtins/mod.rs | 8 ++- src/builtins/strings.rs | 27 +++++--- src/builtins/utils.rs | 39 +++++++++++ .../cases/builtins/strings/tests.yaml | 69 +++++++++++++++++++ 4 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 tests/interpreter/cases/builtins/strings/tests.yaml diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 0f5ed81..b55db45 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -6,6 +6,7 @@ pub mod arrays; pub mod comparison; pub mod numbers; pub mod sets; +pub mod strings; pub mod types; pub mod utils; @@ -24,12 +25,15 @@ pub type BuiltinFcn = fn(&Span, &[Expr], &[Value]) -> Result; lazy_static! { pub static ref BUILTINS: HashMap<&'static str, BuiltinFcn> = { let mut m : HashMap<&'static str, BuiltinFcn> = HashMap::new(); - + + // comparison functions are directly called. numbers::register(&mut m); aggregates::register(&mut m); arrays::register(&mut m); + sets::register(&mut m); types::register(&mut m); - + strings::register(&mut m); + m }; } diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs index f881320..fbc8bd9 100644 --- a/src/builtins/strings.rs +++ b/src/builtins/strings.rs @@ -1,19 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +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 anyhow::{bail, Result}; +use std::collections::HashMap; -fn ensure_numeric(fcn: &str, arg: &Expr, v: &Value) -> Result { - Ok(match &v { - Value::Number(n) => n.0 .0, - _ => { - let span = arg.span(); - bail!( - span.error(format!("`{fcn}` expects numeric argument. Got `{v}` instead").as_str()) - ) - } - }) +use anyhow::Result; + +pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { + m.insert("concat", concat); } +fn concat(span: &Span, params: &[Expr], args: &[Value]) -> Result { + let name = "concat"; + ensure_args_count(span, name, params, args, 2)?; + let delimiter = ensure_string(name, ¶ms[0], &args[0])?; + let collection = ensure_string_collection(name, ¶ms[1], &args[1])?; + Ok(Value::String(collection.join(&delimiter))) +} diff --git a/src/builtins/utils.rs b/src/builtins/utils.rs index f1f8ddc..5ed4c3e 100644 --- a/src/builtins/utils.rs +++ b/src/builtins/utils.rs @@ -53,6 +53,45 @@ pub fn ensure_string(fcn: &str, arg: &Expr, v: &Value) -> Result { }) } +pub fn ensure_string_element<'a>( + fcn: &str, + arg: &Expr, + v: &'a Value, + idx: usize, +) -> Result<&'a str> { + Ok(match &v { + Value::String(s) => s.as_str(), + _ => { + let span = arg.span(); + bail!(span.error( + format!("`{fcn}` expects string collection. Element {idx} is not a string.") + .as_str() + )) + } + }) +} + +pub fn ensure_string_collection<'a>(fcn: &str, arg: &Expr, v: &'a Value) -> Result> { + let mut collection = vec![]; + match &v { + Value::Array(a) => { + for (idx, elem) in a.iter().enumerate() { + collection.push(ensure_string_element(fcn, arg, elem, idx)?); + } + } + Value::Set(s) => { + for (idx, elem) in s.iter().enumerate() { + collection.push(ensure_string_element(fcn, arg, elem, idx)?); + } + } + _ => { + let span = arg.span(); + bail!(span.error(format!("`{fcn}` expects array/set of strings.").as_str())) + } + } + Ok(collection) +} + pub fn ensure_array(fcn: &str, arg: &Expr, v: Value) -> Result>> { Ok(match v { Value::Array(a) => a, diff --git a/tests/interpreter/cases/builtins/strings/tests.yaml b/tests/interpreter/cases/builtins/strings/tests.yaml new file mode 100644 index 0000000..6341b0a --- /dev/null +++ b/tests/interpreter/cases/builtins/strings/tests.yaml @@ -0,0 +1,69 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +cases: + - note: all + data: {} + modules: + - | + package test + + # Arrays + a1 = concat(", ", ["Hello", "world"]) + a2 = concat("", ["Hello", "world"]) # empty delimiter + a3 = concat(",", []) # empty array + 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 + + query: data.test + want_result: + a1: "Hello, world" + a2: "Helloworld" + a3: "" + a4: "" + s1: "Hello, world" + s2: "Helloworld" + s3: "" + s4: "" + + - note: invalid-null-delimiter + data: {} + modules: ["package test\nx=concat(null, [])"] + query: data.test + error: "`concat` expects string argument." + + - note: invalid-bool-delimiter + data: {} + modules: ["package test\nx=concat(true, [])"] + query: data.test + error: "`concat` expects string argument." + + - note: invalid-number-delimiter + data: {} + modules: ["package test\nx=concat(1, [])"] + query: data.test + error: "`concat` expects string argument." + + - note: invalid-array-delimiter + data: {} + modules: ["package test\nx=concat([], [])"] + query: data.test + error: "`concat` expects string argument." + + - note: invalid-set-delimiter + data: {} + modules: ["package test\nx=concat(set(), [])"] + query: data.test + error: "`concat` expects string argument." + + - note: invalid-object-delimiter + data: {} + modules: ["package test\nx=concat({}, [])"] + query: data.test + error: "`concat` expects string argument." +