1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
use sophia_api::{source::StreamResult, term::BaseDirection};
use crate::_term::IndexedTerm;
use crate::lazy_regex;
use crate::parser::_common::{Inner, unescape_iri, unescape_literal};
use crate::parser::_error::ResultExt;
use crate::parser::{Error, ErrorKind};
/// Common super-trait of `TripleSource`/`QuadSource` for all formats.
pub(crate) trait GenericSource {
/// The type of tuple this source outputs
type Output<'x>;
/// Get the common inner attributes of the source
fn inner(&self) -> &Inner;
/// Get mutably the common inner attributes of the source
fn inner_mut(&mut self) -> &mut Inner;
/// Feed `callback` with the triple/quad last parsed
///
/// Precondition: `source` is in an appropriate state
fn emit_tuple<C, E, E2>(&self, callback: &mut C, offset: usize) -> StreamResult<usize, E, E2>
where
C: FnMut(Self::Output<'_>) -> Result<(), E2>,
E: std::error::Error,
E2: std::error::Error;
// terminal productions
/// Partially handle the whole production https://www.w3.org/TR/rdf12-turtle/#grammar-production-IRIREF
/// (or the equivalent in N-Triples, N-Quads, TriG)
/// assuming the leading '<'.
///
/// This parses the text until the closing '>', and allocates it in the buffer stash.
/// Note however that this method DOES NOT push anything in the term stack,
/// because how it is done exactly depends on the parser
/// (Turtle and TriG may resolve it against the base IRI,
/// while N-Triples and N-Quads always expect an absolute IRI).
///
/// `txt` is the remaining of a line of text as read by [`BufRead::read_line`],
/// starting with '<'.
fn iriref_raw<E2>(&mut self, txt: &str) -> StreamResult<usize, Error, E2>
where
E2: std::error::Error + Send + Sync + 'static,
{
debug_assert!(txt.starts_with('<'));
let buf = self.inner_mut().buffers.push();
let txtb = txt.as_bytes();
let mut i = 1;
loop {
match txt[i..].find(['\n', '\r', '\\', '>']).map(|x| x + i) {
Some(j) if txtb[j] == b'\\' => {
buf.push_str(&txt[i..j]);
if let Err(err) = unescape_iri(&txt[j + 1..], &mut i, buf, j + 1) {
return Err(err).wrap_in_at(self, j);
}
}
Some(j) if txtb[j] == b'>' => {
buf.push_str(&txt[i..j]);
i = j;
break;
}
opt => {
let j = opt.unwrap_or(txt.len());
debug_assert!(j >= txtb.len() || txtb[j] == b'\n' || txtb[j] == b'\r');
return Err(ErrorKind::Expected("matching closing '>'".into())).wrap_in(self);
}
}
}
debug_assert!(txtb[i] == b'>');
Ok(i + 1)
}
/// Partially handle the whole production https://www.w3.org/TR/rdf12-turtle/#grammar-production-BLANK_NODE_LABEL
/// (or the equivalent in N-Triples, N-Quads, TriG)
/// assuming the leading '_'.
///
/// Syntaxes that mint bnode labels (Turtle, TriG)
/// must additionally avoid conflict with their own generated labels.
///
/// `txt` is the remaining of a line of text as read by [`BufRead::read_line`],
/// starting with '_'.
fn blank_node_label_raw<E2>(&mut self, txt: &str) -> StreamResult<usize, Error, E2>
where
E2: std::error::Error + Send + Sync + 'static,
{
debug_assert!(txt.starts_with('_'));
lazy_regex!(
LABEL = r#"(?x) ^
# (PN_CHARS_U | [0-9]) ((PN_CHARS | '.')* PN_CHARS)?
[
A-Z a-z \xC0-\xD6 \xD8-\xF6 \xF8-\u{02FF} \u{0370}-\u{037D}
\u{037F}-\u{1FFF} \u{200C}-\u{200D} \u{2070}-\u{218F} \u{2C00}-\u{2FEF}
\u{3001}-\u{D7FF} \u{F900}-\u{FDCF} \u{FDF0}-\u{FFFD} \u{10000}-\u{EFFFF}
_
0-9
]
(?:
[
A-Z a-z \xC0-\xD6 \xD8-\xF6 \xF8-\u{02FF} \u{0370}-\u{037D}
\u{037F}-\u{1FFF} \u{200C}-\u{200D} \u{2070}-\u{218F} \u{2C00}-\u{2FEF}
\u{3001}-\u{D7FF} \u{F900}-\u{FDCF} \u{FDF0}-\u{FFFD} \u{10000}-\u{EFFFF}
_
\- 0-9 \xB7 \u{0300}-\u{036F} \u{203F}-\u{2040}
.
]*
[
A-Z a-z \xC0-\xD6 \xD8-\xF6 \xF8-\u{02FF} \u{0370}-\u{037D}
\u{037F}-\u{1FFF} \u{200C}-\u{200D} \u{2070}-\u{218F} \u{2C00}-\u{2FEF}
\u{3001}-\u{D7FF} \u{F900}-\u{FDCF} \u{FDF0}-\u{FFFD} \u{10000}-\u{EFFFF}
_
\- 0-9 \xB7 \u{0300}-\u{036F} \u{203F}-\u{2040}
]
)?
"#
);
let buffer_idx = self.inner().buffers.len();
if !txt[1..].starts_with(':') {
Err(ErrorKind::Expected("':'".into())).wrap_in_at(self, 1)
} else if let Some(cap) = LABEL.find(&txt[2..]) {
let inner_mut = self.inner_mut();
let buf = inner_mut.buffers.push();
buf.push_str(cap.as_str());
buf.push_str(&inner_mut.bn_suffix);
inner_mut.terms.push(IndexedTerm::BlankNode(buffer_idx));
Ok(2 + cap.len())
} else {
Err(ErrorKind::Bnode).wrap_in_at(self, 2)
}
}
/// Expect production https://www.w3.org/TR/rdf12-turtle/#grammar-production-STRING_LITERAL_QUOTE
/// or production https://www.w3.org/TR/rdf12-turtle/#grammar-production-STRING_SINGLE_LITERAL_QUOTE
/// (or the equivalent in N-Triples, N-Quads or TriG)
///
/// `txt` is the remaining of a line of text as read by [`BufRead::read_line`],
/// starting at self.col.
fn in_string_literal_short<E2>(
&mut self,
txt: &str,
quote: u8,
) -> StreamResult<usize, Error, E2>
where
E2: std::error::Error + Send + Sync + 'static,
{
debug_assert!(!txt.is_empty());
let buf = self.inner_mut().buffers.push();
let txtb = txt.as_bytes();
let mut i = 0;
loop {
match txt[i..]
.find(['\n', '\r', '\\', quote as char])
.map(|x| x + i)
{
Some(j) if txtb[j] == b'\\' => {
buf.push_str(&txt[i..j]);
if let Err(err) = unescape_literal(&txt[j + 1..], &mut i, buf, j + 1) {
// .wrap_in_at can not be directly applied to the result of unescape_literal
// because .self is mutably borrowed via buf
return Err(err).wrap_in_at(self, j + 1);
}
}
Some(j) if txtb[j] == quote => {
buf.push_str(&txt[i..j]);
return Ok(j + 1);
}
opt => {
let j = opt.unwrap_or(txt.len());
debug_assert!(j >= txtb.len() || txtb[j] == b'\n' || txtb[j] == b'\r');
return Err(ErrorKind::Expected(format!(
"closing quote {:?}",
quote as char
)))
.wrap_in_at(self, j + 1);
}
}
}
}
/// Partially handle the whole production https://www.w3.org/TR/rdf12-turtle/#grammar-production-LANG_DIR
/// assuming the leading '@'.
///
/// `txt` is the remaining of a line of text as read by [`BufRead::read_line`],
/// starting with '@'.
fn lang_dir_raw<E2>(&mut self, txt: &str) -> StreamResult<usize, Error, E2>
where
E2: std::error::Error + Send + Sync + 'static,
{
debug_assert!(txt.starts_with('@'));
lazy_regex!(LANG = r"(?x) ^ [a-zA-Z]+ (?: - [a-zA-Z0-9]+ )*");
lazy_regex!(DIR = r"(?x) ^ -- [a-zA-Z]+");
match LANG.find(&txt[1..]) {
None => Err(ErrorKind::Expected("language tag".into())).wrap_in_at(self, 1),
Some(m1) => {
let idx = self.inner().buffers.len();
sophia_api::term::LanguageTag::new(m1.as_str())
.map_err(|_| ErrorKind::Expected("valid language tag".into()))
.wrap_in_at(self, 1)?;
self.inner_mut().buffers.push().push_str(m1.as_str());
let base_dir = DIR
.find(&txt[1 + m1.end()..])
.map(|m2| match &m2.as_str()[2..] {
"ltr" => Ok(BaseDirection::Ltr) as StreamResult<BaseDirection, Error, E2>,
"rtl" => Ok(BaseDirection::Rtl),
_ => Err(ErrorKind::Expected("'ltr' or 'rtl'".into()))
.wrap_in_at(self, 1 + m1.end() + m2.start() + 2)?,
})
.transpose()?;
let dir_len = if base_dir.is_some() { 5 } else { 0 };
self.inner_mut()
.terms
.push(IndexedTerm::LangString(idx - 1, idx, base_dir));
Ok(1 + m1.len() + dir_len)
}
}
}
// other methods
fn check_invalid_literal<E2>(&self, idx: usize) -> StreamResult<(), Error, E2>
where
E2: std::error::Error + Send + Sync + 'static,
{
lazy_regex!(
LANG_STRING = r"^http://www.w3.org/1999/02/22-rdf-syntax-ns#(dirL|l)angString$"
);
if LANG_STRING.find(&self.inner().buffers[idx]).is_some() {
Err(ErrorKind::InvalidLiteral).wrap_in(self)
} else {
Ok(())
}
}
fn pop_term(&mut self) {
debug_assert!(!self.inner().terms.is_empty());
let inner = self.inner_mut();
IndexedTerm::pop(&mut inner.terms, &mut inner.buffers);
}
}