FEATURE: map, filter, and reduce over lists.

This commit is contained in:
Jeremy Wall 2019-01-18 19:58:57 -06:00
parent 25d84a771e
commit 1dba5f66ce
10 changed files with 102 additions and 17 deletions

7
Cargo.lock generated
View File

@ -499,10 +499,16 @@ dependencies = [
"serde_yaml 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"simple-error 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "simple-error 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.5" version = "0.1.5"
@ -652,6 +658,7 @@ dependencies = [
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
"checksum toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4a2ecc31b0351ea18b3fe11274b8db6e4d82bce861bbb22e6dbed40417902c65" "checksum toml 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "4a2ecc31b0351ea18b3fe11274b8db6e4d82bce861bbb22e6dbed40417902c65"
"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"

View File

@ -29,6 +29,7 @@ xml-rs = "0.8.0"
base64 = "0.10.0" base64 = "0.10.0"
regex = "1" regex = "1"
dirs = "1.0.4" dirs = "1.0.4"
unicode-segmentation = "1.2.1"
[build-dependencies] [build-dependencies]
walkdir = "2.2.7" walkdir = "2.2.7"

12
TODO.md
View File

@ -2,18 +2,6 @@
## Compile Errors as expression ## Compile Errors as expression
## String handling
Mostly handled by ranges and indexing for strings?
Type comparisons?
```
let foo = "foo";
foo is str; // evaluates to true
foo is int; // evaluates to false
```
## Query Language (Experimental) ## Query Language (Experimental)
You should be able to ask the compiler to tell you any value or set of values in the You should be able to ask the compiler to tell you any value or set of values in the

View File

@ -299,7 +299,7 @@ Functional processing expressions
--------------------------------- ---------------------------------
UCG has a few functional processing expressions called `map`, `filter`, and UCG has a few functional processing expressions called `map`, `filter`, and
`reduce`. All of them can process a list or tuple. `reduce`. All of them can process a string, list, or tuple.
Their syntax starts with either `map` `filter`, or `reduce followed by a symbol Their syntax starts with either `map` `filter`, or `reduce followed by a symbol
that references a valid macro and finally an expression that resolves to either that references a valid macro and finally an expression that resolves to either

View File

@ -46,6 +46,13 @@ assert {
desc = "map mapper [1, 2, 3, 4] == [2, 3, 4, 5]", desc = "map mapper [1, 2, 3, 4] == [2, 3, 4, 5]",
}; };
let s_mapper = macro(arg) => arg + ",";
assert {
ok = map s_mapper "foo" == "f,o,o,",
desc = "we can map over each character",
};
assert { assert {
ok = filter filtrator list2 == ["foo", "foo"], ok = filter filtrator list2 == ["foo", "foo"],
desc = "filter filtrator list2 == [\"foo\", \"foo\"]", desc = "filter filtrator list2 == [\"foo\", \"foo\"]",
@ -102,6 +109,13 @@ assert {
desc = "filter tpl_filter test_tpl == { quux = \"baz\" }", desc = "filter tpl_filter test_tpl == { quux = \"baz\" }",
}; };
let o_str_filter = macro(s) => s != "o";
assert {
ok = filter o_str_filter "foobar" == "fbar",
desc = "We can strip out characters",
};
let tpl_reducer = macro(acc, name, val) => acc{ let tpl_reducer = macro(acc, name, val) => acc{
keys = self.keys + [name], keys = self.keys + [name],
vals = self.vals + [val], vals = self.vals + [val],
@ -110,4 +124,18 @@ let tpl_reducer = macro(acc, name, val) => acc{
assert { assert {
ok = reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]}, ok = reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = ["foo", "quux"], vals = ["bar", "baz"]},
desc = "reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = [\"foo\", \"quux\"], vals = [\"bar\", \"baz\"]}", desc = "reduce tpl_reducer {keys = [], vals = []}, test_tpl == {keys = [\"foo\", \"quux\"], vals = [\"bar\", \"baz\"]}",
};
let str_identity_reducer = macro(acc, s) => acc + s;
assert {
ok = reduce str_identity_reducer "", "foo" == "foo",
desc = "identity reducer copies string",
};
let char_iter = macro(acc, s) => acc + [s];
assert {
ok = reduce char_iter [], "foo" == ["f", "o", "o"],
desc = "we can split a string into grapheme clusters",
}; };

View File

@ -27,6 +27,7 @@ use std::string::ToString;
use regex; use regex;
use simple_error; use simple_error;
use unicode_segmentation::UnicodeSegmentation;
use crate::ast::*; use crate::ast::*;
use crate::build::scope::{find_in_fieldlist, Scope, ValueMap}; use crate::build::scope::{find_in_fieldlist, Scope, ValueMap};
@ -1375,10 +1376,17 @@ impl<'a> FileBuilder<'a> {
acc = result; acc = result;
} }
} }
&Val::Str(ref s) => {
for gc in s.graphemes(true) {
let argvals = vec![acc.clone(), Rc::new(Val::Str(gc.to_string()))];
let result = macdef.eval(self.file.clone(), self, argvals)?;
acc = result;
}
}
other => { other => {
return Err(Box::new(error::BuildError::new( return Err(Box::new(error::BuildError::new(
format!( format!(
"Expected List or Tuple as target but got {:?}", "Expected List Str, or Tuple as target but got {:?}",
other.type_name() other.type_name()
), ),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
@ -1389,6 +1397,56 @@ impl<'a> FileBuilder<'a> {
Ok(acc) Ok(acc)
} }
fn eval_functional_string_processing(
&self,
s: &str,
def: &MacroDef,
typ: ProcessingOpType,
) -> Result<Rc<Val>, Box<dyn Error>> {
let mut result = String::new();
for gc in s.graphemes(true) {
let arg = Rc::new(Val::Str(gc.to_string()));
let out = def.eval(self.file.clone(), self, vec![arg])?;
match typ {
ProcessingOpType::Filter => {
match out.as_ref() {
Val::Boolean(b) => {
if *b {
result.push_str(gc);
}
}
Val::Empty => {
// noop
}
_ => {
return Err(Box::new(error::BuildError::new(
format!(
"Expected boolean or NULL for filter return but got {}",
out.type_name()
),
error::ErrorType::TypeFail,
def.pos.clone(),
)));
}
}
}
ProcessingOpType::Map => match out.as_ref() {
Val::Str(s) => {
result.push_str(&s);
}
_ => {
return Err(Box::new(error::BuildError::new(
format!("Expected string map return but got {}", out.type_name()),
error::ErrorType::TypeFail,
def.pos.clone(),
)));
}
},
}
}
Ok(Rc::new(Val::Str(result)))
}
fn eval_functional_processing( fn eval_functional_processing(
&self, &self,
def: &MapFilterOpDef, def: &MapFilterOpDef,
@ -1410,6 +1468,8 @@ impl<'a> FileBuilder<'a> {
return match maybe_target.as_ref() { return match maybe_target.as_ref() {
&Val::List(ref elems) => self.eval_functional_list_processing(elems, macdef, typ), &Val::List(ref elems) => self.eval_functional_list_processing(elems, macdef, typ),
&Val::Tuple(ref fs) => self.eval_functional_tuple_processing(fs, macdef, typ), &Val::Tuple(ref fs) => self.eval_functional_tuple_processing(fs, macdef, typ),
// TODO(jwall): Strings?
&Val::Str(ref s) => self.eval_functional_string_processing(s, macdef, typ),
other => Err(Box::new(error::BuildError::new( other => Err(Box::new(error::BuildError::new(
format!( format!(
"Expected List or Tuple as target but got {:?}", "Expected List or Tuple as target but got {:?}",

View File

@ -187,7 +187,7 @@ impl Scope {
format!("Invalid idx type {} for list lookup", field), format!("Invalid idx type {} for list lookup", field),
error::ErrorType::TypeFail, error::ErrorType::TypeFail,
pos.clone(), pos.clone(),
))) )));
} }
}; };
if idx < elems.len() { if idx < elems.len() {

View File

@ -158,7 +158,7 @@ impl ExecConverter {
"Exec args must be a list of strings or tuples of strings.", "Exec args must be a list of strings or tuples of strings.",
ErrorType::TypeFail, ErrorType::TypeFail,
Position::new(0, 0, 0), Position::new(0, 0, 0),
))) )));
} }
} }
} }

View File

@ -28,6 +28,7 @@ extern crate serde_json;
extern crate serde_yaml; extern crate serde_yaml;
extern crate simple_error; extern crate simple_error;
extern crate toml; extern crate toml;
extern crate unicode_segmentation;
extern crate xml; extern crate xml;
#[macro_use] #[macro_use]

View File

@ -31,7 +31,7 @@ fn is_symbol_char<'a>(i: OffsetStrIter<'a>) -> Result<OffsetStrIter<'a>, u8> {
return Result::Fail(Error::new( return Result::Fail(Error::new(
"Unexpected End of Input".to_string(), "Unexpected End of Input".to_string(),
Box::new(_i.clone()), Box::new(_i.clone()),
)) ));
} }
}; };
if (c as char).is_ascii_alphanumeric() || c == b'-' || c == b'_' { if (c as char).is_ascii_alphanumeric() || c == b'-' || c == b'_' {