Skip to content

Commit 31901ba

Browse files
committed
feat(quoting): WithoutQuotingValidator does not have to check len>=0
Many validators states consisted soley of checking if the token had length > 0, this is now done by the quote_if_needed algorithm.
1 parent 45e5849 commit 31901ba

File tree

7 files changed

+75
-38
lines changed

7 files changed

+75
-38
lines changed

src/error.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,39 @@ pub enum CoreError {
88
UnquoteableCharQuoted,
99
DoesNotStartWithDQuotes,
1010
DoesNotEndWithDQuotes,
11-
InvalidChar
11+
InvalidChar,
12+
ZeroSizedValue
1213
}
1314

15+
impl CoreError {
16+
17+
pub fn id(&self) -> u8 {
18+
use self::CoreError::*;
19+
match *self {
20+
AdvancedFailedAutomaton => 0,
21+
QuotedStringAlreadyEnded => 1,
22+
UnquoteableCharQuoted => 2,
23+
DoesNotStartWithDQuotes => 3,
24+
DoesNotEndWithDQuotes => 4,
25+
InvalidChar => 5,
26+
ZeroSizedValue => 6,
27+
}
28+
}
29+
30+
pub fn from_id(id: u8) -> Option<Self> {
31+
use self::CoreError::*;
32+
Some(match id {
33+
0 => AdvancedFailedAutomaton,
34+
1 => QuotedStringAlreadyEnded,
35+
2 => UnquoteableCharQuoted,
36+
3 => DoesNotStartWithDQuotes,
37+
4 => DoesNotEndWithDQuotes,
38+
5 => InvalidChar,
39+
6 => ZeroSizedValue,
40+
_ => return None
41+
})
42+
}
43+
}
1444
impl Display for CoreError {
1545
fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
1646
fter.write_str(self.description())
@@ -32,7 +62,9 @@ impl StdError for CoreError {
3262
DoesNotEndWithDQuotes =>
3363
"quoted string did not end with \"",
3464
InvalidChar =>
35-
"char can not be represented in a quoted string (without encoding)"
65+
"char can not be represented in a quoted string (without encoding)",
66+
ZeroSizedValue =>
67+
"value had a size of zero chars/bytes but has to have at last one"
3668
}
3769
}
3870
}

src/iter.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::str::Chars;
22
use std::iter::Iterator;
33
use std::cmp::{ PartialEq };
44

