1use std::collections::HashMap;
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct UpdateParseError {
17 pub message: String,
19 pub position: usize,
21 pub line: Option<usize>,
23 pub column: Option<usize>,
25}
26
27impl UpdateParseError {
28 pub fn new(message: impl Into<String>, position: usize) -> Self {
30 Self {
31 message: message.into(),
32 position,
33 line: None,
34 column: None,
35 }
36 }
37
38 pub fn with_location(mut self, line: usize, column: usize) -> Self {
40 self.line = Some(line);
41 self.column = Some(column);
42 self
43 }
44}
45
46impl fmt::Display for UpdateParseError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match (self.line, self.column) {
49 (Some(ln), Some(col)) => {
50 write!(f, "update parse error at {}:{}: {}", ln, col, self.message)
51 }
52 _ => write!(
53 f,
54 "update parse error at byte {}: {}",
55 self.position, self.message
56 ),
57 }
58 }
59}
60
61impl std::error::Error for UpdateParseError {}
62
63pub(crate) fn line_col(source: &str, byte_offset: usize) -> (usize, usize) {
65 let prefix = &source[..byte_offset.min(source.len())];
66 let line = prefix.chars().filter(|&c| c == '\n').count() + 1;
67 let last_newline = prefix.rfind('\n').map(|p| p + 1).unwrap_or(0);
68 let col = byte_offset.saturating_sub(last_newline) + 1;
69 (line, col)
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct TriplePattern {
79 pub subject: String,
80 pub predicate: String,
81 pub object: String,
82}
83
84impl TriplePattern {
85 pub fn new(
86 subject: impl Into<String>,
87 predicate: impl Into<String>,
88 object: impl Into<String>,
89 ) -> Self {
90 Self {
91 subject: subject.into(),
92 predicate: predicate.into(),
93 object: object.into(),
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum GraphTarget {
101 Graph(String),
103 Default,
105 Named,
107 All,
109}
110
111impl fmt::Display for GraphTarget {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 match self {
114 GraphTarget::Graph(iri) => write!(f, "GRAPH <{}>", iri),
115 GraphTarget::Default => write!(f, "DEFAULT"),
116 GraphTarget::Named => write!(f, "NAMED"),
117 GraphTarget::All => write!(f, "ALL"),
118 }
119 }
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub enum UpdateOperation {
125 InsertData {
127 triples: Vec<TriplePattern>,
129 graph: Option<String>,
131 },
132 DeleteData {
134 triples: Vec<TriplePattern>,
136 graph: Option<String>,
138 },
139 DeleteInsertWhere {
141 delete_triples: Vec<TriplePattern>,
142 insert_triples: Vec<TriplePattern>,
143 where_triples: Vec<TriplePattern>,
144 graph: Option<String>,
145 },
146 Load {
148 source_uri: String,
149 target_graph: Option<String>,
150 silent: bool,
151 },
152 Clear { target: GraphTarget, silent: bool },
154 Drop { target: GraphTarget, silent: bool },
156 CreateGraph { graph_iri: String, silent: bool },
158 Copy {
160 source: GraphTarget,
161 destination: GraphTarget,
162 silent: bool,
163 },
164 Move {
166 source: GraphTarget,
167 destination: GraphTarget,
168 silent: bool,
169 },
170 Add {
172 source: GraphTarget,
173 destination: GraphTarget,
174 silent: bool,
175 },
176}
177
178impl UpdateOperation {
179 pub fn kind_label(&self) -> &'static str {
181 match self {
182 UpdateOperation::InsertData { .. } => "INSERT DATA",
183 UpdateOperation::DeleteData { .. } => "DELETE DATA",
184 UpdateOperation::DeleteInsertWhere { .. } => "DELETE/INSERT WHERE",
185 UpdateOperation::Load { .. } => "LOAD",
186 UpdateOperation::Clear { .. } => "CLEAR",
187 UpdateOperation::Drop { .. } => "DROP",
188 UpdateOperation::CreateGraph { .. } => "CREATE GRAPH",
189 UpdateOperation::Copy { .. } => "COPY",
190 UpdateOperation::Move { .. } => "MOVE",
191 UpdateOperation::Add { .. } => "ADD",
192 }
193 }
194}
195
196#[derive(Debug, Clone, PartialEq)]
198pub struct UpdateRequest {
199 pub prefixes: HashMap<String, String>,
201 pub operations: Vec<UpdateOperation>,
203}
204
205pub struct UpdateParser {
211 prefixes: HashMap<String, String>,
213}
214
215impl Default for UpdateParser {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl UpdateParser {
222 pub fn new() -> Self {
224 Self {
225 prefixes: HashMap::new(),
226 }
227 }
228
229 pub fn with_prefixes(prefixes: HashMap<String, String>) -> Self {
231 Self { prefixes }
232 }
233
234 pub fn prefixes(&self) -> &HashMap<String, String> {
236 &self.prefixes
237 }
238
239 pub fn parse(&mut self, input: &str) -> Result<UpdateRequest, UpdateParseError> {
241 let mut pos = 0usize;
242 let mut operations = Vec::new();
243
244 pos = self.skip_ws(input, pos);
246 pos = self.parse_prologue(input, pos)?;
247
248 loop {
250 pos = self.skip_ws(input, pos);
251 if pos >= input.len() {
252 break;
253 }
254
255 let op = self.parse_operation(input, &mut pos)?;
256 operations.push(op);
257
258 pos = self.skip_ws(input, pos);
259 if pos < input.len() && input.as_bytes().get(pos) == Some(&b';') {
260 pos += 1; }
262 }
263
264 if operations.is_empty() {
265 let (ln, col) = line_col(input, pos);
266 return Err(UpdateParseError::new("empty update request", pos).with_location(ln, col));
267 }
268
269 Ok(UpdateRequest {
270 prefixes: self.prefixes.clone(),
271 operations,
272 })
273 }
274
275 fn parse_prologue(&mut self, input: &str, mut pos: usize) -> Result<usize, UpdateParseError> {
278 loop {
279 pos = self.skip_ws(input, pos);
280 if self.match_keyword(input, pos, "PREFIX") {
281 pos = self.consume_keyword(input, pos, "PREFIX")?;
282 pos = self.skip_ws(input, pos);
283
284 let (prefix, new_pos) = self.read_prefix_label(input, pos)?;
285 pos = self.skip_ws(input, new_pos);
286
287 let (iri, new_pos) = self.read_iri_ref(input, pos)?;
288 pos = new_pos;
289
290 self.prefixes.insert(prefix, iri);
291 } else if self.match_keyword(input, pos, "BASE") {
292 pos = self.consume_keyword(input, pos, "BASE")?;
293 pos = self.skip_ws(input, pos);
294 let (_, new_pos) = self.read_iri_ref(input, pos)?;
296 pos = new_pos;
297 } else {
298 break;
299 }
300 }
301 Ok(pos)
302 }
303
304 fn parse_operation(
307 &self,
308 input: &str,
309 pos: &mut usize,
310 ) -> Result<UpdateOperation, UpdateParseError> {
311 *pos = self.skip_ws(input, *pos);
312 if *pos >= input.len() {
313 let (ln, col) = line_col(input, *pos);
314 return Err(
315 UpdateParseError::new("unexpected end of input", *pos).with_location(ln, col)
316 );
317 }
318
319 if self.match_keyword(input, *pos, "INSERT") {
321 self.parse_insert(input, pos)
322 } else if self.match_keyword(input, *pos, "DELETE") {
323 self.parse_delete(input, pos)
324 } else if self.match_keyword(input, *pos, "LOAD") {
325 self.parse_load(input, pos)
326 } else if self.match_keyword(input, *pos, "CLEAR") {
327 self.parse_clear(input, pos)
328 } else if self.match_keyword(input, *pos, "DROP") {
329 self.parse_drop(input, pos)
330 } else if self.match_keyword(input, *pos, "CREATE") {
331 self.parse_create(input, pos)
332 } else if self.match_keyword(input, *pos, "COPY") {
333 self.parse_copy(input, pos)
334 } else if self.match_keyword(input, *pos, "MOVE") {
335 self.parse_move(input, pos)
336 } else if self.match_keyword(input, *pos, "ADD") {
337 self.parse_add(input, pos)
338 } else {
339 let (ln, col) = line_col(input, *pos);
340 let snippet: String = input[*pos..].chars().take(20).collect();
341 Err(UpdateParseError::new(
342 format!("expected update keyword, found: '{}'", snippet),
343 *pos,
344 )
345 .with_location(ln, col))
346 }
347 }
348
349 fn parse_insert(
352 &self,
353 input: &str,
354 pos: &mut usize,
355 ) -> Result<UpdateOperation, UpdateParseError> {
356 *pos = self.consume_keyword(input, *pos, "INSERT")?;
357 *pos = self.skip_ws(input, *pos);
358
359 if self.match_keyword(input, *pos, "DATA") {
360 *pos = self.consume_keyword(input, *pos, "DATA")?;
361 *pos = self.skip_ws(input, *pos);
362
363 let (graph, triples) = self.parse_quad_data(input, pos)?;
364 Ok(UpdateOperation::InsertData { triples, graph })
365 } else {
366 let (ln, col) = line_col(input, *pos);
368 Err(UpdateParseError::new(
369 "expected DATA after INSERT (standalone INSERT without DELETE is not supported here; use DELETE/INSERT WHERE)",
370 *pos,
371 )
372 .with_location(ln, col))
373 }
374 }
375
376 fn parse_delete(
379 &self,
380 input: &str,
381 pos: &mut usize,
382 ) -> Result<UpdateOperation, UpdateParseError> {
383 *pos = self.consume_keyword(input, *pos, "DELETE")?;
384 *pos = self.skip_ws(input, *pos);
385
386 if self.match_keyword(input, *pos, "DATA") {
387 *pos = self.consume_keyword(input, *pos, "DATA")?;
388 *pos = self.skip_ws(input, *pos);
389
390 let (graph, triples) = self.parse_quad_data(input, pos)?;
391 Ok(UpdateOperation::DeleteData { triples, graph })
392 } else if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'{') {
393 let delete_triples = self.parse_brace_block(input, pos)?;
395 *pos = self.skip_ws(input, *pos);
396
397 let mut insert_triples = Vec::new();
398 if self.match_keyword(input, *pos, "INSERT") {
399 *pos = self.consume_keyword(input, *pos, "INSERT")?;
400 *pos = self.skip_ws(input, *pos);
401 insert_triples = self.parse_brace_block(input, pos)?;
402 *pos = self.skip_ws(input, *pos);
403 }
404
405 *pos = self.consume_keyword(input, *pos, "WHERE")?;
406 *pos = self.skip_ws(input, *pos);
407 let where_triples = self.parse_brace_block(input, pos)?;
408
409 Ok(UpdateOperation::DeleteInsertWhere {
410 delete_triples,
411 insert_triples,
412 where_triples,
413 graph: None,
414 })
415 } else {
416 let (ln, col) = line_col(input, *pos);
417 Err(
418 UpdateParseError::new("expected DATA or '{' after DELETE", *pos)
419 .with_location(ln, col),
420 )
421 }
422 }
423
424 fn parse_load(
427 &self,
428 input: &str,
429 pos: &mut usize,
430 ) -> Result<UpdateOperation, UpdateParseError> {
431 *pos = self.consume_keyword(input, *pos, "LOAD")?;
432 *pos = self.skip_ws(input, *pos);
433
434 let silent = self.try_consume_keyword(input, pos, "SILENT");
435 *pos = self.skip_ws(input, *pos);
436
437 let (source_uri, new_pos) = self.read_iri_ref(input, *pos)?;
438 *pos = new_pos;
439 *pos = self.skip_ws(input, *pos);
440
441 let target_graph = if self.match_keyword(input, *pos, "INTO") {
442 *pos = self.consume_keyword(input, *pos, "INTO")?;
443 *pos = self.skip_ws(input, *pos);
444 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
445 *pos = self.skip_ws(input, *pos);
446 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
447 *pos = new_pos;
448 Some(iri)
449 } else {
450 None
451 };
452
453 Ok(UpdateOperation::Load {
454 source_uri,
455 target_graph,
456 silent,
457 })
458 }
459
460 fn parse_clear(
463 &self,
464 input: &str,
465 pos: &mut usize,
466 ) -> Result<UpdateOperation, UpdateParseError> {
467 *pos = self.consume_keyword(input, *pos, "CLEAR")?;
468 *pos = self.skip_ws(input, *pos);
469 let silent = self.try_consume_keyword(input, pos, "SILENT");
470 *pos = self.skip_ws(input, *pos);
471 let target = self.parse_graph_ref_all(input, pos)?;
472 Ok(UpdateOperation::Clear { target, silent })
473 }
474
475 fn parse_drop(
476 &self,
477 input: &str,
478 pos: &mut usize,
479 ) -> Result<UpdateOperation, UpdateParseError> {
480 *pos = self.consume_keyword(input, *pos, "DROP")?;
481 *pos = self.skip_ws(input, *pos);
482 let silent = self.try_consume_keyword(input, pos, "SILENT");
483 *pos = self.skip_ws(input, *pos);
484 let target = self.parse_graph_ref_all(input, pos)?;
485 Ok(UpdateOperation::Drop { target, silent })
486 }
487
488 fn parse_create(
491 &self,
492 input: &str,
493 pos: &mut usize,
494 ) -> Result<UpdateOperation, UpdateParseError> {
495 *pos = self.consume_keyword(input, *pos, "CREATE")?;
496 *pos = self.skip_ws(input, *pos);
497 let silent = self.try_consume_keyword(input, pos, "SILENT");
498 *pos = self.skip_ws(input, *pos);
499 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
500 *pos = self.skip_ws(input, *pos);
501 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
502 *pos = new_pos;
503 Ok(UpdateOperation::CreateGraph {
504 graph_iri: iri,
505 silent,
506 })
507 }
508
509 fn parse_copy(
512 &self,
513 input: &str,
514 pos: &mut usize,
515 ) -> Result<UpdateOperation, UpdateParseError> {
516 *pos = self.consume_keyword(input, *pos, "COPY")?;
517 *pos = self.skip_ws(input, *pos);
518 let silent = self.try_consume_keyword(input, pos, "SILENT");
519 *pos = self.skip_ws(input, *pos);
520 let source = self.parse_graph_or_default(input, pos)?;
521 *pos = self.skip_ws(input, *pos);
522 *pos = self.consume_keyword(input, *pos, "TO")?;
523 *pos = self.skip_ws(input, *pos);
524 let destination = self.parse_graph_or_default(input, pos)?;
525 Ok(UpdateOperation::Copy {
526 source,
527 destination,
528 silent,
529 })
530 }
531
532 fn parse_move(
533 &self,
534 input: &str,
535 pos: &mut usize,
536 ) -> Result<UpdateOperation, UpdateParseError> {
537 *pos = self.consume_keyword(input, *pos, "MOVE")?;
538 *pos = self.skip_ws(input, *pos);
539 let silent = self.try_consume_keyword(input, pos, "SILENT");
540 *pos = self.skip_ws(input, *pos);
541 let source = self.parse_graph_or_default(input, pos)?;
542 *pos = self.skip_ws(input, *pos);
543 *pos = self.consume_keyword(input, *pos, "TO")?;
544 *pos = self.skip_ws(input, *pos);
545 let destination = self.parse_graph_or_default(input, pos)?;
546 Ok(UpdateOperation::Move {
547 source,
548 destination,
549 silent,
550 })
551 }
552
553 fn parse_add(&self, input: &str, pos: &mut usize) -> Result<UpdateOperation, UpdateParseError> {
554 *pos = self.consume_keyword(input, *pos, "ADD")?;
555 *pos = self.skip_ws(input, *pos);
556 let silent = self.try_consume_keyword(input, pos, "SILENT");
557 *pos = self.skip_ws(input, *pos);
558 let source = self.parse_graph_or_default(input, pos)?;
559 *pos = self.skip_ws(input, *pos);
560 *pos = self.consume_keyword(input, *pos, "TO")?;
561 *pos = self.skip_ws(input, *pos);
562 let destination = self.parse_graph_or_default(input, pos)?;
563 Ok(UpdateOperation::Add {
564 source,
565 destination,
566 silent,
567 })
568 }
569
570 fn parse_graph_ref_all(
573 &self,
574 input: &str,
575 pos: &mut usize,
576 ) -> Result<GraphTarget, UpdateParseError> {
577 *pos = self.skip_ws(input, *pos);
578 if self.match_keyword(input, *pos, "ALL") {
579 *pos = self.consume_keyword(input, *pos, "ALL")?;
580 Ok(GraphTarget::All)
581 } else if self.match_keyword(input, *pos, "DEFAULT") {
582 *pos = self.consume_keyword(input, *pos, "DEFAULT")?;
583 Ok(GraphTarget::Default)
584 } else if self.match_keyword(input, *pos, "NAMED") {
585 *pos = self.consume_keyword(input, *pos, "NAMED")?;
586 Ok(GraphTarget::Named)
587 } else if self.match_keyword(input, *pos, "GRAPH") {
588 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
589 *pos = self.skip_ws(input, *pos);
590 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
591 *pos = new_pos;
592 Ok(GraphTarget::Graph(iri))
593 } else {
594 let (ln, col) = line_col(input, *pos);
595 Err(
596 UpdateParseError::new("expected GRAPH, DEFAULT, NAMED, or ALL", *pos)
597 .with_location(ln, col),
598 )
599 }
600 }
601
602 fn parse_graph_or_default(
603 &self,
604 input: &str,
605 pos: &mut usize,
606 ) -> Result<GraphTarget, UpdateParseError> {
607 *pos = self.skip_ws(input, *pos);
608 if self.match_keyword(input, *pos, "DEFAULT") {
609 *pos = self.consume_keyword(input, *pos, "DEFAULT")?;
610 Ok(GraphTarget::Default)
611 } else if self.match_keyword(input, *pos, "GRAPH") {
612 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
613 *pos = self.skip_ws(input, *pos);
614 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
615 *pos = new_pos;
616 Ok(GraphTarget::Graph(iri))
617 } else {
618 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'<') {
620 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
621 *pos = new_pos;
622 Ok(GraphTarget::Graph(iri))
623 } else {
624 let (ln, col) = line_col(input, *pos);
625 Err(
626 UpdateParseError::new("expected DEFAULT, GRAPH <iri>, or <iri>", *pos)
627 .with_location(ln, col),
628 )
629 }
630 }
631 }
632
633 fn parse_quad_data(
642 &self,
643 input: &str,
644 pos: &mut usize,
645 ) -> Result<(Option<String>, Vec<TriplePattern>), UpdateParseError> {
646 *pos = self.skip_ws(input, *pos);
647
648 if *pos >= input.len() || input.as_bytes().get(*pos) != Some(&b'{') {
650 let (ln, col) = line_col(input, *pos);
651 return Err(
652 UpdateParseError::new("expected '{' to open quad data block", *pos)
653 .with_location(ln, col),
654 );
655 }
656 *pos += 1; *pos = self.skip_ws(input, *pos);
659
660 if self.match_keyword(input, *pos, "GRAPH") {
662 *pos = self.consume_keyword(input, *pos, "GRAPH")?;
663 *pos = self.skip_ws(input, *pos);
664 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
665 *pos = new_pos;
666 *pos = self.skip_ws(input, *pos);
667
668 let triples = self.parse_brace_block(input, pos)?;
670
671 *pos = self.skip_ws(input, *pos);
673 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
674 *pos += 1;
675 }
676
677 *pos = self.skip_ws(input, *pos);
679 if *pos >= input.len() || input.as_bytes().get(*pos) != Some(&b'}') {
680 let (ln, col) = line_col(input, *pos);
681 return Err(
682 UpdateParseError::new("expected '}' to close quad data block", *pos)
683 .with_location(ln, col),
684 );
685 }
686 *pos += 1;
687
688 Ok((Some(iri), triples))
689 } else {
690 let mut triples = Vec::new();
692 loop {
693 *pos = self.skip_ws(input, *pos);
694 if *pos >= input.len() {
695 let (ln, col) = line_col(input, *pos);
696 return Err(UpdateParseError::new(
697 "unexpected end of input, expected '}'",
698 *pos,
699 )
700 .with_location(ln, col));
701 }
702 if input.as_bytes().get(*pos) == Some(&b'}') {
703 *pos += 1;
704 break;
705 }
706
707 let triple = self.parse_triple_pattern(input, pos)?;
708 triples.push(triple);
709
710 *pos = self.skip_ws(input, *pos);
711 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
713 *pos += 1;
714 }
715 }
716
717 Ok((None, triples))
718 }
719 }
720
721 fn parse_brace_block(
723 &self,
724 input: &str,
725 pos: &mut usize,
726 ) -> Result<Vec<TriplePattern>, UpdateParseError> {
727 *pos = self.skip_ws(input, *pos);
728 if *pos >= input.len() || input.as_bytes().get(*pos) != Some(&b'{') {
729 let (ln, col) = line_col(input, *pos);
730 return Err(UpdateParseError::new("expected '{'", *pos).with_location(ln, col));
731 }
732 *pos += 1; let mut triples = Vec::new();
735 loop {
736 *pos = self.skip_ws(input, *pos);
737 if *pos >= input.len() {
738 let (ln, col) = line_col(input, *pos);
739 return Err(
740 UpdateParseError::new("unexpected end of input, expected '}'", *pos)
741 .with_location(ln, col),
742 );
743 }
744 if input.as_bytes().get(*pos) == Some(&b'}') {
745 *pos += 1;
746 break;
747 }
748
749 let triple = self.parse_triple_pattern(input, pos)?;
750 triples.push(triple);
751
752 *pos = self.skip_ws(input, *pos);
753 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
755 *pos += 1;
756 }
757 }
758
759 Ok(triples)
760 }
761
762 fn parse_triple_pattern(
764 &self,
765 input: &str,
766 pos: &mut usize,
767 ) -> Result<TriplePattern, UpdateParseError> {
768 *pos = self.skip_ws(input, *pos);
769 let subject = self.read_term(input, pos)?;
770 *pos = self.skip_ws(input, *pos);
771 let predicate = self.read_term(input, pos)?;
772 *pos = self.skip_ws(input, *pos);
773 let object = self.read_term(input, pos)?;
774 Ok(TriplePattern::new(subject, predicate, object))
775 }
776
777 fn read_term(&self, input: &str, pos: &mut usize) -> Result<String, UpdateParseError> {
781 *pos = self.skip_ws(input, *pos);
782 if *pos >= input.len() {
783 let (ln, col) = line_col(input, *pos);
784 return Err(
785 UpdateParseError::new("unexpected end of input while reading term", *pos)
786 .with_location(ln, col),
787 );
788 }
789
790 let ch = input.as_bytes()[*pos];
791
792 if ch == b'<' {
794 let (iri, new_pos) = self.read_iri_ref(input, *pos)?;
795 *pos = new_pos;
796 return Ok(format!("<{}>", iri));
797 }
798
799 if ch == b'?' || ch == b'$' {
801 let start = *pos;
802 *pos += 1; while *pos < input.len() && is_name_char(input.as_bytes()[*pos]) {
804 *pos += 1;
805 }
806 return Ok(input[start..*pos].to_string());
807 }
808
809 if ch == b'"' || ch == b'\'' {
811 return self.read_literal(input, pos);
812 }
813
814 if ch == b'_' && input.as_bytes().get(*pos + 1) == Some(&b':') {
816 let start = *pos;
817 *pos += 2;
818 while *pos < input.len() && is_name_char(input.as_bytes()[*pos]) {
819 *pos += 1;
820 }
821 return Ok(input[start..*pos].to_string());
822 }
823
824 if ch == b'a'
826 && (*pos + 1 >= input.len()
827 || !is_name_char(input.as_bytes().get(*pos + 1).copied().unwrap_or(b' ')))
828 {
829 *pos += 1;
830 return Ok("<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>".to_string());
831 }
832
833 let start = *pos;
835 while *pos < input.len() && is_name_char(input.as_bytes()[*pos]) {
836 *pos += 1;
837 }
838 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b':') {
839 let prefix = &input[start..*pos];
840 *pos += 1; let local_start = *pos;
842 while *pos < input.len() && is_pname_local_char(input.as_bytes()[*pos]) {
843 *pos += 1;
844 }
845 let local = &input[local_start..*pos];
846
847 if let Some(ns) = self.prefixes.get(prefix) {
849 return Ok(format!("<{}{}>", ns, local));
850 }
851 return Ok(format!("{}:{}", prefix, local));
852 }
853
854 *pos = start; if ch.is_ascii_digit() || ch == b'+' || ch == b'-' {
857 return self.read_numeric_literal(input, pos);
858 }
859
860 if self.match_keyword(input, *pos, "true") {
862 *pos += 4;
863 return Ok("\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>".to_string());
864 }
865 if self.match_keyword(input, *pos, "false") {
866 *pos += 5;
867 return Ok("\"false\"^^<http://www.w3.org/2001/XMLSchema#boolean>".to_string());
868 }
869
870 let (ln, col) = line_col(input, *pos);
871 let snippet: String = input[*pos..].chars().take(20).collect();
872 Err(
873 UpdateParseError::new(format!("unexpected token: '{}'", snippet), *pos)
874 .with_location(ln, col),
875 )
876 }
877
878 fn read_literal(&self, input: &str, pos: &mut usize) -> Result<String, UpdateParseError> {
880 let quote = input.as_bytes()[*pos];
881 let start = *pos;
882 *pos += 1; let mut value = String::new();
884
885 while *pos < input.len() {
886 let ch = input.as_bytes()[*pos];
887 if ch == b'\\' && *pos + 1 < input.len() {
888 let esc = input.as_bytes()[*pos + 1];
889 let escaped = match esc {
890 b'n' => '\n',
891 b't' => '\t',
892 b'\\' => '\\',
893 b'"' => '"',
894 b'\'' => '\'',
895 _ => {
896 *pos += 2;
897 continue;
898 }
899 };
900 value.push(escaped);
901 *pos += 2;
902 } else if ch == quote {
903 *pos += 1; if *pos < input.len()
906 && input.as_bytes().get(*pos) == Some(&b'^')
907 && input.as_bytes().get(*pos + 1) == Some(&b'^')
908 {
909 *pos += 2;
910 if input.as_bytes().get(*pos) == Some(&b'<') {
911 let (dt, new_pos) = self.read_iri_ref(input, *pos)?;
912 *pos = new_pos;
913 return Ok(format!("\"{}\"^^<{}>", value, dt));
914 }
915 }
916 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'@') {
917 *pos += 1;
918 let lang_start = *pos;
919 while *pos < input.len()
920 && (input.as_bytes()[*pos].is_ascii_alphanumeric()
921 || input.as_bytes()[*pos] == b'-')
922 {
923 *pos += 1;
924 }
925 let lang = &input[lang_start..*pos];
926 return Ok(format!("\"{}\"@{}", value, lang));
927 }
928 return Ok(format!("\"{}\"", value));
929 } else {
930 value.push(ch as char);
931 *pos += 1;
932 }
933 }
934
935 let (ln, col) = line_col(input, start);
936 Err(UpdateParseError::new("unterminated string literal", start).with_location(ln, col))
937 }
938
939 fn read_numeric_literal(
941 &self,
942 input: &str,
943 pos: &mut usize,
944 ) -> Result<String, UpdateParseError> {
945 let start = *pos;
946 if *pos < input.len() && (input.as_bytes()[*pos] == b'+' || input.as_bytes()[*pos] == b'-')
948 {
949 *pos += 1;
950 }
951 while *pos < input.len() && input.as_bytes()[*pos].is_ascii_digit() {
953 *pos += 1;
954 }
955 let mut is_decimal = false;
957 if *pos < input.len() && input.as_bytes().get(*pos) == Some(&b'.') {
958 is_decimal = true;
959 *pos += 1;
960 while *pos < input.len() && input.as_bytes()[*pos].is_ascii_digit() {
961 *pos += 1;
962 }
963 }
964 if *pos < input.len() && (input.as_bytes()[*pos] == b'e' || input.as_bytes()[*pos] == b'E')
966 {
967 is_decimal = true;
968 *pos += 1;
969 if *pos < input.len()
970 && (input.as_bytes()[*pos] == b'+' || input.as_bytes()[*pos] == b'-')
971 {
972 *pos += 1;
973 }
974 while *pos < input.len() && input.as_bytes()[*pos].is_ascii_digit() {
975 *pos += 1;
976 }
977 }
978
979 if *pos == start {
980 let (ln, col) = line_col(input, *pos);
981 return Err(
982 UpdateParseError::new("expected numeric literal", *pos).with_location(ln, col)
983 );
984 }
985
986 let text = &input[start..*pos];
987 if is_decimal {
988 Ok(format!(
989 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#double>",
990 text
991 ))
992 } else {
993 Ok(format!(
994 "\"{}\"^^<http://www.w3.org/2001/XMLSchema#integer>",
995 text
996 ))
997 }
998 }
999
1000 fn read_iri_ref(&self, input: &str, pos: usize) -> Result<(String, usize), UpdateParseError> {
1002 if pos >= input.len() || input.as_bytes().get(pos) != Some(&b'<') {
1003 let (ln, col) = line_col(input, pos);
1004 return Err(
1005 UpdateParseError::new("expected '<' to start IRI reference", pos)
1006 .with_location(ln, col),
1007 );
1008 }
1009 let start = pos + 1;
1010 let mut end = start;
1011 while end < input.len() && input.as_bytes()[end] != b'>' {
1012 end += 1;
1013 }
1014 if end >= input.len() {
1015 let (ln, col) = line_col(input, pos);
1016 return Err(
1017 UpdateParseError::new("unterminated IRI reference", pos).with_location(ln, col)
1018 );
1019 }
1020 let iri = input[start..end].to_string();
1021 Ok((iri, end + 1))
1022 }
1023
1024 fn read_prefix_label(
1026 &self,
1027 input: &str,
1028 pos: usize,
1029 ) -> Result<(String, usize), UpdateParseError> {
1030 let start = pos;
1031 let mut p = pos;
1032 while p < input.len() && input.as_bytes()[p] != b':' {
1033 p += 1;
1034 }
1035 if p >= input.len() {
1036 let (ln, col) = line_col(input, pos);
1037 return Err(
1038 UpdateParseError::new("expected ':' in prefix declaration", pos)
1039 .with_location(ln, col),
1040 );
1041 }
1042 let prefix = input[start..p].trim().to_string();
1043 Ok((prefix, p + 1)) }
1045
1046 fn skip_ws(&self, input: &str, mut pos: usize) -> usize {
1049 let bytes = input.as_bytes();
1050 while pos < bytes.len() {
1051 if bytes[pos].is_ascii_whitespace() {
1052 pos += 1;
1053 } else if bytes[pos] == b'#' {
1054 while pos < bytes.len() && bytes[pos] != b'\n' {
1056 pos += 1;
1057 }
1058 } else {
1059 break;
1060 }
1061 }
1062 pos
1063 }
1064
1065 fn match_keyword(&self, input: &str, pos: usize, kw: &str) -> bool {
1066 let end = pos + kw.len();
1067 if end > input.len() {
1068 return false;
1069 }
1070 if !input[pos..end].eq_ignore_ascii_case(kw) {
1071 return false;
1072 }
1073 end >= input.len() || !is_name_char(input.as_bytes()[end])
1075 }
1076
1077 fn consume_keyword(
1078 &self,
1079 input: &str,
1080 pos: usize,
1081 kw: &str,
1082 ) -> Result<usize, UpdateParseError> {
1083 if !self.match_keyword(input, pos, kw) {
1084 let (ln, col) = line_col(input, pos);
1085 let snippet: String = input[pos..].chars().take(20).collect();
1086 return Err(UpdateParseError::new(
1087 format!("expected keyword '{}', found: '{}'", kw, snippet),
1088 pos,
1089 )
1090 .with_location(ln, col));
1091 }
1092 Ok(pos + kw.len())
1093 }
1094
1095 fn try_consume_keyword(&self, input: &str, pos: &mut usize, kw: &str) -> bool {
1097 if self.match_keyword(input, *pos, kw) {
1098 *pos += kw.len();
1099 true
1100 } else {
1101 false
1102 }
1103 }
1104}
1105
1106fn is_name_char(b: u8) -> bool {
1109 b.is_ascii_alphanumeric() || b == b'_' || b == b'-'
1110}
1111
1112fn is_pname_local_char(b: u8) -> bool {
1113 b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.'
1114}
1115
1116pub fn parse_update(input: &str) -> Result<UpdateRequest, UpdateParseError> {
1122 let mut parser = UpdateParser::new();
1123 parser.parse(input)
1124}
1125
1126pub fn parse_update_with_prefixes(
1128 input: &str,
1129 prefixes: HashMap<String, String>,
1130) -> Result<UpdateRequest, UpdateParseError> {
1131 let mut parser = UpdateParser::with_prefixes(prefixes);
1132 parser.parse(input)
1133}