1use crate::ast::*;
4use crate::lexer::{Lexer, Token, TokenKind};
5use std::collections::HashMap;
6use thiserror::Error;
7
8#[derive(Debug, Error)]
9pub enum ParseError {
10 #[error("Unexpected token at line {line}: expected {expected}, found {found}")]
11 UnexpectedToken {
12 expected: String,
13 found: String,
14 line: usize,
15 column: usize,
16 },
17 #[error("Unexpected end of input")]
18 UnexpectedEof,
19 #[error("Invalid syntax at line {line}: {message}")]
20 InvalidSyntax { message: String, line: usize },
21 #[error("Lexer error at position {position}")]
22 LexerError { position: usize },
23}
24
25pub type ParseResult<T> = Result<T, ParseError>;
26
27pub struct Parser<'a> {
28 tokens: Vec<Token>,
29 pos: usize,
30 source: &'a str,
31}
32
33impl<'a> Parser<'a> {
34 pub fn new(input: &'a str) -> Self {
35 let lexer = Lexer::new(input);
36 let tokens: Vec<Token> = lexer
37 .filter_map(|r| r.ok())
38 .filter(|t| !matches!(t.kind, TokenKind::Newline))
39 .collect();
40 Self {
41 tokens,
42 pos: 0,
43 source: input,
44 }
45 }
46
47 pub fn parse_document(&mut self) -> ParseResult<UclDocument> {
48 let mut doc = UclDocument::new();
49 while !self.is_at_end() {
50 match self.peek_kind() {
51 Some(TokenKind::Structure) => {
52 self.advance();
53 doc.structure = self.parse_structure()?;
54 }
55 Some(TokenKind::Blocks) => {
56 self.advance();
57 doc.blocks = self.parse_blocks()?;
58 }
59 Some(TokenKind::Commands) => {
60 self.advance();
61 doc.commands = self.parse_commands()?;
62 }
63 Some(_) => {
64 if let Ok(cmd) = self.parse_command() {
65 doc.commands.push(cmd);
66 } else {
67 self.advance();
68 }
69 }
70 None => break,
71 }
72 }
73 Ok(doc)
74 }
75
76 pub fn parse_commands_only(&mut self) -> ParseResult<Vec<Command>> {
77 let mut cmds = Vec::new();
78 while !self.is_at_end() {
79 cmds.push(self.parse_command()?);
80 }
81 Ok(cmds)
82 }
83
84 fn parse_structure(&mut self) -> ParseResult<HashMap<String, Vec<String>>> {
85 let mut structure = HashMap::new();
86 while !self.is_at_end() && !self.is_section_header() {
87 if matches!(self.peek_kind(), Some(TokenKind::BlockId)) {
88 let parent = self.expect_block_id()?;
89 self.expect(TokenKind::Colon)?;
90 self.expect(TokenKind::LBracket)?;
91 let mut children = Vec::new();
92 while !self.check(TokenKind::RBracket) {
93 children.push(self.expect_block_id()?);
94 if !self.check(TokenKind::RBracket) {
95 let _ = self.expect(TokenKind::Comma);
96 }
97 }
98 self.expect(TokenKind::RBracket)?;
99 structure.insert(parent, children);
100 } else {
101 break;
102 }
103 }
104 Ok(structure)
105 }
106
107 fn parse_blocks(&mut self) -> ParseResult<Vec<BlockDef>> {
108 let mut blocks = Vec::new();
109 while !self.is_at_end() && !self.is_section_header() {
110 if let Some(ct) = self.try_content_type() {
111 blocks.push(self.parse_block_def(ct)?);
112 } else {
113 break;
114 }
115 }
116 Ok(blocks)
117 }
118
119 fn parse_block_def(&mut self, content_type: ContentType) -> ParseResult<BlockDef> {
120 self.expect(TokenKind::Hash)?;
121 let id = self.expect_block_id()?;
122 let mut props = HashMap::new();
123 while !self.check(TokenKind::DoubleColon) && !self.is_at_end() {
124 let k = self.expect_ident_or_keyword()?;
125 self.expect(TokenKind::Eq)?;
126 props.insert(k, self.parse_value()?);
127 }
128 self.expect(TokenKind::DoubleColon)?;
129 let content = self.parse_content_literal()?;
130 Ok(BlockDef {
131 content_type,
132 id,
133 properties: props,
134 content,
135 })
136 }
137
138 fn parse_commands(&mut self) -> ParseResult<Vec<Command>> {
139 let mut cmds = Vec::new();
140 while !self.is_at_end() && !self.is_section_header() {
141 if let Ok(cmd) = self.parse_command() {
142 cmds.push(cmd);
143 } else {
144 break;
145 }
146 }
147 Ok(cmds)
148 }
149
150 fn parse_command(&mut self) -> ParseResult<Command> {
151 match self.peek_kind() {
152 Some(TokenKind::Edit) => self.parse_edit(),
154 Some(TokenKind::Move) => self.parse_move(),
155 Some(TokenKind::Append) => self.parse_append(),
156 Some(TokenKind::Delete) => self.parse_delete(),
157 Some(TokenKind::Prune) => self.parse_prune(),
158 Some(TokenKind::Link) => self.parse_link(),
159 Some(TokenKind::Unlink) => self.parse_unlink(),
160 Some(TokenKind::Fold) => self.parse_fold(),
161 Some(TokenKind::Snapshot) => self.parse_snapshot(),
162 Some(TokenKind::Begin) => self.parse_begin(),
163 Some(TokenKind::Commit) => self.parse_commit(),
164 Some(TokenKind::Rollback) => self.parse_rollback(),
165 Some(TokenKind::Atomic) => self.parse_atomic(),
166 Some(TokenKind::WriteSection) => self.parse_write_section(),
167
168 Some(TokenKind::Goto) => self.parse_goto(),
170 Some(TokenKind::Back) => self.parse_back(),
171 Some(TokenKind::Expand) => self.parse_expand(),
172 Some(TokenKind::Follow) => self.parse_follow(),
173 Some(TokenKind::Path) => self.parse_path_find(),
174 Some(TokenKind::Search) => self.parse_search(),
175 Some(TokenKind::Find) => self.parse_find(),
176 Some(TokenKind::View) => self.parse_view(),
177
178 Some(TokenKind::Ctx) => self.parse_ctx(),
180
181 _ => Err(self.error("command")),
182 }
183 }
184
185 fn parse_edit(&mut self) -> ParseResult<Command> {
186 self.advance();
187 let id = self.expect_block_id()?;
188 self.expect(TokenKind::Set)?;
189 let path = self.parse_path()?;
190 let op = self.parse_op()?;
191 let val = self.parse_value()?;
192 let cond = if self.check(TokenKind::Where) {
193 self.advance();
194 Some(self.parse_cond()?)
195 } else {
196 None
197 };
198 Ok(Command::Edit(EditCommand {
199 block_id: id,
200 path,
201 operator: op,
202 value: val,
203 condition: cond,
204 }))
205 }
206
207 fn parse_move(&mut self) -> ParseResult<Command> {
208 self.advance();
209 let id = self.expect_block_id()?;
210 let target = if self.check(TokenKind::To) {
211 self.advance();
212 let pid = self.expect_block_id()?;
213 let idx = if self.check(TokenKind::At) {
214 self.advance();
215 Some(self.expect_int()? as usize)
216 } else {
217 None
218 };
219 MoveTarget::ToParent {
220 parent_id: pid,
221 index: idx,
222 }
223 } else if self.check(TokenKind::Before) {
224 self.advance();
225 MoveTarget::Before {
226 sibling_id: self.expect_block_id()?,
227 }
228 } else if self.check(TokenKind::After) {
229 self.advance();
230 MoveTarget::After {
231 sibling_id: self.expect_block_id()?,
232 }
233 } else {
234 return Err(self.error("TO/BEFORE/AFTER"));
235 };
236 Ok(Command::Move(MoveCommand {
237 block_id: id,
238 target,
239 }))
240 }
241
242 fn parse_append(&mut self) -> ParseResult<Command> {
243 self.advance();
244 let pid = self.expect_block_id()?;
245 let ct = self.parse_content_type()?;
246 let mut props = HashMap::new();
247 let mut idx = None;
248 if self.check(TokenKind::At) {
249 self.advance();
250 idx = Some(self.expect_int()? as usize);
251 }
252 if self.check(TokenKind::With) {
253 self.advance();
254 while !self.check(TokenKind::DoubleColon) && !self.is_at_end() {
255 let k = self.expect_ident()?;
256 self.expect(TokenKind::Eq)?;
257 props.insert(k, self.parse_value()?);
258 }
259 }
260 self.expect(TokenKind::DoubleColon)?;
261 let content = self.parse_content_literal()?;
262 Ok(Command::Append(AppendCommand {
263 parent_id: pid,
264 content_type: ct,
265 properties: props,
266 content,
267 index: idx,
268 }))
269 }
270
271 fn parse_delete(&mut self) -> ParseResult<Command> {
272 self.advance();
273 let (bid, cond) = if self.check(TokenKind::Where) {
274 self.advance();
275 (None, Some(self.parse_cond()?))
276 } else {
277 (Some(self.expect_block_id()?), None)
278 };
279 let casc = if self.check(TokenKind::Cascade) {
280 {
281 self.advance();
282 true
283 }
284 } else {
285 false
286 };
287 let pres = if self.check(TokenKind::PreserveChildren) {
288 {
289 self.advance();
290 true
291 }
292 } else {
293 false
294 };
295 Ok(Command::Delete(DeleteCommand {
296 block_id: bid,
297 cascade: casc,
298 preserve_children: pres,
299 condition: cond,
300 }))
301 }
302
303 fn parse_prune(&mut self) -> ParseResult<Command> {
304 self.advance();
305 let tgt = if self.check(TokenKind::Unreachable) {
306 self.advance();
307 PruneTarget::Unreachable
308 } else if self.check(TokenKind::Where) {
309 self.advance();
310 PruneTarget::Where(self.parse_cond()?)
311 } else {
312 PruneTarget::Unreachable
313 };
314 let dry = if self.check(TokenKind::DryRun) {
315 {
316 self.advance();
317 true
318 }
319 } else {
320 false
321 };
322 Ok(Command::Prune(PruneCommand {
323 target: tgt,
324 dry_run: dry,
325 }))
326 }
327
328 fn parse_fold(&mut self) -> ParseResult<Command> {
329 self.advance();
330 let id = self.expect_block_id()?;
331 let (mut d, mut t, mut tags) = (None, None, Vec::new());
332 while !self.is_at_end() && !self.is_cmd_start() {
333 if self.check(TokenKind::Depth) {
334 self.advance();
335 d = Some(self.expect_int()? as usize);
336 } else if self.check(TokenKind::MaxTokens) {
337 self.advance();
338 t = Some(self.expect_int()? as usize);
339 } else if self.check(TokenKind::PreserveTags) {
340 self.advance();
341 self.expect(TokenKind::LBracket)?;
342 while !self.check(TokenKind::RBracket) {
343 tags.push(self.expect_str()?);
344 if !self.check(TokenKind::RBracket) {
345 let _ = self.expect(TokenKind::Comma);
346 }
347 }
348 self.expect(TokenKind::RBracket)?;
349 } else {
350 break;
351 }
352 }
353 Ok(Command::Fold(FoldCommand {
354 block_id: id,
355 depth: d,
356 max_tokens: t,
357 preserve_tags: tags,
358 }))
359 }
360
361 fn parse_link(&mut self) -> ParseResult<Command> {
362 self.advance();
363 let s = self.expect_block_id()?;
364 let e = self.expect_ident()?;
365 let t = self.expect_block_id()?;
366 let mut m = HashMap::new();
367 if self.check(TokenKind::With) {
368 self.advance();
369 while !self.is_at_end() && !self.is_cmd_start() {
370 let k = self.expect_ident()?;
371 self.expect(TokenKind::Eq)?;
372 m.insert(k, self.parse_value()?);
373 }
374 }
375 Ok(Command::Link(LinkCommand {
376 source_id: s,
377 edge_type: e,
378 target_id: t,
379 metadata: m,
380 }))
381 }
382
383 fn parse_unlink(&mut self) -> ParseResult<Command> {
384 self.advance();
385 Ok(Command::Unlink(UnlinkCommand {
386 source_id: self.expect_block_id()?,
387 edge_type: self.expect_ident()?,
388 target_id: self.expect_block_id()?,
389 }))
390 }
391
392 fn parse_write_section(&mut self) -> ParseResult<Command> {
393 self.advance(); let section_id = self.expect_block_id()?;
396
397 self.expect(TokenKind::DoubleColon)?;
399
400 let markdown = self.expect_str()?;
402
403 let base_heading_level = if self.check(TokenKind::BaseLevel) {
405 self.advance();
406 Some(self.expect_int()? as usize)
407 } else {
408 None
409 };
410
411 Ok(Command::WriteSection(WriteSectionCommand {
412 section_id,
413 markdown,
414 base_heading_level,
415 }))
416 }
417
418 fn parse_snapshot(&mut self) -> ParseResult<Command> {
419 self.advance();
420 let cmd = if self.check(TokenKind::Create) {
421 self.advance();
422 let n = self.expect_str()?;
423 let d = if self.check(TokenKind::With) {
424 self.advance();
425 self.expect_ident()?;
426 self.expect(TokenKind::Eq)?;
427 Some(self.expect_str()?)
428 } else {
429 None
430 };
431 SnapshotCommand::Create {
432 name: n,
433 description: d,
434 }
435 } else if self.check(TokenKind::Restore) {
436 self.advance();
437 SnapshotCommand::Restore {
438 name: self.expect_str()?,
439 }
440 } else if self.check(TokenKind::List) {
441 self.advance();
442 SnapshotCommand::List
443 } else if self.check(TokenKind::Delete) {
444 self.advance();
445 SnapshotCommand::Delete {
446 name: self.expect_str()?,
447 }
448 } else if self.check(TokenKind::Diff) {
449 self.advance();
450 SnapshotCommand::Diff {
451 name1: self.expect_str()?,
452 name2: self.expect_str()?,
453 }
454 } else {
455 return Err(self.error("snapshot action"));
456 };
457 Ok(Command::Snapshot(cmd))
458 }
459
460 fn parse_begin(&mut self) -> ParseResult<Command> {
461 self.advance();
462 self.expect(TokenKind::Transaction)?;
463 let n = self.try_str();
464 Ok(Command::Transaction(TransactionCommand::Begin { name: n }))
465 }
466 fn parse_commit(&mut self) -> ParseResult<Command> {
467 self.advance();
468 Ok(Command::Transaction(TransactionCommand::Commit {
469 name: self.try_str(),
470 }))
471 }
472 fn parse_rollback(&mut self) -> ParseResult<Command> {
473 self.advance();
474 Ok(Command::Transaction(TransactionCommand::Rollback {
475 name: self.try_str(),
476 }))
477 }
478
479 fn parse_atomic(&mut self) -> ParseResult<Command> {
480 self.advance();
481 self.expect(TokenKind::LBrace)?;
482 let mut cmds = Vec::new();
483 while !self.check(TokenKind::RBrace) && !self.is_at_end() {
484 cmds.push(self.parse_command()?);
485 }
486 self.expect(TokenKind::RBrace)?;
487 Ok(Command::Atomic(cmds))
488 }
489
490 fn parse_goto(&mut self) -> ParseResult<Command> {
496 self.advance(); let block_id = self.expect_block_id()?;
498 Ok(Command::Goto(GotoCommand { block_id }))
499 }
500
501 fn parse_back(&mut self) -> ParseResult<Command> {
503 self.advance(); let steps = if let Some(TokenKind::Integer(n)) = self.peek_kind() {
505 self.advance();
506 n as usize
507 } else {
508 1
509 };
510 Ok(Command::Back(BackCommand { steps }))
511 }
512
513 fn parse_expand(&mut self) -> ParseResult<Command> {
515 self.advance(); if matches!(
517 self.peek_kind(),
518 Some(TokenKind::Down | TokenKind::Up | TokenKind::Both | TokenKind::Semantic)
519 ) {
520 return Err(self.error_with_hint(
521 "EXPAND syntax: provide the block ID before the direction (e.g., EXPAND blk_root DOWN)",
522 ));
523 }
524 let block_id = self.expect_block_id()?;
525
526 let direction = match self.peek_kind() {
528 Some(TokenKind::Down) => {
529 self.advance();
530 ExpandDirection::Down
531 }
532 Some(TokenKind::Up) => {
533 self.advance();
534 ExpandDirection::Up
535 }
536 Some(TokenKind::Both) => {
537 self.advance();
538 ExpandDirection::Both
539 }
540 Some(TokenKind::Semantic) => {
541 self.advance();
542 ExpandDirection::Semantic
543 }
544 _ => ExpandDirection::Down, };
546
547 let mut depth = 1usize;
549 let mut mode = None;
550 let mut filter = TraversalFilterCriteria::default();
551
552 while !self.is_at_end() && !self.is_cmd_start() {
553 match self.peek_kind() {
554 Some(TokenKind::Depth) => {
555 self.advance();
556 self.expect(TokenKind::Eq)?;
557 depth = self.expect_int()? as usize;
558 }
559 Some(TokenKind::Mode) => {
560 self.advance();
561 self.expect(TokenKind::Eq)?;
562 mode = Some(self.parse_view_mode()?);
563 }
564 Some(TokenKind::Roles) => {
565 self.advance();
566 self.expect(TokenKind::Eq)?;
567 filter.include_roles = self.parse_comma_list()?;
568 }
569 Some(TokenKind::Tags) => {
570 self.advance();
571 self.expect(TokenKind::Eq)?;
572 filter.include_tags = self.parse_comma_list()?;
573 }
574 _ => break,
575 }
576 }
577
578 Ok(Command::Expand(ExpandCommand {
579 block_id,
580 direction,
581 depth,
582 mode,
583 filter: if filter.include_roles.is_empty()
584 && filter.include_tags.is_empty()
585 && filter.exclude_roles.is_empty()
586 && filter.exclude_tags.is_empty()
587 {
588 None
589 } else {
590 Some(filter)
591 },
592 }))
593 }
594
595 fn parse_follow(&mut self) -> ParseResult<Command> {
597 self.advance(); let source_id = self.expect_block_id()?;
599
600 let edge_types = self.parse_comma_list()?;
602
603 let target_id = if matches!(self.peek_kind(), Some(TokenKind::BlockId)) {
605 Some(self.expect_block_id()?)
606 } else {
607 None
608 };
609
610 Ok(Command::Follow(FollowCommand {
611 source_id,
612 edge_types,
613 target_id,
614 }))
615 }
616
617 fn parse_path_find(&mut self) -> ParseResult<Command> {
619 self.advance(); let from_id = self.expect_block_id()?;
621 if self.check(TokenKind::To) {
622 self.advance();
623 } else if matches!(self.peek_kind(), Some(TokenKind::BlockId)) {
624 return Err(self.error_with_hint(
625 "PATH syntax: include the TO keyword between the two block IDs (e.g., PATH blk_a TO blk_b)",
626 ));
627 } else {
628 self.expect(TokenKind::To)?;
629 }
630 let to_id = self.expect_block_id()?;
631
632 let max_length = if self.check(TokenKind::Max) {
633 self.advance();
634 self.expect(TokenKind::Eq)?;
635 Some(self.expect_int()? as usize)
636 } else {
637 None
638 };
639
640 Ok(Command::Path(PathFindCommand {
641 from_id,
642 to_id,
643 max_length,
644 }))
645 }
646
647 fn parse_search(&mut self) -> ParseResult<Command> {
649 self.advance(); let query = self.expect_str()?;
651
652 let mut limit = None;
653 let mut min_similarity = None;
654 let mut filter = TraversalFilterCriteria::default();
655
656 while !self.is_at_end() && !self.is_cmd_start() {
657 match self.peek_kind() {
658 Some(TokenKind::Limit) => {
659 self.advance();
660 self.expect(TokenKind::Eq)?;
661 limit = Some(self.expect_int()? as usize);
662 }
663 Some(TokenKind::MinSimilarity) => {
664 self.advance();
665 self.expect(TokenKind::Eq)?;
666 min_similarity = Some(self.expect_float()?);
667 }
668 Some(TokenKind::Roles) => {
669 self.advance();
670 self.expect(TokenKind::Eq)?;
671 filter.include_roles = self.parse_comma_list()?;
672 }
673 Some(TokenKind::Tags) => {
674 self.advance();
675 self.expect(TokenKind::Eq)?;
676 filter.include_tags = self.parse_comma_list()?;
677 }
678 _ => break,
679 }
680 }
681
682 Ok(Command::Search(SearchCommand {
683 query,
684 limit,
685 min_similarity,
686 filter: if filter.include_roles.is_empty() && filter.include_tags.is_empty() {
687 None
688 } else {
689 Some(filter)
690 },
691 }))
692 }
693
694 fn parse_find(&mut self) -> ParseResult<Command> {
696 self.advance(); let mut cmd = FindCommand::default();
698
699 while !self.is_at_end() && !self.is_cmd_start() {
700 match self.peek_kind() {
701 Some(TokenKind::Role) => {
702 self.advance();
703 self.expect_eq_with_hint("FIND ROLE must use '=' (e.g., ROLE=heading1)")?;
704 cmd.role = Some(self.expect_ident_or_str()?);
705 }
706 Some(TokenKind::Tag) => {
707 self.advance();
708 self.expect_eq_with_hint("FIND TAG must use '=' (e.g., TAG=\"important\")")?;
709 cmd.tag = Some(self.expect_str()?);
710 }
711 Some(TokenKind::Label) => {
712 self.advance();
713 self.expect_eq_with_hint("FIND LABEL must use '=' (e.g., LABEL=\"summary\")")?;
714 cmd.label = Some(self.expect_str()?);
715 }
716 Some(TokenKind::Pattern) => {
717 self.advance();
718 self.expect_eq_with_hint("FIND PATTERN must use '=' (e.g., PATTERN=\".*\")")?;
719 cmd.pattern = Some(self.expect_str()?);
720 }
721 _ => break,
722 }
723 }
724
725 Ok(Command::Find(cmd))
726 }
727
728 fn parse_view(&mut self) -> ParseResult<Command> {
730 self.advance(); let target = if self.check(TokenKind::Neighborhood) {
733 self.advance();
734 ViewTarget::Neighborhood
735 } else if matches!(self.peek_kind(), Some(TokenKind::BlockId)) {
736 ViewTarget::Block(self.expect_block_id()?)
737 } else {
738 ViewTarget::Neighborhood
739 };
740
741 let mut mode = ViewMode::Full;
742 let mut depth = None;
743
744 while !self.is_at_end() && !self.is_cmd_start() {
745 match self.peek_kind() {
746 Some(TokenKind::Mode) => {
747 self.advance();
748 self.expect(TokenKind::Eq)?;
749 mode = self.parse_view_mode()?;
750 }
751 Some(TokenKind::Depth) => {
752 self.advance();
753 self.expect(TokenKind::Eq)?;
754 depth = Some(self.expect_int()? as usize);
755 }
756 _ => break,
757 }
758 }
759
760 Ok(Command::View(ViewCommand {
761 target,
762 mode,
763 depth,
764 }))
765 }
766
767 fn parse_ctx(&mut self) -> ParseResult<Command> {
773 self.advance(); match self.peek_kind() {
776 Some(TokenKind::Add) => self.parse_ctx_add(),
777 Some(TokenKind::Remove) => self.parse_ctx_remove(),
778 Some(TokenKind::Clear) => {
779 self.advance();
780 Ok(Command::Context(ContextCommand::Clear))
781 }
782 Some(TokenKind::Expand) => self.parse_ctx_expand(),
783 Some(TokenKind::Compress) => self.parse_ctx_compress(),
784 Some(TokenKind::Prune) => self.parse_ctx_prune(),
785 Some(TokenKind::Render) => self.parse_ctx_render(),
786 Some(TokenKind::Stats) => {
787 self.advance();
788 Ok(Command::Context(ContextCommand::Stats))
789 }
790 Some(TokenKind::Focus) => self.parse_ctx_focus(),
791 _ => Err(self.error(
792 "CTX subcommand (ADD/REMOVE/CLEAR/EXPAND/COMPRESS/PRUNE/RENDER/STATS/FOCUS)",
793 )),
794 }
795 }
796
797 fn parse_ctx_add(&mut self) -> ParseResult<Command> {
799 self.advance(); let target = if self.check(TokenKind::Results) {
802 self.advance();
803 ContextAddTarget::Results
804 } else if self.check(TokenKind::Children) {
805 self.advance();
806 let parent_id = self.expect_block_id()?;
807 ContextAddTarget::Children { parent_id }
808 } else if self.check(TokenKind::Path) {
809 self.advance();
810 let from_id = self.expect_block_id()?;
811 if self.check(TokenKind::To) {
812 self.advance();
813 } else if matches!(self.peek_kind(), Some(TokenKind::BlockId)) {
814 return Err(self.error_with_hint(
815 "CTX ADD PATH syntax: include the TO keyword between the two block IDs",
816 ));
817 } else {
818 self.expect(TokenKind::To)?;
819 }
820 let to_id = self.expect_block_id()?;
821 ContextAddTarget::Path { from_id, to_id }
822 } else {
823 let block_id = self.expect_block_id()?;
824 ContextAddTarget::Block(block_id)
825 };
826
827 let mut reason = None;
828 let mut relevance = None;
829
830 while !self.is_at_end() && !self.is_cmd_start() {
831 match self.peek_kind() {
832 Some(TokenKind::Reason) => {
833 self.advance();
834 self.expect_eq_with_hint(
835 "CTX ADD REASON must use '=' (e.g., REASON=\"for_llm\")",
836 )?;
837 reason = Some(self.expect_str()?);
838 }
839 Some(TokenKind::Relevance) => {
840 self.advance();
841 self.expect_eq_with_hint(
842 "CTX ADD RELEVANCE must use '=' (e.g., RELEVANCE=0.8)",
843 )?;
844 relevance = Some(self.expect_float()?);
845 }
846 _ => break,
847 }
848 }
849
850 Ok(Command::Context(ContextCommand::Add(ContextAddCommand {
851 target,
852 reason,
853 relevance,
854 })))
855 }
856
857 fn parse_ctx_remove(&mut self) -> ParseResult<Command> {
859 self.advance(); let block_id = self.expect_block_id()?;
861 Ok(Command::Context(ContextCommand::Remove { block_id }))
862 }
863
864 fn parse_ctx_expand(&mut self) -> ParseResult<Command> {
866 self.advance(); let direction = match self.peek_kind() {
869 Some(TokenKind::Down) => {
870 self.advance();
871 ExpandDirection::Down
872 }
873 Some(TokenKind::Up) => {
874 self.advance();
875 ExpandDirection::Up
876 }
877 Some(TokenKind::Semantic) => {
878 self.advance();
879 ExpandDirection::Semantic
880 }
881 Some(TokenKind::Both) => {
882 self.advance();
883 ExpandDirection::Both
884 }
885 _ => ExpandDirection::Down,
886 };
887
888 let mut depth = None;
889 let mut token_budget = None;
890
891 while !self.is_at_end() && !self.is_cmd_start() {
892 match self.peek_kind() {
893 Some(TokenKind::Depth) => {
894 self.advance();
895 self.expect(TokenKind::Eq)?;
896 depth = Some(self.expect_int()? as usize);
897 }
898 Some(TokenKind::Tokens) => {
899 self.advance();
900 self.expect(TokenKind::Eq)?;
901 token_budget = Some(self.expect_int()? as usize);
902 }
903 _ => break,
904 }
905 }
906
907 Ok(Command::Context(ContextCommand::Expand(
908 ContextExpandCommand {
909 direction,
910 depth,
911 token_budget,
912 },
913 )))
914 }
915
916 fn parse_ctx_compress(&mut self) -> ParseResult<Command> {
918 self.advance(); let method = if self.check(TokenKind::Method) {
921 self.advance();
922 self.expect(TokenKind::Eq)?;
923 self.parse_compression_method()?
924 } else {
925 CompressionMethod::Truncate
926 };
927
928 Ok(Command::Context(ContextCommand::Compress { method }))
929 }
930
931 fn parse_ctx_prune(&mut self) -> ParseResult<Command> {
933 self.advance(); let mut min_relevance = None;
936 let mut max_age_secs = None;
937
938 while !self.is_at_end() && !self.is_cmd_start() {
939 match self.peek_kind() {
940 Some(TokenKind::MinSimilarity) | Some(TokenKind::Relevance) => {
941 self.advance();
942 self.expect(TokenKind::Eq)?;
943 min_relevance = Some(self.expect_float()?);
944 }
945 Some(TokenKind::MaxAge) => {
946 self.advance();
947 self.expect(TokenKind::Eq)?;
948 max_age_secs = Some(self.expect_int()? as u64);
949 }
950 _ => break,
951 }
952 }
953
954 Ok(Command::Context(ContextCommand::Prune(
955 ContextPruneCommand {
956 min_relevance,
957 max_age_secs,
958 },
959 )))
960 }
961
962 fn parse_ctx_render(&mut self) -> ParseResult<Command> {
964 self.advance(); let format = if self.check(TokenKind::Format) {
967 self.advance();
968 self.expect(TokenKind::Eq)?;
969 Some(self.parse_render_format()?)
970 } else {
971 None
972 };
973
974 Ok(Command::Context(ContextCommand::Render { format }))
975 }
976
977 fn parse_ctx_focus(&mut self) -> ParseResult<Command> {
979 self.advance(); let block_id = if self.check(TokenKind::Clear) {
982 self.advance();
983 None
984 } else if matches!(self.peek_kind(), Some(TokenKind::BlockId)) {
985 Some(self.expect_block_id()?)
986 } else {
987 None
988 };
989
990 Ok(Command::Context(ContextCommand::Focus { block_id }))
991 }
992
993 fn parse_view_mode(&mut self) -> ParseResult<ViewMode> {
998 match self.peek_kind() {
999 Some(TokenKind::Full) => {
1000 self.advance();
1001 Ok(ViewMode::Full)
1002 }
1003 Some(TokenKind::Preview) => {
1004 self.advance();
1005 Ok(ViewMode::Preview { length: 100 })
1006 }
1007 Some(TokenKind::MetadataToken) => {
1008 self.advance();
1009 Ok(ViewMode::Metadata)
1010 }
1011 Some(TokenKind::Ids) => {
1012 self.advance();
1013 Ok(ViewMode::IdsOnly)
1014 }
1015 Some(TokenKind::Identifier) => {
1016 let span = self.tokens[self.pos].span.clone();
1017 let s = self.source[span].to_string();
1018 self.advance();
1019 ViewMode::parse(&s).ok_or_else(|| self.error("view mode"))
1020 }
1021 _ => Err(self.error("view mode (FULL/PREVIEW/METADATA/IDS)")),
1022 }
1023 }
1024
1025 fn parse_compression_method(&mut self) -> ParseResult<CompressionMethod> {
1026 match self.peek_kind() {
1027 Some(TokenKind::Truncate) => {
1028 self.advance();
1029 Ok(CompressionMethod::Truncate)
1030 }
1031 Some(TokenKind::Summarize) => {
1032 self.advance();
1033 Ok(CompressionMethod::Summarize)
1034 }
1035 Some(TokenKind::StructureOnly) => {
1036 self.advance();
1037 Ok(CompressionMethod::StructureOnly)
1038 }
1039 Some(TokenKind::Identifier) => {
1040 let span = self.tokens[self.pos].span.clone();
1041 let s = self.source[span].to_string();
1042 self.advance();
1043 CompressionMethod::parse(&s).ok_or_else(|| self.error("compression method"))
1044 }
1045 _ => Err(self.error("compression method (TRUNCATE/SUMMARIZE/STRUCTURE_ONLY)")),
1046 }
1047 }
1048
1049 fn parse_render_format(&mut self) -> ParseResult<RenderFormat> {
1050 match self.peek_kind() {
1051 Some(TokenKind::ShortIds) => {
1052 self.advance();
1053 Ok(RenderFormat::ShortIds)
1054 }
1055 Some(TokenKind::Markdown) => {
1056 self.advance();
1057 Ok(RenderFormat::Markdown)
1058 }
1059 Some(TokenKind::Identifier) => {
1060 let span = self.tokens[self.pos].span.clone();
1061 let s = self.source[span].to_string();
1062 self.advance();
1063 RenderFormat::parse(&s).ok_or_else(|| self.error("render format"))
1064 }
1065 _ => Ok(RenderFormat::Default),
1066 }
1067 }
1068
1069 fn parse_comma_list(&mut self) -> ParseResult<Vec<String>> {
1071 let mut items = Vec::new();
1072 items.push(self.expect_ident_or_str()?);
1073
1074 while self.check(TokenKind::Comma) {
1075 self.advance();
1076 items.push(self.expect_ident_or_str()?);
1077 }
1078
1079 Ok(items)
1080 }
1081
1082 fn expect_ident_or_str(&mut self) -> ParseResult<String> {
1083 match self.peek_kind() {
1084 Some(TokenKind::DoubleString(s)) | Some(TokenKind::SingleString(s)) => {
1085 self.advance();
1086 Ok(s)
1087 }
1088 Some(TokenKind::Identifier) => {
1089 let span = self.tokens[self.pos].span.clone();
1090 self.advance();
1091 Ok(self.source[span].to_string())
1092 }
1093 _ => self.expect_ident_or_keyword(),
1094 }
1095 }
1096
1097 fn expect_float(&mut self) -> ParseResult<f32> {
1098 match self.peek_kind() {
1099 Some(TokenKind::Float(n)) => {
1100 self.advance();
1101 Ok(n as f32)
1102 }
1103 Some(TokenKind::Integer(n)) => {
1104 self.advance();
1105 Ok(n as f32)
1106 }
1107 _ => Err(self.error("float")),
1108 }
1109 }
1110
1111 fn parse_path(&mut self) -> ParseResult<Path> {
1112 let mut segs = Vec::new();
1113 if self.check(TokenKind::Dollar) {
1114 self.advance();
1115 segs.push(PathSegment::JsonPath(self.expect_ident_or_keyword()?));
1116 return Ok(Path::new(segs));
1117 }
1118 loop {
1119 if self.is_path_property_start() {
1120 segs.push(PathSegment::Property(self.expect_path_property()?));
1121 } else {
1122 break;
1123 }
1124 if self.check(TokenKind::LBracket) {
1125 self.advance();
1126 let s = if matches!(self.peek_kind(), Some(TokenKind::Integer(_))) {
1127 let n = self.expect_int()?;
1128 Some(n)
1129 } else {
1130 None
1131 };
1132 if self.check(TokenKind::Colon) {
1133 self.advance();
1134 let e = if matches!(self.peek_kind(), Some(TokenKind::Integer(_))) {
1135 Some(self.expect_int()?)
1136 } else {
1137 None
1138 };
1139 segs.push(PathSegment::Slice { start: s, end: e });
1140 } else if let Some(i) = s {
1141 segs.push(PathSegment::Index(i));
1142 }
1143 self.expect(TokenKind::RBracket)?;
1144 }
1145 if self.check(TokenKind::Dot) {
1146 self.advance();
1147 } else {
1148 break;
1149 }
1150 }
1151 Ok(Path::new(segs))
1152 }
1153
1154 fn parse_op(&mut self) -> ParseResult<Operator> {
1155 match self.peek_kind() {
1156 Some(TokenKind::Eq) => {
1157 self.advance();
1158 Ok(Operator::Set)
1159 }
1160 Some(TokenKind::PlusEq) => {
1161 self.advance();
1162 Ok(Operator::Append)
1163 }
1164 Some(TokenKind::MinusEq) => {
1165 self.advance();
1166 Ok(Operator::Remove)
1167 }
1168 Some(TokenKind::PlusPlus) => {
1169 self.advance();
1170 Ok(Operator::Increment)
1171 }
1172 Some(TokenKind::MinusMinus) => {
1173 self.advance();
1174 Ok(Operator::Decrement)
1175 }
1176 _ => Err(self.error("operator")),
1177 }
1178 }
1179
1180 fn parse_value(&mut self) -> ParseResult<Value> {
1181 match self.peek_kind() {
1182 Some(TokenKind::Null) => {
1183 self.advance();
1184 Ok(Value::Null)
1185 }
1186 Some(TokenKind::True) => {
1187 self.advance();
1188 Ok(Value::Bool(true))
1189 }
1190 Some(TokenKind::False) => {
1191 self.advance();
1192 Ok(Value::Bool(false))
1193 }
1194 Some(TokenKind::Integer(n)) => {
1195 self.advance();
1196 Ok(Value::Number(n as f64))
1197 }
1198 Some(TokenKind::Float(n)) => {
1199 self.advance();
1200 Ok(Value::Number(n))
1201 }
1202 Some(TokenKind::DoubleString(s))
1203 | Some(TokenKind::SingleString(s))
1204 | Some(TokenKind::TripleString(s)) => {
1205 self.advance();
1206 Ok(Value::String(s))
1207 }
1208 Some(TokenKind::At_) => {
1209 self.advance();
1210 Ok(Value::BlockRef(self.expect_block_id()?))
1211 }
1212 Some(TokenKind::LBracket) => self.parse_array(),
1213 Some(TokenKind::LBrace) => self.parse_object(),
1214 _ => Err(self.error("value")),
1215 }
1216 }
1217
1218 fn parse_array(&mut self) -> ParseResult<Value> {
1219 self.expect(TokenKind::LBracket)?;
1220 let mut arr = Vec::new();
1221 while !self.check(TokenKind::RBracket) && !self.is_at_end() {
1222 arr.push(self.parse_value()?);
1223 if !self.check(TokenKind::RBracket) {
1224 let _ = self.expect(TokenKind::Comma);
1225 }
1226 }
1227 self.expect(TokenKind::RBracket)?;
1228 Ok(Value::Array(arr))
1229 }
1230
1231 fn parse_object(&mut self) -> ParseResult<Value> {
1232 self.expect(TokenKind::LBrace)?;
1233 let mut m = HashMap::new();
1234 while !self.check(TokenKind::RBrace) && !self.is_at_end() {
1235 let k = self.expect_str()?;
1236 self.expect(TokenKind::Colon)?;
1237 m.insert(k, self.parse_value()?);
1238 if !self.check(TokenKind::RBrace) {
1239 let _ = self.expect(TokenKind::Comma);
1240 }
1241 }
1242 self.expect(TokenKind::RBrace)?;
1243 Ok(Value::Object(m))
1244 }
1245
1246 fn parse_cond(&mut self) -> ParseResult<Condition> {
1247 self.parse_or()
1248 }
1249 fn parse_or(&mut self) -> ParseResult<Condition> {
1250 let mut l = self.parse_and()?;
1251 while self.check(TokenKind::Or) {
1252 self.advance();
1253 l = Condition::Or(Box::new(l), Box::new(self.parse_and()?));
1254 }
1255 Ok(l)
1256 }
1257 fn parse_and(&mut self) -> ParseResult<Condition> {
1258 let mut l = self.parse_unary()?;
1259 while self.check(TokenKind::And) {
1260 self.advance();
1261 l = Condition::And(Box::new(l), Box::new(self.parse_unary()?));
1262 }
1263 Ok(l)
1264 }
1265 fn parse_unary(&mut self) -> ParseResult<Condition> {
1266 if self.check(TokenKind::Not) {
1267 self.advance();
1268 return Ok(Condition::Not(Box::new(self.parse_unary()?)));
1269 }
1270 if self.check(TokenKind::LParen) {
1271 self.advance();
1272 let c = self.parse_cond()?;
1273 self.expect(TokenKind::RParen)?;
1274 return Ok(c);
1275 }
1276 self.parse_comp()
1277 }
1278 fn parse_comp(&mut self) -> ParseResult<Condition> {
1279 let p = self.parse_path()?;
1280 match self.peek_kind() {
1281 Some(TokenKind::Eq) => {
1282 self.advance();
1283 Ok(Condition::Comparison {
1284 path: p,
1285 op: ComparisonOp::Eq,
1286 value: self.parse_value()?,
1287 })
1288 }
1289 Some(TokenKind::Ne) => {
1290 self.advance();
1291 Ok(Condition::Comparison {
1292 path: p,
1293 op: ComparisonOp::Ne,
1294 value: self.parse_value()?,
1295 })
1296 }
1297 Some(TokenKind::Gt) => {
1298 self.advance();
1299 Ok(Condition::Comparison {
1300 path: p,
1301 op: ComparisonOp::Gt,
1302 value: self.parse_value()?,
1303 })
1304 }
1305 Some(TokenKind::Ge) => {
1306 self.advance();
1307 Ok(Condition::Comparison {
1308 path: p,
1309 op: ComparisonOp::Ge,
1310 value: self.parse_value()?,
1311 })
1312 }
1313 Some(TokenKind::Lt) => {
1314 self.advance();
1315 Ok(Condition::Comparison {
1316 path: p,
1317 op: ComparisonOp::Lt,
1318 value: self.parse_value()?,
1319 })
1320 }
1321 Some(TokenKind::Le) => {
1322 self.advance();
1323 Ok(Condition::Comparison {
1324 path: p,
1325 op: ComparisonOp::Le,
1326 value: self.parse_value()?,
1327 })
1328 }
1329 Some(TokenKind::Contains) => {
1330 self.advance();
1331 Ok(Condition::Contains {
1332 path: p,
1333 value: self.parse_value()?,
1334 })
1335 }
1336 Some(TokenKind::StartsWith) => {
1337 self.advance();
1338 Ok(Condition::StartsWith {
1339 path: p,
1340 prefix: self.expect_str()?,
1341 })
1342 }
1343 Some(TokenKind::EndsWith) => {
1344 self.advance();
1345 Ok(Condition::EndsWith {
1346 path: p,
1347 suffix: self.expect_str()?,
1348 })
1349 }
1350 Some(TokenKind::Matches) => {
1351 self.advance();
1352 Ok(Condition::Matches {
1353 path: p,
1354 regex: self.expect_str()?,
1355 })
1356 }
1357 Some(TokenKind::Exists) => {
1358 self.advance();
1359 Ok(Condition::Exists { path: p })
1360 }
1361 Some(TokenKind::IsNull) => {
1362 self.advance();
1363 Ok(Condition::IsNull { path: p })
1364 }
1365 _ => Err(self.error("comparison")),
1366 }
1367 }
1368
1369 fn parse_content_literal(&mut self) -> ParseResult<String> {
1370 match self.peek_kind() {
1371 Some(TokenKind::DoubleString(s))
1372 | Some(TokenKind::SingleString(s))
1373 | Some(TokenKind::TripleString(s)) => {
1374 self.advance();
1375 Ok(s)
1376 }
1377 Some(TokenKind::CodeBlock(s)) | Some(TokenKind::TableLiteral(s)) => {
1378 self.advance();
1379 Ok(s)
1380 }
1381 Some(TokenKind::LBrace) => {
1382 let o = self.parse_object()?;
1383 Ok(serde_json::to_string(&o.to_json()).unwrap_or_default())
1384 }
1385 _ => Err(self.error("content")),
1386 }
1387 }
1388
1389 fn parse_content_type(&mut self) -> ParseResult<ContentType> {
1390 self.try_content_type()
1391 .ok_or_else(|| self.error("content type"))
1392 }
1393 fn try_content_type(&mut self) -> Option<ContentType> {
1394 match self.peek_kind() {
1395 Some(TokenKind::TextType) => {
1396 self.advance();
1397 Some(ContentType::Text)
1398 }
1399 Some(TokenKind::TableType) => {
1400 self.advance();
1401 Some(ContentType::Table)
1402 }
1403 Some(TokenKind::CodeType) => {
1404 self.advance();
1405 Some(ContentType::Code)
1406 }
1407 Some(TokenKind::MathType) => {
1408 self.advance();
1409 Some(ContentType::Math)
1410 }
1411 Some(TokenKind::MediaType) => {
1412 self.advance();
1413 Some(ContentType::Media)
1414 }
1415 Some(TokenKind::JsonType) => {
1416 self.advance();
1417 Some(ContentType::Json)
1418 }
1419 Some(TokenKind::BinaryType) => {
1420 self.advance();
1421 Some(ContentType::Binary)
1422 }
1423 Some(TokenKind::CompositeType) => {
1424 self.advance();
1425 Some(ContentType::Composite)
1426 }
1427 _ => None,
1428 }
1429 }
1430
1431 fn peek(&self) -> Option<&Token> {
1432 self.tokens.get(self.pos)
1433 }
1434 fn peek_kind(&self) -> Option<TokenKind> {
1435 self.peek().map(|t| t.kind.clone())
1436 }
1437 fn advance(&mut self) -> Option<&Token> {
1438 if !self.is_at_end() {
1439 self.pos += 1;
1440 }
1441 self.tokens.get(self.pos - 1)
1442 }
1443 fn check(&self, k: TokenKind) -> bool {
1444 self.peek_kind() == Some(k)
1445 }
1446 fn is_at_end(&self) -> bool {
1447 self.pos >= self.tokens.len()
1448 }
1449 fn expect(&mut self, k: TokenKind) -> ParseResult<&Token> {
1450 if self.check(k.clone()) {
1451 Ok(self.advance().unwrap())
1452 } else {
1453 Err(self.error(&format!("{:?}", k)))
1454 }
1455 }
1456 fn expect_block_id(&mut self) -> ParseResult<String> {
1457 if matches!(self.peek_kind(), Some(TokenKind::BlockId)) {
1458 let span = self.tokens[self.pos].span.clone();
1459 self.advance();
1460 Ok(self.source[span].to_string())
1461 } else {
1462 Err(self.error("block ID"))
1463 }
1464 }
1465 fn expect_ident(&mut self) -> ParseResult<String> {
1466 if matches!(self.peek_kind(), Some(TokenKind::Identifier)) {
1467 let span = self.tokens[self.pos].span.clone();
1468 self.advance();
1469 Ok(self.source[span].to_string())
1470 } else {
1471 Err(self.error("identifier"))
1472 }
1473 }
1474 fn is_ident_or_keyword(&self) -> bool {
1475 matches!(
1476 self.peek_kind(),
1477 Some(TokenKind::Identifier)
1478 | Some(TokenKind::TextType)
1479 | Some(TokenKind::TableType)
1480 | Some(TokenKind::CodeType)
1481 | Some(TokenKind::MathType)
1482 | Some(TokenKind::MediaType)
1483 | Some(TokenKind::JsonType)
1484 | Some(TokenKind::BinaryType)
1485 | Some(TokenKind::CompositeType)
1486 | Some(TokenKind::True)
1487 | Some(TokenKind::False)
1488 | Some(TokenKind::Null)
1489 | Some(TokenKind::Label)
1491 | Some(TokenKind::Role)
1492 | Some(TokenKind::Tag)
1493 | Some(TokenKind::Tags)
1494 | Some(TokenKind::Mode)
1495 | Some(TokenKind::Depth)
1496 | Some(TokenKind::Limit)
1497 | Some(TokenKind::Max)
1498 | Some(TokenKind::Format)
1499 | Some(TokenKind::Method)
1500 | Some(TokenKind::Reason)
1501 | Some(TokenKind::Relevance)
1502 | Some(TokenKind::Pattern)
1503 | Some(TokenKind::Full)
1504 | Some(TokenKind::Preview)
1505 | Some(TokenKind::MetadataToken)
1506 )
1507 }
1508 fn expect_ident_or_keyword(&mut self) -> ParseResult<String> {
1509 if self.is_ident_or_keyword() {
1510 let span = self.tokens[self.pos].span.clone();
1511 self.advance();
1512 Ok(self.source[span].to_string())
1513 } else {
1514 Err(self.error("identifier"))
1515 }
1516 }
1517 fn is_path_property_start(&self) -> bool {
1518 self.is_ident_or_keyword()
1519 || matches!(
1520 self.peek_kind(),
1521 Some(TokenKind::DoubleString(_)) | Some(TokenKind::SingleString(_))
1522 )
1523 }
1524 fn expect_path_property(&mut self) -> ParseResult<String> {
1525 match self.peek_kind() {
1526 Some(TokenKind::DoubleString(s)) | Some(TokenKind::SingleString(s)) => {
1527 self.advance();
1528 Ok(s)
1529 }
1530 _ => self.expect_ident_or_keyword(),
1531 }
1532 }
1533 fn expect_str(&mut self) -> ParseResult<String> {
1534 match self.peek_kind() {
1535 Some(TokenKind::DoubleString(s))
1536 | Some(TokenKind::SingleString(s))
1537 | Some(TokenKind::TripleString(s)) => {
1538 self.advance();
1539 Ok(s)
1540 }
1541 _ => Err(self.error("string")),
1542 }
1543 }
1544 fn expect_int(&mut self) -> ParseResult<i64> {
1545 if let Some(TokenKind::Integer(n)) = self.peek_kind() {
1546 self.advance();
1547 Ok(n)
1548 } else {
1549 Err(self.error("integer"))
1550 }
1551 }
1552
1553 fn expect_eq_with_hint(&mut self, hint: &str) -> ParseResult<()> {
1554 if self.check(TokenKind::Eq) {
1555 self.advance();
1556 Ok(())
1557 } else {
1558 Err(self.error_with_hint(hint))
1559 }
1560 }
1561 fn try_str(&mut self) -> Option<String> {
1562 match self.peek_kind() {
1563 Some(TokenKind::DoubleString(s)) | Some(TokenKind::SingleString(s)) => {
1564 self.advance();
1565 Some(s)
1566 }
1567 _ => None,
1568 }
1569 }
1570 fn is_section_header(&self) -> bool {
1571 matches!(
1572 self.peek_kind(),
1573 Some(TokenKind::Structure) | Some(TokenKind::Blocks) | Some(TokenKind::Commands)
1574 )
1575 }
1576 fn is_cmd_start(&self) -> bool {
1577 matches!(
1578 self.peek_kind(),
1579 Some(TokenKind::Edit)
1581 | Some(TokenKind::Move)
1582 | Some(TokenKind::Append)
1583 | Some(TokenKind::Delete)
1584 | Some(TokenKind::Prune)
1585 | Some(TokenKind::Fold)
1586 | Some(TokenKind::Link)
1587 | Some(TokenKind::Unlink)
1588 | Some(TokenKind::Snapshot)
1589 | Some(TokenKind::Begin)
1590 | Some(TokenKind::Commit)
1591 | Some(TokenKind::Rollback)
1592 | Some(TokenKind::Atomic)
1593 | Some(TokenKind::WriteSection)
1594 | Some(TokenKind::Goto)
1596 | Some(TokenKind::Back)
1597 | Some(TokenKind::Expand)
1598 | Some(TokenKind::Follow)
1599 | Some(TokenKind::Path)
1600 | Some(TokenKind::Search)
1601 | Some(TokenKind::Find)
1602 | Some(TokenKind::View)
1603 | Some(TokenKind::Ctx)
1605 )
1606 }
1607 fn error(&self, exp: &str) -> ParseError {
1608 let (l, c, f) = self
1609 .peek()
1610 .map(|t| (t.line, t.column, format!("{:?}", t.kind)))
1611 .unwrap_or((0, 0, "EOF".into()));
1612 ParseError::UnexpectedToken {
1613 expected: exp.into(),
1614 found: f,
1615 line: l,
1616 column: c,
1617 }
1618 }
1619 fn error_with_hint(&self, message: &str) -> ParseError {
1620 let line = self.peek().map(|t| t.line).unwrap_or(0);
1621 ParseError::InvalidSyntax {
1622 message: message.into(),
1623 line,
1624 }
1625 }
1626}
1627
1628#[cfg(test)]
1629mod tests {
1630 use super::*;
1631
1632 #[test]
1633 fn test_parse_edit() {
1634 let r = Parser::new(r#"EDIT blk_abc123def456 SET name = "hello""#).parse_commands_only();
1635 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1636 }
1637
1638 #[test]
1639 fn test_parse_edit_with_keyword_path() {
1640 let r = Parser::new(r#"EDIT blk_abc123def456 SET content.text = "hello""#)
1642 .parse_commands_only();
1643 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1644 }
1645
1646 #[test]
1651 fn test_parse_goto() {
1652 let r = Parser::new("GOTO blk_abc123def456").parse_commands_only();
1653 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1654 let cmds = r.unwrap();
1655 assert_eq!(cmds.len(), 1);
1656 match &cmds[0] {
1657 Command::Goto(cmd) => assert_eq!(cmd.block_id, "blk_abc123def456"),
1658 _ => panic!("Expected Goto command"),
1659 }
1660 }
1661
1662 #[test]
1663 fn test_parse_back() {
1664 let r = Parser::new("BACK").parse_commands_only();
1666 assert!(r.is_ok());
1667 match &r.unwrap()[0] {
1668 Command::Back(cmd) => assert_eq!(cmd.steps, 1),
1669 _ => panic!("Expected Back command"),
1670 }
1671
1672 let r = Parser::new("BACK 3").parse_commands_only();
1674 assert!(r.is_ok());
1675 match &r.unwrap()[0] {
1676 Command::Back(cmd) => assert_eq!(cmd.steps, 3),
1677 _ => panic!("Expected Back command"),
1678 }
1679 }
1680
1681 #[test]
1682 fn test_parse_expand() {
1683 let r =
1684 Parser::new("EXPAND blk_abc123def456 DOWN DEPTH=3 MODE=PREVIEW").parse_commands_only();
1685 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1686 match &r.unwrap()[0] {
1687 Command::Expand(cmd) => {
1688 assert_eq!(cmd.block_id, "blk_abc123def456");
1689 assert_eq!(cmd.direction, ExpandDirection::Down);
1690 assert_eq!(cmd.depth, 3);
1691 assert!(matches!(cmd.mode, Some(ViewMode::Preview { .. })));
1692 }
1693 _ => panic!("Expected Expand command"),
1694 }
1695 }
1696
1697 #[test]
1698 fn test_parse_expand_semantic() {
1699 let r = Parser::new("EXPAND blk_abc123def456 SEMANTIC").parse_commands_only();
1700 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1701 match &r.unwrap()[0] {
1702 Command::Expand(cmd) => {
1703 assert_eq!(cmd.direction, ExpandDirection::Semantic);
1704 }
1705 _ => panic!("Expected Expand command"),
1706 }
1707 }
1708
1709 #[test]
1710 fn test_parse_follow() {
1711 let r = Parser::new("FOLLOW blk_abc123def456 references").parse_commands_only();
1712 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1713 match &r.unwrap()[0] {
1714 Command::Follow(cmd) => {
1715 assert_eq!(cmd.source_id, "blk_abc123def456");
1716 assert_eq!(cmd.edge_types, vec!["references"]);
1717 assert!(cmd.target_id.is_none());
1718 }
1719 _ => panic!("Expected Follow command"),
1720 }
1721 }
1722
1723 #[test]
1724 fn test_parse_path_find() {
1725 let r = Parser::new("PATH blk_abc123def456 TO blk_111222333444").parse_commands_only();
1726 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1727 match &r.unwrap()[0] {
1728 Command::Path(cmd) => {
1729 assert_eq!(cmd.from_id, "blk_abc123def456");
1730 assert_eq!(cmd.to_id, "blk_111222333444");
1731 assert!(cmd.max_length.is_none());
1732 }
1733 _ => panic!("Expected Path command"),
1734 }
1735 }
1736
1737 #[test]
1738 fn test_parse_search() {
1739 let r = Parser::new(r#"SEARCH "authentication flow" LIMIT=10"#).parse_commands_only();
1740 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1741 match &r.unwrap()[0] {
1742 Command::Search(cmd) => {
1743 assert_eq!(cmd.query, "authentication flow");
1744 assert_eq!(cmd.limit, Some(10));
1745 }
1746 _ => panic!("Expected Search command"),
1747 }
1748 }
1749
1750 #[test]
1751 fn test_parse_find() {
1752 let r = Parser::new(r#"FIND ROLE=heading1 TAG="important""#).parse_commands_only();
1753 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1754 match &r.unwrap()[0] {
1755 Command::Find(cmd) => {
1756 assert_eq!(cmd.role, Some("heading1".to_string()));
1757 assert_eq!(cmd.tag, Some("important".to_string()));
1758 }
1759 _ => panic!("Expected Find command"),
1760 }
1761 }
1762
1763 #[test]
1764 fn test_parse_view_block() {
1765 let r = Parser::new("VIEW blk_abc123def456 MODE=METADATA").parse_commands_only();
1766 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1767 match &r.unwrap()[0] {
1768 Command::View(cmd) => {
1769 assert!(
1770 matches!(cmd.target, ViewTarget::Block(ref id) if id == "blk_abc123def456")
1771 );
1772 assert!(matches!(cmd.mode, ViewMode::Metadata));
1773 }
1774 _ => panic!("Expected View command"),
1775 }
1776 }
1777
1778 #[test]
1779 fn test_parse_view_neighborhood() {
1780 let r = Parser::new("VIEW NEIGHBORHOOD DEPTH=2").parse_commands_only();
1781 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1782 match &r.unwrap()[0] {
1783 Command::View(cmd) => {
1784 assert!(matches!(cmd.target, ViewTarget::Neighborhood));
1785 assert_eq!(cmd.depth, Some(2));
1786 }
1787 _ => panic!("Expected View command"),
1788 }
1789 }
1790
1791 #[test]
1796 fn test_parse_ctx_add_block() {
1797 let r = Parser::new(r#"CTX ADD blk_abc123def456 REASON="semantic_relevance""#)
1798 .parse_commands_only();
1799 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1800 match &r.unwrap()[0] {
1801 Command::Context(ContextCommand::Add(cmd)) => {
1802 assert!(
1803 matches!(cmd.target, ContextAddTarget::Block(ref id) if id == "blk_abc123def456")
1804 );
1805 assert_eq!(cmd.reason, Some("semantic_relevance".to_string()));
1806 }
1807 _ => panic!("Expected CTX ADD command"),
1808 }
1809 }
1810
1811 #[test]
1812 fn test_parse_ctx_add_results() {
1813 let r = Parser::new("CTX ADD RESULTS").parse_commands_only();
1814 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1815 match &r.unwrap()[0] {
1816 Command::Context(ContextCommand::Add(cmd)) => {
1817 assert!(matches!(cmd.target, ContextAddTarget::Results));
1818 }
1819 _ => panic!("Expected CTX ADD RESULTS command"),
1820 }
1821 }
1822
1823 #[test]
1824 fn test_parse_ctx_add_children() {
1825 let r = Parser::new("CTX ADD CHILDREN blk_abc123def456").parse_commands_only();
1826 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1827 match &r.unwrap()[0] {
1828 Command::Context(ContextCommand::Add(cmd)) => {
1829 assert!(
1830 matches!(cmd.target, ContextAddTarget::Children { ref parent_id } if parent_id == "blk_abc123def456")
1831 );
1832 }
1833 _ => panic!("Expected CTX ADD CHILDREN command"),
1834 }
1835 }
1836
1837 #[test]
1838 fn test_parse_ctx_remove() {
1839 let r = Parser::new("CTX REMOVE blk_abc123def456").parse_commands_only();
1840 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1841 match &r.unwrap()[0] {
1842 Command::Context(ContextCommand::Remove { block_id }) => {
1843 assert_eq!(block_id, "blk_abc123def456");
1844 }
1845 _ => panic!("Expected CTX REMOVE command"),
1846 }
1847 }
1848
1849 #[test]
1850 fn test_parse_ctx_clear() {
1851 let r = Parser::new("CTX CLEAR").parse_commands_only();
1852 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1853 assert!(matches!(
1854 r.unwrap()[0],
1855 Command::Context(ContextCommand::Clear)
1856 ));
1857 }
1858
1859 #[test]
1860 fn test_parse_ctx_expand() {
1861 let r = Parser::new("CTX EXPAND SEMANTIC DEPTH=2").parse_commands_only();
1862 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1863 match &r.unwrap()[0] {
1864 Command::Context(ContextCommand::Expand(cmd)) => {
1865 assert_eq!(cmd.direction, ExpandDirection::Semantic);
1866 assert_eq!(cmd.depth, Some(2));
1867 }
1868 _ => panic!("Expected CTX EXPAND command"),
1869 }
1870 }
1871
1872 #[test]
1873 fn test_parse_ctx_compress() {
1874 let r = Parser::new("CTX COMPRESS METHOD=TRUNCATE").parse_commands_only();
1875 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1876 match &r.unwrap()[0] {
1877 Command::Context(ContextCommand::Compress { method }) => {
1878 assert_eq!(*method, CompressionMethod::Truncate);
1879 }
1880 _ => panic!("Expected CTX COMPRESS command"),
1881 }
1882 }
1883
1884 #[test]
1885 fn test_parse_ctx_prune() {
1886 let r = Parser::new("CTX PRUNE RELEVANCE=0.3 MAX_AGE=300").parse_commands_only();
1887 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1888 match &r.unwrap()[0] {
1889 Command::Context(ContextCommand::Prune(cmd)) => {
1890 assert_eq!(cmd.min_relevance, Some(0.3));
1891 assert_eq!(cmd.max_age_secs, Some(300));
1892 }
1893 _ => panic!("Expected CTX PRUNE command"),
1894 }
1895 }
1896
1897 #[test]
1898 fn test_parse_ctx_render() {
1899 let r = Parser::new("CTX RENDER FORMAT=SHORT_IDS").parse_commands_only();
1900 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1901 match &r.unwrap()[0] {
1902 Command::Context(ContextCommand::Render { format }) => {
1903 assert_eq!(*format, Some(RenderFormat::ShortIds));
1904 }
1905 _ => panic!("Expected CTX RENDER command"),
1906 }
1907 }
1908
1909 #[test]
1910 fn test_parse_ctx_stats() {
1911 let r = Parser::new("CTX STATS").parse_commands_only();
1912 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1913 assert!(matches!(
1914 r.unwrap()[0],
1915 Command::Context(ContextCommand::Stats)
1916 ));
1917 }
1918
1919 #[test]
1920 fn test_parse_ctx_focus() {
1921 let r = Parser::new("CTX FOCUS blk_abc123def456").parse_commands_only();
1922 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1923 match &r.unwrap()[0] {
1924 Command::Context(ContextCommand::Focus { block_id }) => {
1925 assert_eq!(*block_id, Some("blk_abc123def456".to_string()));
1926 }
1927 _ => panic!("Expected CTX FOCUS command"),
1928 }
1929 }
1930
1931 #[test]
1932 fn test_parse_ctx_focus_clear() {
1933 let r = Parser::new("CTX FOCUS CLEAR").parse_commands_only();
1934 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1935 match &r.unwrap()[0] {
1936 Command::Context(ContextCommand::Focus { block_id }) => {
1937 assert!(block_id.is_none());
1938 }
1939 _ => panic!("Expected CTX FOCUS CLEAR command"),
1940 }
1941 }
1942
1943 #[test]
1944 fn test_parse_multiple_commands() {
1945 let input = r#"
1946 GOTO blk_abc123def456
1947 EXPAND blk_abc123def456 DOWN DEPTH=2
1948 CTX ADD RESULTS
1949 CTX RENDER FORMAT=SHORT_IDS
1950 "#;
1951 let r = Parser::new(input).parse_commands_only();
1952 assert!(r.is_ok(), "Parse error: {:?}", r.err());
1953 assert_eq!(r.unwrap().len(), 4);
1954 }
1955}