Skip to main content

ucl_parser/
parser.rs

1//! Parser for UCL documents.
2
3use 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            // Document modification commands
153            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            // Agent traversal commands
169            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            // Context commands
179            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(); // consume WRITE_SECTION
394
395        let section_id = self.expect_block_id()?;
396
397        // Expect :: separator for markdown content
398        self.expect(TokenKind::DoubleColon)?;
399
400        // Parse markdown content (string literal)
401        let markdown = self.expect_str()?;
402
403        // Parse optional BASE_LEVEL
404        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    // ========================================================================
491    // Agent Traversal Command Parsers
492    // ========================================================================
493
494    /// Parse GOTO blk_xxx
495    fn parse_goto(&mut self) -> ParseResult<Command> {
496        self.advance(); // consume GOTO
497        let block_id = self.expect_block_id()?;
498        Ok(Command::Goto(GotoCommand { block_id }))
499    }
500
501    /// Parse BACK [n]
502    fn parse_back(&mut self) -> ParseResult<Command> {
503        self.advance(); // consume BACK
504        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    /// Parse EXPAND blk_xxx DOWN|UP|BOTH|SEMANTIC [depth=N] [mode=MODE] [roles=...] [tags=...]
514    fn parse_expand(&mut self) -> ParseResult<Command> {
515        self.advance(); // consume EXPAND
516        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        // Parse direction
527        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, // Default
545        };
546
547        // Parse options
548        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    /// Parse FOLLOW blk_xxx edge_type[,edge_type...] [blk_yyy]
596    fn parse_follow(&mut self) -> ParseResult<Command> {
597        self.advance(); // consume FOLLOW
598        let source_id = self.expect_block_id()?;
599
600        // Parse edge types (comma-separated identifiers)
601        let edge_types = self.parse_comma_list()?;
602
603        // Optional target block
604        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    /// Parse PATH blk_xxx TO blk_yyy [max=N]
618    fn parse_path_find(&mut self) -> ParseResult<Command> {
619        self.advance(); // consume PATH
620        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    /// Parse SEARCH "query" [limit=N] [min_similarity=F] [roles=...]
648    fn parse_search(&mut self) -> ParseResult<Command> {
649        self.advance(); // consume SEARCH
650        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    /// Parse FIND [role=...] [tag=...] [label=...] [pattern=...]
695    fn parse_find(&mut self) -> ParseResult<Command> {
696        self.advance(); // consume FIND
697        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    /// Parse VIEW blk_xxx|NEIGHBORHOOD [mode=...] [depth=N]
729    fn parse_view(&mut self) -> ParseResult<Command> {
730        self.advance(); // consume VIEW
731
732        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    // ========================================================================
768    // Context Command Parsers
769    // ========================================================================
770
771    /// Parse CTX ADD|REMOVE|CLEAR|EXPAND|COMPRESS|PRUNE|RENDER|STATS|FOCUS ...
772    fn parse_ctx(&mut self) -> ParseResult<Command> {
773        self.advance(); // consume CTX
774
775        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    /// Parse CTX ADD blk_xxx|RESULTS|CHILDREN blk_xxx|PATH blk_xxx TO blk_yyy [reason=...] [relevance=F]
798    fn parse_ctx_add(&mut self) -> ParseResult<Command> {
799        self.advance(); // consume ADD
800
801        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    /// Parse CTX REMOVE blk_xxx
858    fn parse_ctx_remove(&mut self) -> ParseResult<Command> {
859        self.advance(); // consume REMOVE
860        let block_id = self.expect_block_id()?;
861        Ok(Command::Context(ContextCommand::Remove { block_id }))
862    }
863
864    /// Parse CTX EXPAND DOWN|UP|SEMANTIC|AUTO [depth=N] [tokens=N]
865    fn parse_ctx_expand(&mut self) -> ParseResult<Command> {
866        self.advance(); // consume EXPAND
867
868        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    /// Parse CTX COMPRESS method=TRUNCATE|SUMMARIZE|STRUCTURE_ONLY
917    fn parse_ctx_compress(&mut self) -> ParseResult<Command> {
918        self.advance(); // consume COMPRESS
919
920        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    /// Parse CTX PRUNE [min_relevance=F] [max_age=N]
932    fn parse_ctx_prune(&mut self) -> ParseResult<Command> {
933        self.advance(); // consume PRUNE
934
935        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    /// Parse CTX RENDER [format=DEFAULT|SHORT_IDS|MARKDOWN]
963    fn parse_ctx_render(&mut self) -> ParseResult<Command> {
964        self.advance(); // consume RENDER
965
966        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    /// Parse CTX FOCUS blk_xxx|CLEAR
978    fn parse_ctx_focus(&mut self) -> ParseResult<Command> {
979        self.advance(); // consume FOCUS
980
981        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    // ========================================================================
994    // Helper Parsers
995    // ========================================================================
996
997    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    /// Parse comma-separated list of identifiers
1070    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                // Allow keywords to be used as property names
1490                | 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            // Document modification commands
1580            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                // Agent traversal commands
1595                | 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                // Context commands
1604                | 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        // Test that keywords like 'text' work as identifiers in paths
1641        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    // ========================================================================
1647    // Agent Traversal Command Tests
1648    // ========================================================================
1649
1650    #[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        // Default steps
1665        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        // Custom steps
1673        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    // ========================================================================
1792    // Context Command Tests
1793    // ========================================================================
1794
1795    #[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}