use tabnas::GrammarSpec;
#[test]
fn the_string_matcher_is_usable_on_a_plain_jsonic_instance() {
let mut parser = tabnas_jsonic::make();
parser.lex_match_factory_ref("@csv-string", tabnas_csv::csv_string_matcher("\""));
let spec = GrammarSpec::from_value(serde_json::json!({
"options": { "lex": { "match": {
"stringcsv": { "order": 100000, "make": "@csv-string" },
} } },
}))
.expect("the options document is valid");
parser.grammar(&spec).expect("the options apply");
let value = parser
.parse(r#""a""b""#)
.expect("a doubled quote is an escape");
assert_eq!(value, tabnas::Value::String("a\"b".to_string()));
}
#[test]
fn the_quote_character_is_configurable() {
let mut parser = tabnas_jsonic::make();
parser.lex_match_factory_ref("@csv-string", tabnas_csv::csv_string_matcher("|"));
let spec = GrammarSpec::from_value(serde_json::json!({
"options": { "lex": { "match": {
"stringcsv": { "order": 100000, "make": "@csv-string" },
} } },
}))
.expect("the options document is valid");
parser.grammar(&spec).expect("the options apply");
let value = parser.parse("|a,b||c|").expect("a pipe-quoted string");
assert_eq!(value, tabnas::Value::String("a,b|c".to_string()));
}
#[test]
fn an_empty_quote_disables_the_matcher() {
let mut parser = tabnas_jsonic::make();
parser.lex_match_factory_ref("@csv-string", tabnas_csv::csv_string_matcher(""));
let spec = GrammarSpec::from_value(serde_json::json!({
"options": { "lex": { "match": {
"stringcsv": { "order": 100000, "make": "@csv-string" },
} } },
}))
.expect("the options document is valid");
parser.grammar(&spec).expect("the options apply");
let value = parser.parse(r#""a\"b""#).expect("a backslash escape");
assert_eq!(value, tabnas::Value::String("a\"b".to_string()));
}
#[test]
fn a_degenerate_quote_leaves_the_matcher_out() {
let options = tabnas::Options::default();
for quote in ["", "ab", "\"\"", "\u{1F600}"] {
assert!(
tabnas_csv::csv_string_matcher(quote)(&options).is_none(),
"quote {quote:?} should install no matcher"
);
}
for quote in ["\"", "|", "\u{20AC}"] {
assert!(
tabnas_csv::csv_string_matcher(quote)(&options).is_some(),
"quote {quote:?} is one code unit and should install a matcher"
);
}
}
#[test]
fn the_string_matcher_honours_allow_control() {
let make_quoted = |string: serde_json::Value, quote: &str| {
let mut parser = tabnas_jsonic::make();
parser.lex_match_factory_ref("@csv-string", tabnas_csv::csv_string_matcher(quote));
let spec = GrammarSpec::from_value(serde_json::json!({
"options": {
"string": string,
"lex": { "match": {
"stringcsv": { "order": 100000, "make": "@csv-string" },
} },
},
}))
.expect("the options document is valid");
parser.grammar(&spec).expect("the options apply");
parser
};
let make = |string: serde_json::Value| make_quoted(string, "\"");
let value = make_quoted(serde_json::json!({}), "\u{1e}")
.parse("\u{1e}x y\u{1e}")
.expect("a control-character quote opens and closes the field");
assert_eq!(value, tabnas::Value::String("x y".to_string()));
for string in [
serde_json::json!({}),
serde_json::json!({ "allowControl": false }),
] {
let error = make(string.clone())
.parse("\"a\tb\"")
.expect_err("a raw TAB is refused with allowControl off");
assert_eq!(error.code, "unprintable", "under {string}");
}
let on = serde_json::json!({ "allowControl": true });
for (src, want) in [
("\"a\tb\"\"c\"", "a\tb\"c"),
("\"\u{0}\u{1e}\u{1f}\"", "\u{0}\u{1e}\u{1f}"),
] {
let value = make(on.clone())
.parse(src)
.unwrap_or_else(|error| panic!("{src:?} with allowControl on: {error}"));
assert_eq!(value, tabnas::Value::String(want.to_string()), "{src:?}");
}
}