5+
use error::CoreError;
56
use spec::{GeneralQSSpec, ScanAutomaton, PartialCodePoint};
67
// this import will become unused in future rust versions
78
// but won't be removed for now for supporting current
@@ -98,7 +99,7 @@ impl<'s, Impl> ContentChars<'s, Impl>
9899
impl<'a, Impl> Iterator for ContentChars<'a, Impl>
99100
where Impl: GeneralQSSpec
100101
{
101-
type Item = Result<char, Impl::Error>;
102+
type Item = Result<char, CoreError>;
102103

103104
fn next(&mut self) -> Option<Self::Item> {
104105
loop {

src/parse.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use spec::{ScanAutomaton, GeneralQSSpec, PartialCodePoint};
2+
use error::CoreError;
23

34
/// validates if input is a valid quoted-string
45
///
@@ -54,7 +55,7 @@ pub struct Parsed<'a> {
5455
/// });
5556
/// ```
5657
///
57-
pub fn parse<Impl: GeneralQSSpec>(input: &str) -> Result<Parsed, (usize, Impl::Error)> {
58+
pub fn parse<Impl: GeneralQSSpec>(input: &str) -> Result<Parsed, (usize, CoreError)> {
5859
let mut automaton = ScanAutomaton::<Impl::Parsing>::new();
5960

6061
for (idx, bch) in input.bytes().enumerate() {

src/quote.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use spec::{
3232
#[inline]
3333
pub fn quote<Spec: GeneralQSSpec>(
3434
input: &str
35-
) -> Result<String, Spec::Error>
35+
) -> Result<String, CoreError>
3636
{
3737
let mut out = String::with_capacity(input.len()+2);
3838
out.push('"');
@@ -50,20 +50,14 @@ pub fn quote<Spec: GeneralQSSpec>(
5050
fn quote_inner<Spec: GeneralQSSpec>(
5151
input: &str,
5252
out: &mut String,
53-
) -> Result<(), Spec::Error>
53+
) -> Result<(), CoreError>
5454
{
5555
use self::QuotingClass::*;
5656
for ch in input.chars() {
5757
match Spec::Quoting::classify_for_quoting(PartialCodePoint::from_code_point(ch as u32)) {
5858
QText => out.push(ch),
5959
NeedsQuoting => { out.push('\\'); out.push(ch); }
60-
Invalid => {
61-
let err: <Spec::Quoting as QuotingClassifier>::Error
62-
= CoreError::InvalidChar.into();
63-
let err: Spec::Error
64-
= err.into();
65-
return Err(err)
66-
}
60+
Invalid => return Err(CoreError::InvalidChar)
6761
}
6862
}
6963
Ok(())
@@ -97,7 +91,7 @@ fn quote_inner<Spec: GeneralQSSpec>(
9791
pub fn quote_if_needed<'a, Spec, WQImpl>(
9892
input: &'a str,
9993
validator: &mut WQImpl
100-
) -> Result<Cow<'a, str>, Spec::Error>
94+
) -> Result<Cow<'a, str>, CoreError>
10195
where Spec: GeneralQSSpec,
10296
WQImpl: WithoutQuotingValidator
10397
{
@@ -109,6 +103,7 @@ pub fn quote_if_needed<'a, Spec, WQImpl>(
109103
needs_quoting_from = Some(idx);
110104
break;
111105
} else {
106+
//FIXME check if is this even enabled in the right context
112107
#[cfg(debug_assertions)]
113108
{
114109
match Spec::Quoting::classify_for_quoting(pcp) {
@@ -123,7 +118,9 @@ pub fn quote_if_needed<'a, Spec, WQImpl>(
123118
}
124119

125120
let start_quoting_from =
126-
if let Some(offset) = needs_quoting_from {
121+
if input.len() == 0 {
122+
0
123+
} else if let Some(offset) = needs_quoting_from {
127124
offset
128125
} else {
129126
return if validator.end() {
@@ -199,7 +196,7 @@ mod test {
199196
let out = quote_if_needed::<TestSpec, _>("ab def", &mut without_quoting).unwrap();
200197
let expected: Cow<'static, str> = Cow::Owned("\"ab def\"".into());
201198
assert_eq!(out, expected);
202-
assert!(without_quoting.count >= 3);
199+
assert!(without_quoting.count >= 2);
203200
}
204201

205202
#[test]
@@ -208,7 +205,7 @@ mod test {
208205
let out = quote_if_needed::<TestSpec, _>("abc..f", &mut without_quoting).unwrap();
209206
let expected: Cow<'static, str> = Cow::Owned("\"abc..f\"".into());
210207
assert_eq!(out, expected);
211-
assert!(without_quoting.count >= 5);
208+
assert!(without_quoting.count >= 4);
212209
}
213210

214211
#[test]
@@ -220,4 +217,12 @@ mod test {
220217
assert!(without_quoting.count >= 1);
221218
}
222219

220+
#[test]
221+
fn quote_if_needed_empty_value() {
222+
let mut without_quoting = TestUnquotedValidator::new();
223+
let out = quote_if_needed::<TestSpec, _>("", &mut without_quoting).unwrap();
224+
let expected: Cow<'static, str> = Cow::Owned("\"\"".into());
225+
assert_eq!(out, expected);
226+
assert_eq!(without_quoting.count, 0);
227+
}
223228
}

src/spec.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,11 @@ use std::fmt::Debug;
22
use error::CoreError;
33

44
pub trait GeneralQSSpec: Clone+Debug {
5-
type Error:
6-
From<<Self::Quoting as QuotingClassifier>::Error>
7-
+ From<<Self::Parsing as ParsingImpl>::Error>;
85
type Quoting: QuotingClassifier;
96
type Parsing: ParsingImpl;
107
}
118

129
pub trait QuotingClassifier {
13-
type Error: From<CoreError>;
1410
fn classify_for_quoting(pcp: PartialCodePoint) -> QuotingClass;
1511
}
1612

@@ -21,8 +17,10 @@ pub enum QuotingClass {
2117
}
2218

2319
pub trait WithoutQuotingValidator {
20+
/// if next returns false, it's state should NOT be modified i.e. calling
21+
/// .end() after next(..) returned false corresponds to the input sequence _until_ next(..) was false
2422
fn next(&mut self, pcp: PartialCodePoint) -> bool;
25-
fn end(&mut self) -> bool;
23+
fn end(&self) -> bool;
2624
}
2725

2826

@@ -38,10 +36,9 @@ pub enum State<T: Copy+Eq+Debug> {
3836

3937

4038
pub trait ParsingImpl: Copy+Eq+Debug {
41-
type Error: From<CoreError>;
4239
fn can_be_quoted(bch: PartialCodePoint) -> bool;
43-
fn handle_normal_state(bch: PartialCodePoint) -> Result<(State<Self>, bool), Self::Error>;
44-
fn advance(&self, _pcp: PartialCodePoint) -> Result<(State<Self>, bool), Self::Error> {
40+
fn handle_normal_state(bch: PartialCodePoint) -> Result<(State<Self>, bool), CoreError>;
41+
fn advance(&self, _pcp: PartialCodePoint) -> Result<(State<Self>, bool), CoreError> {
4542
unreachable!("[BUG] custom state is not used, so advance is unreachable")
4643
}
4744
}
@@ -64,15 +61,15 @@ impl<Impl> ScanAutomaton<Impl>
6461
self.state == State::End
6562
}
6663

67-
pub fn end(&mut self) -> Result<(), Impl::Error> {
64+
pub fn end(&mut self) -> Result<(), CoreError> {
6865
if self.did_end() {
6966
Ok(())
7067
} else {
7168
Err(CoreError::DoesNotEndWithDQuotes.into())
7269
}
7370
}
7471

75-
pub fn advance(&mut self, pcp: PartialCodePoint) -> Result<bool, Impl::Error> {
72+
pub fn advance(&mut self, pcp: PartialCodePoint) -> Result<bool, CoreError> {
7673
match _advance_scan_automaton(self.state, pcp) {
7774
Ok((state, emit)) => {
7875
self.state = state;
@@ -88,7 +85,7 @@ impl<Impl> ScanAutomaton<Impl>
8885
}
8986

9087
fn _advance_scan_automaton<Impl: ParsingImpl>(state: State<Impl>, pcp: PartialCodePoint)
91-
-> Result<(State<Impl>, bool), Impl::Error>
88+
-> Result<(State<Impl>, bool), CoreError>
9289
{
9390
use self::State::*;
9491
let pcp_val = pcp.as_u8();
@@ -97,7 +94,7 @@ fn _advance_scan_automaton<Impl: ParsingImpl>(state: State<Impl>, pcp: PartialCo
9794
if pcp_val == b'"' {
9895
Ok((Normal, false))
9996
} else {
100-
Err(CoreError::DoesNotStartWithDQuotes.into())
97+
Err(CoreError::DoesNotStartWithDQuotes)
10198
}
10299
}
103100
Normal => {

src/test_utils.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ use error::CoreError;
1515
pub struct TestSpec;
1616

1717
impl GeneralQSSpec for TestSpec {
18-
type Error = CoreError;
1918
type Quoting = Self;
2019
type Parsing = TestParsingImpl;
2120
}
2221

2322
impl QuotingClassifier for TestSpec {
24-
type Error = CoreError;
2523
fn classify_for_quoting(pcp: PartialCodePoint) -> QuotingClass {
2624
if !is_valid_pcp(pcp) {
2725
QuotingClass::Invalid
@@ -57,11 +55,10 @@ pub enum TestParsingImpl {
5755
}
5856

5957
impl ParsingImpl for TestParsingImpl {
60-
type Error = CoreError;
6158
fn can_be_quoted(bch: PartialCodePoint) -> bool {
6259
is_valid_pcp(bch)
6360
}
64-
fn handle_normal_state(bch: PartialCodePoint) -> Result<(State<Self>, bool), Self::Error> {
61+
fn handle_normal_state(bch: PartialCodePoint) -> Result<(State<Self>, bool), CoreError> {
6562
if bch.as_u8() == b'\n' {
6663
Ok((State::Custom(TestParsingImpl::StrangeInc(0)), false))
6764
} else if is_valid_pcp(bch) {
@@ -71,7 +68,7 @@ impl ParsingImpl for TestParsingImpl {
7168
}
7269
}
7370

74-
fn advance(&self, pcp: PartialCodePoint) -> Result<(State<Self>, bool), Self::Error> {
71+
fn advance(&self, pcp: PartialCodePoint) -> Result<(State<Self>, bool), CoreError> {
7572
use self::TestParsingImpl::*;
7673
let bch = pcp.as_u8();
7774
match *self {
@@ -120,10 +117,9 @@ impl TestUnquotedValidator {
120117

121118
impl WithoutQuotingValidator for TestUnquotedValidator {
122119
fn next(&mut self, pcp: PartialCodePoint) -> bool {
123-
self.count += 1;
124120
let bch = pcp.as_u8();
125121
let lwd = self.last_was_dot;
126-
match bch {
122+
let res = match bch {
127123
b'a'...b'z' => {
128124
self.last_was_dot = false;
129125
true
@@ -133,9 +129,13 @@ impl WithoutQuotingValidator for TestUnquotedValidator {
133129
true
134130
}
135131
_ => false
132+
};
133+
if res {
134+
self.count += 1;
136135
}
136+
res
137137
}
138-
fn end(&mut self) -> bool {
138+
fn end(&self) -> bool {
139139
self.count == 6 && !self.last_was_dot
140140
}
141141
}

src/unquote.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use spec::{GeneralQSSpec, ScanAutomaton, PartialCodePoint};
2+
use error::CoreError;
23
use std::borrow::Cow;
34

45
/// converts a quoted string into it's content
@@ -25,7 +26,7 @@ use std::borrow::Cow;
2526
///
2627
pub fn to_content<'a, Spec: GeneralQSSpec>(
2728
quoted_string: &'a str
28-
) -> Result<Cow<'a, str>, Spec::Error>
29+
) -> Result<Cow<'a, str>, CoreError>
2930
{
3031
let mut automaton = ScanAutomaton::<Spec::Parsing>::new();
3132
let mut continue_copy_from = None;

0 commit comments

Comments
 (0)