1use crate::ast::*;
13use crate::tokens::{is_declaration_keyword, Token, TokenType, Trivia, TriviaKind};
14
15const fn is_comment_token(tt: &TokenType) -> bool {
21 matches!(
22 tt,
23 TokenType::LineComment
24 | TokenType::BlockComment
25 | TokenType::DocLineComment
26 | TokenType::DocBlockComment
27 | TokenType::InnerDocLineComment
28 | TokenType::InnerDocBlockComment
29 )
30}
31
32const fn token_to_trivia_kind(tt: &TokenType) -> Option<TriviaKind> {
33 match tt {
34 TokenType::LineComment => Some(TriviaKind::Line),
35 TokenType::BlockComment => Some(TriviaKind::Block),
36 TokenType::DocLineComment => Some(TriviaKind::DocLine),
37 TokenType::DocBlockComment => Some(TriviaKind::DocBlock),
38 TokenType::InnerDocLineComment => Some(TriviaKind::InnerDocLine),
39 TokenType::InnerDocBlockComment => Some(TriviaKind::InnerDocBlock),
40 _ => None,
41 }
42}
43
44fn attach_trivia_to_decl(decl: &mut Declaration, leading: Vec<Trivia>, trailing: Vec<Trivia>) {
52 match decl {
53 Declaration::Import(n) => {
54 n.leading_trivia = leading;
55 n.trailing_trivia = trailing;
56 }
57 Declaration::Persona(n) => {
58 n.leading_trivia = leading;
59 n.trailing_trivia = trailing;
60 }
61 Declaration::Context(n) => {
62 n.leading_trivia = leading;
63 n.trailing_trivia = trailing;
64 }
65 Declaration::Anchor(n) => {
66 n.leading_trivia = leading;
67 n.trailing_trivia = trailing;
68 }
69 Declaration::Memory(n) => {
70 n.leading_trivia = leading;
71 n.trailing_trivia = trailing;
72 }
73 Declaration::Tool(n) => {
74 n.leading_trivia = leading;
75 n.trailing_trivia = trailing;
76 }
77 Declaration::Type(n) => {
78 n.leading_trivia = leading;
79 n.trailing_trivia = trailing;
80 }
81 Declaration::Flow(n) => {
82 n.leading_trivia = leading;
83 n.trailing_trivia = trailing;
84 }
85 Declaration::Intent(n) => {
86 n.leading_trivia = leading;
87 n.trailing_trivia = trailing;
88 }
89 Declaration::Run(n) => {
90 n.leading_trivia = leading;
91 n.trailing_trivia = trailing;
92 }
93 Declaration::Epistemic(n) => {
94 n.leading_trivia = leading;
95 n.trailing_trivia = trailing;
96 }
97 Declaration::Let(n) => {
98 n.leading_trivia = leading;
99 n.trailing_trivia = trailing;
100 }
101 Declaration::LambdaData(n) => {
102 n.leading_trivia = leading;
103 n.trailing_trivia = trailing;
104 }
105 Declaration::Agent(n) => {
106 n.leading_trivia = leading;
107 n.trailing_trivia = trailing;
108 }
109 Declaration::Shield(n) => {
110 n.leading_trivia = leading;
111 n.trailing_trivia = trailing;
112 }
113 Declaration::Pix(n) => {
114 n.leading_trivia = leading;
115 n.trailing_trivia = trailing;
116 }
117 Declaration::Ledger(n) => {
118 n.leading_trivia = leading;
119 n.trailing_trivia = trailing;
120 }
121 Declaration::Psyche(n) => {
122 n.leading_trivia = leading;
123 n.trailing_trivia = trailing;
124 }
125 Declaration::Corpus(n) => {
126 n.leading_trivia = leading;
127 n.trailing_trivia = trailing;
128 }
129 Declaration::Dataspace(n) => {
130 n.leading_trivia = leading;
131 n.trailing_trivia = trailing;
132 }
133 Declaration::Ots(n) => {
134 n.leading_trivia = leading;
135 n.trailing_trivia = trailing;
136 }
137 Declaration::Mandate(n) => {
138 n.leading_trivia = leading;
139 n.trailing_trivia = trailing;
140 }
141 Declaration::Compute(n) => {
142 n.leading_trivia = leading;
143 n.trailing_trivia = trailing;
144 }
145 Declaration::Daemon(n) => {
146 n.leading_trivia = leading;
147 n.trailing_trivia = trailing;
148 }
149 Declaration::Extension(n) => {
150 n.leading_trivia = leading;
151 n.trailing_trivia = trailing;
152 }
153 Declaration::AxonStore(n) => {
154 n.leading_trivia = leading;
155 n.trailing_trivia = trailing;
156 }
157 Declaration::AxonEndpoint(n) => {
158 n.leading_trivia = leading;
159 n.trailing_trivia = trailing;
160 }
161 Declaration::Resource(n) => {
162 n.leading_trivia = leading;
163 n.trailing_trivia = trailing;
164 }
165 Declaration::Fabric(n) => {
166 n.leading_trivia = leading;
167 n.trailing_trivia = trailing;
168 }
169 Declaration::Manifest(n) => {
170 n.leading_trivia = leading;
171 n.trailing_trivia = trailing;
172 }
173 Declaration::Observe(n) => {
174 n.leading_trivia = leading;
175 n.trailing_trivia = trailing;
176 }
177 Declaration::Reconcile(n) => {
178 n.leading_trivia = leading;
179 n.trailing_trivia = trailing;
180 }
181 Declaration::Lease(n) => {
182 n.leading_trivia = leading;
183 n.trailing_trivia = trailing;
184 }
185 Declaration::Ensemble(n) => {
186 n.leading_trivia = leading;
187 n.trailing_trivia = trailing;
188 }
189 Declaration::Session(n) => {
190 n.leading_trivia = leading;
191 n.trailing_trivia = trailing;
192 }
193 Declaration::Topology(n) => {
194 n.leading_trivia = leading;
195 n.trailing_trivia = trailing;
196 }
197 Declaration::Immune(n) => {
198 n.leading_trivia = leading;
199 n.trailing_trivia = trailing;
200 }
201 Declaration::Reflex(n) => {
202 n.leading_trivia = leading;
203 n.trailing_trivia = trailing;
204 }
205 Declaration::Heal(n) => {
206 n.leading_trivia = leading;
207 n.trailing_trivia = trailing;
208 }
209 Declaration::Component(n) => {
210 n.leading_trivia = leading;
211 n.trailing_trivia = trailing;
212 }
213 Declaration::View(n) => {
214 n.leading_trivia = leading;
215 n.trailing_trivia = trailing;
216 }
217 Declaration::Channel(n) => {
218 n.leading_trivia = leading;
219 n.trailing_trivia = trailing;
220 }
221 Declaration::Socket(n) => {
222 n.leading_trivia = leading;
223 n.trailing_trivia = trailing;
224 }
225 Declaration::Observable(n) => {
226 n.leading_trivia = leading;
227 n.trailing_trivia = trailing;
228 }
229 Declaration::Witness(n) => {
230 n.leading_trivia = leading;
231 n.trailing_trivia = trailing;
232 }
233 Declaration::Generic(n) => {
234 n.leading_trivia = leading;
235 n.trailing_trivia = trailing;
236 }
237 }
238}
239
240pub const SOURCE_CONTEXT_LINES_BEFORE: usize = 2;
247pub const SOURCE_CONTEXT_LINES_AFTER: usize = 2;
248
249#[derive(Debug, Clone)]
260pub struct SourceSnippet {
261 pub source: String,
262 pub line: u32,
263 pub column: u32,
264 pub filename: String,
265 pub context_before: usize,
266 pub context_after: usize,
267}
268
269impl SourceSnippet {
270 pub fn new(source: String, line: u32, column: u32, filename: String) -> Self {
272 Self {
273 source,
274 line,
275 column,
276 filename,
277 context_before: SOURCE_CONTEXT_LINES_BEFORE,
278 context_after: SOURCE_CONTEXT_LINES_AFTER,
279 }
280 }
281
282 #[must_use]
289 pub fn render(&self) -> String {
290 if self.source.is_empty() || self.line < 1 {
291 return String::new();
292 }
293 let raw: Vec<&str> = self.source.split('\n').collect();
294 let lines: Vec<&str> = if raw.last() == Some(&"") {
297 raw[..raw.len() - 1].to_vec()
298 } else {
299 raw
300 };
301 if lines.is_empty() || self.line as usize > lines.len() {
302 return String::new();
303 }
304
305 let line_idx = self.line as usize;
306 let start = line_idx.saturating_sub(self.context_before).max(1);
307 let end = (line_idx + self.context_after).min(lines.len());
308
309 let gutter = end.to_string().len();
310 let empty_gutter = " ".repeat(gutter);
311
312 let mut out: Vec<String> = Vec::with_capacity(end - start + 4);
313 out.push(format!(
314 "{empty_gutter} --> {}:{}:{}",
315 self.filename, self.line, self.column
316 ));
317 out.push(format!("{empty_gutter} |"));
318 for n in start..=end {
319 let line_text = lines[n - 1];
320 out.push(format!("{n:>gutter$} | {line_text}", gutter = gutter));
321 if n == line_idx {
322 let line_len = line_text.chars().count();
323 let col = (self.column as usize).clamp(1, line_len + 1);
324 out.push(format!(
325 "{empty_gutter} | {pad}^",
326 pad = " ".repeat(col - 1)
327 ));
328 }
329 }
330 out.join("\n")
331 }
332}
333
334#[derive(Debug, Clone, Default)]
335pub struct ParseError {
336 pub message: String,
337 pub line: u32,
338 pub column: u32,
339 pub source_snippet: Option<SourceSnippet>,
346}
347
348impl ParseError {
349 #[must_use]
353 pub fn attach_source(mut self, source: &str, filename: &str) -> Self {
354 if self.line >= 1 {
355 self.source_snippet = Some(SourceSnippet::new(
356 source.to_string(),
357 self.line,
358 self.column,
359 filename.to_string(),
360 ));
361 }
362 self
363 }
364}
365
366impl std::fmt::Display for ParseError {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 write!(f, "[line {}:{}] {}", self.line, self.column, self.message)?;
369 if let Some(snippet) = &self.source_snippet {
370 let block = snippet.render();
371 if !block.is_empty() {
372 write!(f, "\n{block}")?;
373 }
374 }
375 Ok(())
376 }
377}
378
379impl std::error::Error for ParseError {}
380
381#[derive(Debug)]
399pub struct ParseResult {
400 pub program: Program,
401 pub errors: Vec<ParseError>,
402}
403
404impl ParseResult {
405 #[inline]
410 #[must_use]
411 pub fn has_errors(&self) -> bool {
412 !self.errors.is_empty()
413 }
414
415 #[inline]
418 #[must_use]
419 pub fn is_clean(&self) -> bool {
420 self.errors.is_empty()
421 }
422}
423
424#[inline]
438const fn is_top_level_decl_kw_for_recovery(tt: &TokenType) -> bool {
439 matches!(
440 tt,
441 TokenType::Import
442 | TokenType::Persona
443 | TokenType::Context
444 | TokenType::Anchor
445 | TokenType::Memory
446 | TokenType::Tool
447 | TokenType::Type
448 | TokenType::Flow
449 | TokenType::Intent
450 | TokenType::Run
451 | TokenType::Let
452 | TokenType::Know
453 | TokenType::Believe
454 | TokenType::Speculate
455 | TokenType::Doubt
456 | TokenType::Lambda
457 | TokenType::Agent
458 | TokenType::Shield
459 | TokenType::Pix
460 | TokenType::Ledger
461 | TokenType::Psyche
462 | TokenType::Corpus
463 | TokenType::Dataspace
464 | TokenType::Ots
465 | TokenType::Mandate
466 | TokenType::Compute
467 | TokenType::Daemon
468 | TokenType::AxonStore
469 | TokenType::AxonEndpoint
470 | TokenType::Resource
471 | TokenType::Fabric
472 | TokenType::Manifest
473 | TokenType::Observe
474 | TokenType::Reconcile
475 | TokenType::Lease
476 | TokenType::Ensemble
477 | TokenType::Session
478 | TokenType::Topology
479 | TokenType::Immune
480 | TokenType::Reflex
481 | TokenType::Heal
482 | TokenType::Component
483 | TokenType::View
484 | TokenType::Channel
485 | TokenType::Ingest
486 | TokenType::Persist
487 | TokenType::Retrieve
488 | TokenType::Mutate
489 | TokenType::Purge
490 | TokenType::Transact
491 | TokenType::Mcp
492 )
493}
494
495pub const AXONENDPOINT_TRANSPORT_VALUES: &[&str] = &["json", "sse", "ndjson"];
508
509pub const AXONENDPOINT_TRANSPORT_DIALECTS: &[&str] =
553 &["axon", "openai", "kimi", "glm", "anthropic"];
554
555pub const AXONENDPOINT_KEEPALIVE_VALUES: &[&str] = &["5s", "15s", "30s", "60s"];
557
558pub const AXONENDPOINT_METHOD_VALUES: &[&str] = &["GET", "POST", "PUT", "DELETE", "PATCH"];
564
565pub const AXONENDPOINT_BACKEND_VALUES: &[&str] = &[
586 "anthropic",
587 "auto",
588 "gemini",
589 "glm",
590 "kimi",
591 "ollama",
592 "openai",
593 "openrouter",
594 "stub",
595];
596
597#[inline]
598fn axonendpoint_is_valid_transport(s: &str) -> bool {
599 AXONENDPOINT_TRANSPORT_VALUES.iter().any(|&v| v == s)
600}
601
602#[inline]
603fn axonendpoint_is_valid_method(s: &str) -> bool {
604 AXONENDPOINT_METHOD_VALUES.iter().any(|&v| v == s)
605}
606
607#[inline]
608fn axonendpoint_is_valid_backend(s: &str) -> bool {
609 AXONENDPOINT_BACKEND_VALUES.iter().any(|&v| v == s)
610}
611
612#[inline]
613fn axonendpoint_is_valid_keepalive(s: &str) -> bool {
614 AXONENDPOINT_KEEPALIVE_VALUES.iter().any(|&v| v == s)
615}
616
617pub const AXONENDPOINT_QUERY_PARAM_TYPES: &[&str] =
632 &["Text", "Int", "Float", "Bool", "Uuid"];
633
634#[inline]
637pub(crate) fn axonendpoint_is_valid_query_param_type(s: &str) -> bool {
638 AXONENDPOINT_QUERY_PARAM_TYPES.iter().any(|&v| v == s)
639}
640
641pub(crate) fn extract_path_param_names(path: &str) -> Result<Vec<String>, String> {
668 let mut out: Vec<String> = Vec::new();
669 let bytes = path.as_bytes();
670 let mut i = 0;
671 while i < bytes.len() {
672 if bytes[i] != b'{' {
673 i += 1;
674 continue;
675 }
676 let start = i + 1;
679 let mut end = start;
680 while end < bytes.len() && bytes[end] != b'}' {
681 end += 1;
682 }
683 if end == bytes.len() {
684 break;
687 }
688 let raw = &path[start..end];
689 let valid = !raw.is_empty()
691 && raw.bytes().enumerate().all(|(idx, b)| {
692 if idx == 0 {
693 b.is_ascii_alphabetic() || b == b'_'
694 } else {
695 b.is_ascii_alphanumeric() || b == b'_'
696 }
697 });
698 if valid {
699 let name = raw.to_string();
700 if out.iter().any(|existing| existing == &name) {
701 return Err(name);
702 }
703 out.push(name);
704 }
705 i = end + 1;
706 }
707 Ok(out)
708}
709
710pub fn is_valid_capability_slug(slug: &str) -> bool {
724 if slug.is_empty() {
725 return false;
726 }
727 for segment in slug.split('.') {
728 if !is_valid_slug_segment(segment) {
729 return false;
730 }
731 }
732 true
733}
734
735fn is_valid_slug_segment(seg: &str) -> bool {
736 let mut chars = seg.chars();
737 let first = match chars.next() {
738 Some(c) => c,
739 None => return false,
740 };
741 if !first.is_ascii_lowercase() {
742 return false;
743 }
744 chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
745}
746
747#[cfg(test)]
757mod query_param_catalog_tests {
758 use super::{axonendpoint_is_valid_query_param_type, AXONENDPOINT_QUERY_PARAM_TYPES};
759
760 #[test]
761 fn accepts_every_catalog_entry() {
762 for ty in AXONENDPOINT_QUERY_PARAM_TYPES {
763 assert!(
764 axonendpoint_is_valid_query_param_type(ty),
765 "catalog entry `{ty}` must validate"
766 );
767 }
768 }
769
770 #[test]
771 fn rejects_off_catalog_types() {
772 for off in &[
773 "Timestamp", "Date",
775 "DateTime",
776 "List<Text>", "Jsonb", "Bytea",
779 "text", "TEXT",
781 "Number", "", " ", ] {
785 assert!(
786 !axonendpoint_is_valid_query_param_type(off),
787 "off-catalog `{off}` must reject"
788 );
789 }
790 }
791
792 #[test]
793 fn catalog_size_matches_design() {
794 assert_eq!(AXONENDPOINT_QUERY_PARAM_TYPES.len(), 5);
798 }
799}
800
801#[cfg(test)]
802mod query_param_parser_tests {
803 use crate::lexer::Lexer;
804 use crate::parser::Parser;
805
806 fn parse_endpoint_source(src: &str) -> Result<crate::ast::AxonEndpointDefinition, String> {
807 let tokens = Lexer::new(src, "test.axon")
808 .tokenize()
809 .map_err(|e| format!("lex: {}", e.message))?;
810 let mut parser = Parser::new(tokens);
811 let program = parser.parse().map_err(|e| format!("parse: {}", e.message))?;
812 program
813 .declarations
814 .into_iter()
815 .find_map(|d| match d {
816 crate::ast::Declaration::AxonEndpoint(e) => Some(e),
817 _ => None,
818 })
819 .ok_or_else(|| "no axonendpoint in program".to_string())
820 }
821
822 #[test]
823 fn endpoint_with_no_query_block_keeps_empty_vec() {
824 let src = r#"
825 axonendpoint write_secret {
826 method: POST
827 path: "/api/users"
828 body: SecretWriteRequest
829 execute: WriteSecret
830 }
831 "#;
832 let ep = parse_endpoint_source(src).expect("parses");
833 assert!(
834 ep.query_params.is_empty(),
835 "D5 — no `query:` block ⇒ empty query_params"
836 );
837 }
838
839 #[test]
840 fn single_query_param_required() {
841 let src = r#"
842 axonendpoint list_users {
843 method: GET
844 path: "/api/users"
845 query: { status: Text }
846 execute: ListUsers
847 }
848 "#;
849 let ep = parse_endpoint_source(src).expect("parses");
850 assert_eq!(ep.query_params.len(), 1);
851 assert_eq!(ep.query_params[0].name, "status");
852 assert_eq!(ep.query_params[0].type_expr.name, "Text");
853 assert!(!ep.query_params[0].type_expr.optional);
854 }
855
856 #[test]
857 fn optional_query_param_via_question_suffix() {
858 let src = r#"
859 axonendpoint list_users {
860 method: GET
861 path: "/api/users"
862 query: { limit: Int? }
863 execute: ListUsers
864 }
865 "#;
866 let ep = parse_endpoint_source(src).expect("parses");
867 assert_eq!(ep.query_params.len(), 1);
868 assert_eq!(ep.query_params[0].name, "limit");
869 assert_eq!(ep.query_params[0].type_expr.name, "Int");
870 assert!(
871 ep.query_params[0].type_expr.optional,
872 "`?` suffix sets optional"
873 );
874 }
875
876 #[test]
877 fn multiple_query_params_preserve_declaration_order() {
878 let src = r#"
879 axonendpoint search {
880 method: GET
881 path: "/api/search"
882 query: { q: Text, page: Int?, limit: Int?, exact: Bool? }
883 execute: Search
884 }
885 "#;
886 let ep = parse_endpoint_source(src).expect("parses");
887 let names: Vec<&str> = ep.query_params.iter().map(|f| f.name.as_str()).collect();
888 assert_eq!(names, vec!["q", "page", "limit", "exact"]);
889 let types: Vec<&str> = ep
890 .query_params
891 .iter()
892 .map(|f| f.type_expr.name.as_str())
893 .collect();
894 assert_eq!(types, vec!["Text", "Int", "Int", "Bool"]);
895 let optionals: Vec<bool> = ep
896 .query_params
897 .iter()
898 .map(|f| f.type_expr.optional)
899 .collect();
900 assert_eq!(optionals, vec![false, true, true, true]);
901 }
902
903 #[test]
904 fn duplicate_query_param_is_parse_error() {
905 let src = r#"
906 axonendpoint bad {
907 method: GET
908 path: "/api/x"
909 query: { name: Text, name: Int? }
910 execute: Bad
911 }
912 "#;
913 let err = parse_endpoint_source(src).expect_err("must fail");
914 assert!(
915 err.contains("duplicate query param 'name'"),
916 "error must name the duplicate. Got: {err}"
917 );
918 }
919
920 #[test]
921 fn off_catalog_type_with_smart_suggest_hint() {
922 let src = r#"
927 axonendpoint bad {
928 method: GET
929 path: "/api/x"
930 query: { value: Strng }
931 execute: Bad
932 }
933 "#;
934 let err = parse_endpoint_source(src).expect_err("must fail");
935 assert!(
936 err.contains("unsupported type 'Strng'"),
937 "error must name the bad type. Got: {err}"
938 );
939 assert!(
940 err.contains("Expected one of: Text | Int | Float | Bool | Uuid"),
941 "error must list the closed catalog. Got: {err}"
942 );
943 }
944
945 #[test]
946 fn close_typo_gets_did_you_mean_hint() {
947 let src = r#"
950 axonendpoint bad {
951 method: GET
952 path: "/api/x"
953 query: { value: Txt }
954 execute: Bad
955 }
956 "#;
957 let err = parse_endpoint_source(src).expect_err("must fail");
958 assert!(
959 err.contains("Did you mean") && err.contains("`Text`"),
960 "smart-suggest must hint `Text`. Got: {err}"
961 );
962 }
963
964 #[test]
965 fn every_catalog_type_parses_cleanly() {
966 for ty in &["Text", "Int", "Float", "Bool", "Uuid"] {
968 let src = format!(
969 r#"
970 axonendpoint x {{
971 method: GET
972 path: "/api/x"
973 query: {{ v: {ty} }}
974 execute: X
975 }}
976 "#
977 );
978 let ep = parse_endpoint_source(&src)
979 .unwrap_or_else(|e| panic!("`{ty}` should parse: {e}"));
980 assert_eq!(ep.query_params[0].type_expr.name, *ty);
981 }
982 }
983
984 #[test]
985 fn comma_optional_between_params() {
986 let src = r#"
990 axonendpoint x {
991 method: GET
992 path: "/api/x"
993 query: { a: Text b: Int? }
994 execute: X
995 }
996 "#;
997 let ep = parse_endpoint_source(src).expect("parses without commas");
998 assert_eq!(ep.query_params.len(), 2);
999 }
1000
1001 #[test]
1004 fn double_query_block_is_parse_error() {
1005 let src = r#"
1009 axonendpoint x {
1010 method: GET
1011 path: "/api/x"
1012 query: { a: Text }
1013 query: { b: Int? }
1014 execute: X
1015 }
1016 "#;
1017 let err = parse_endpoint_source(src).expect_err("must fail");
1018 assert!(
1019 err.contains("declares `query: { … }` more than once"),
1020 "error must call out the duplicate block. Got: {err}"
1021 );
1022 assert!(
1023 err.contains("combine all params into a single block"),
1024 "error must hint the canonical fix. Got: {err}"
1025 );
1026 }
1027
1028 #[test]
1029 fn optional_generic_type_is_parse_error_with_canonical_hint() {
1030 let src = r#"
1034 axonendpoint x {
1035 method: GET
1036 path: "/api/x"
1037 query: { value: Optional<Text> }
1038 execute: X
1039 }
1040 "#;
1041 let err = parse_endpoint_source(src).expect_err("must fail");
1042 assert!(
1043 err.contains("generic type `Optional<Text>`"),
1044 "error must name the generic type literally. Got: {err}"
1045 );
1046 assert!(
1047 err.contains("Use `Text?` (the `?` suffix)"),
1048 "error must hint the canonical `Text?` syntax. Got: {err}"
1049 );
1050 }
1051
1052 #[test]
1053 fn list_generic_type_is_parse_error_with_deferral_hint() {
1054 let src = r#"
1059 axonendpoint x {
1060 method: GET
1061 path: "/api/x"
1062 query: { tags: List<Text> }
1063 execute: X
1064 }
1065 "#;
1066 let err = parse_endpoint_source(src).expect_err("must fail");
1067 assert!(
1068 err.contains("generic type `List<Text>`"),
1069 "error must name the generic type. Got: {err}"
1070 );
1071 assert!(
1072 err.contains("Multi-value query params")
1073 && err.contains("honest-deferred"),
1074 "error must mention the multi-value deferral. Got: {err}"
1075 );
1076 }
1077
1078 #[test]
1079 fn other_generic_types_caught_generically() {
1080 let src = r#"
1084 axonendpoint x {
1085 method: GET
1086 path: "/api/x"
1087 query: { value: Stream<Int> }
1088 execute: X
1089 }
1090 "#;
1091 let err = parse_endpoint_source(src).expect_err("must fail");
1092 assert!(
1093 err.contains("generic type `Stream<Int>`"),
1094 "error must name the generic type. Got: {err}"
1095 );
1096 assert!(
1097 err.contains("Text | Int | Float | Bool | Uuid"),
1098 "error must list the closed catalog. Got: {err}"
1099 );
1100 }
1101
1102 #[test]
1103 fn uuid_optional_parses_cleanly() {
1104 let src = r#"
1107 axonendpoint find {
1108 method: GET
1109 path: "/api/x"
1110 query: { after: Uuid? }
1111 execute: Find
1112 }
1113 "#;
1114 let ep = parse_endpoint_source(src).expect("parses");
1115 assert_eq!(ep.query_params.len(), 1);
1116 assert_eq!(ep.query_params[0].name, "after");
1117 assert_eq!(ep.query_params[0].type_expr.name, "Uuid");
1118 assert!(ep.query_params[0].type_expr.optional);
1119 assert_eq!(ep.query_params[0].type_expr.generic_param, "");
1120 }
1121
1122 #[test]
1123 fn empty_query_block_yields_empty_vec() {
1124 let src = r#"
1128 axonendpoint x {
1129 method: GET
1130 path: "/api/x"
1131 query: { }
1132 execute: X
1133 }
1134 "#;
1135 let ep = parse_endpoint_source(src).expect("empty block parses");
1136 assert!(ep.query_params.is_empty());
1137 }
1138
1139 #[test]
1140 fn kivi_secret_write_path_plus_query() {
1141 let src = r#"
1145 axonendpoint write_secret {
1146 method: POST
1147 path: "/api/tenants/{tenant_id}/secrets/{secret_name}"
1148 query: { dry_run: Bool?, overwrite: Bool? }
1149 body: SecretWriteRequest
1150 execute: WriteSecret
1151 }
1152 "#;
1153 let ep = parse_endpoint_source(src).expect("parses");
1154 assert_eq!(ep.path_params, vec!["tenant_id", "secret_name"]);
1156 assert_eq!(ep.query_params.len(), 2);
1158 assert_eq!(ep.query_params[0].name, "dry_run");
1159 assert_eq!(ep.query_params[0].type_expr.name, "Bool");
1160 assert!(ep.query_params[0].type_expr.optional);
1161 assert_eq!(ep.query_params[1].name, "overwrite");
1162 assert_eq!(ep.body_type, "SecretWriteRequest");
1164 }
1165}
1166
1167#[cfg(test)]
1168mod path_param_extraction_tests {
1169 use super::extract_path_param_names;
1170
1171 #[test]
1172 fn empty_path_no_placeholders() {
1173 assert_eq!(extract_path_param_names("/api/users"), Ok(vec![]));
1174 assert_eq!(extract_path_param_names("/"), Ok(vec![]));
1175 assert_eq!(extract_path_param_names(""), Ok(vec![]));
1176 }
1177
1178 #[test]
1179 fn single_placeholder() {
1180 assert_eq!(
1181 extract_path_param_names("/api/users/{id}"),
1182 Ok(vec!["id".to_string()])
1183 );
1184 }
1185
1186 #[test]
1187 fn multiple_placeholders_in_declaration_order() {
1188 assert_eq!(
1189 extract_path_param_names(
1190 "/api/tenants/{tenant_id}/secrets/{secret_name}"
1191 ),
1192 Ok(vec![
1193 "tenant_id".to_string(),
1194 "secret_name".to_string(),
1195 ])
1196 );
1197 }
1198
1199 #[test]
1200 fn kivi_chat_history_path_pattern() {
1201 let names = extract_path_param_names(
1205 "/api/tenants/{tenant_id}/secrets/{secret_name}",
1206 );
1207 assert_eq!(
1208 names,
1209 Ok(vec![
1210 "tenant_id".to_string(),
1211 "secret_name".to_string(),
1212 ])
1213 );
1214 }
1215
1216 #[test]
1217 fn duplicate_placeholder_returns_err() {
1218 assert_eq!(
1219 extract_path_param_names("/api/users/{id}/posts/{id}"),
1220 Err("id".to_string())
1221 );
1222 }
1223
1224 #[test]
1225 fn underscore_and_numeric_in_name() {
1226 assert_eq!(
1227 extract_path_param_names("/api/{user_id}/items/{item_2}"),
1228 Ok(vec!["user_id".to_string(), "item_2".to_string()])
1229 );
1230 }
1231
1232 #[test]
1233 fn leading_underscore_accepted() {
1234 assert_eq!(
1240 extract_path_param_names("/api/{_internal}"),
1241 Ok(vec!["_internal".to_string()])
1242 );
1243 }
1244
1245 #[test]
1246 fn malformed_placeholder_silently_ignored() {
1247 assert_eq!(
1251 extract_path_param_names("/api/{not valid}"),
1252 Ok(vec![])
1253 );
1254 assert_eq!(extract_path_param_names("/api/{}"), Ok(vec![]));
1256 assert_eq!(
1258 extract_path_param_names("/api/{tenant id}/users/{id}"),
1259 Ok(vec!["id".to_string()])
1260 );
1261 }
1262
1263 #[test]
1264 fn unterminated_brace_returns_clean() {
1265 assert_eq!(extract_path_param_names("/api/{id"), Ok(vec![]));
1268 }
1269
1270 #[test]
1271 fn placeholders_at_path_boundaries() {
1272 assert_eq!(
1275 extract_path_param_names("{prefix}/api/users/{id}"),
1276 Ok(vec!["prefix".to_string(), "id".to_string()])
1277 );
1278 assert_eq!(
1279 extract_path_param_names("/api/{id}"),
1280 Ok(vec!["id".to_string()])
1281 );
1282 }
1283
1284 #[test]
1285 fn deduplication_detects_non_adjacent_duplicates() {
1286 assert_eq!(
1288 extract_path_param_names(
1289 "/api/orgs/{org_id}/teams/{team_id}/repos/{org_id}"
1290 ),
1291 Err("org_id".to_string())
1292 );
1293 }
1294
1295 #[test]
1296 fn never_panics_on_arbitrary_input() {
1297 for input in &[
1299 "{",
1300 "}",
1301 "{}",
1302 "{{}}",
1303 "{{{",
1304 "/api/{}/{id}",
1305 "////",
1306 "\u{1F4A1}", "\u{0000}", ] {
1309 let _ = extract_path_param_names(input); }
1311 }
1312}
1313
1314#[cfg(test)]
1315mod capability_slug_tests {
1316 use super::is_valid_capability_slug;
1317
1318 #[test]
1319 fn accepts_canonical_examples() {
1320 assert!(is_valid_capability_slug("admin"));
1321 assert!(is_valid_capability_slug("legal.read"));
1322 assert!(is_valid_capability_slug("hipaa.phi.read"));
1323 assert!(is_valid_capability_slug("bank.officer.senior"));
1324 assert!(is_valid_capability_slug("a"));
1325 assert!(is_valid_capability_slug("a_b"));
1326 assert!(is_valid_capability_slug("a1"));
1327 assert!(is_valid_capability_slug("a.b1_c"));
1328 }
1329
1330 #[test]
1331 fn rejects_empty_string() {
1332 assert!(!is_valid_capability_slug(""));
1333 }
1334
1335 #[test]
1336 fn rejects_uppercase() {
1337 assert!(!is_valid_capability_slug("Admin"));
1338 assert!(!is_valid_capability_slug("admin.READ"));
1339 }
1340
1341 #[test]
1342 fn rejects_digit_first() {
1343 assert!(!is_valid_capability_slug("1admin"));
1344 assert!(!is_valid_capability_slug("admin.1read"));
1345 }
1346
1347 #[test]
1348 fn rejects_hyphen() {
1349 assert!(!is_valid_capability_slug("bank-officer"));
1350 }
1351
1352 #[test]
1353 fn rejects_empty_segments() {
1354 assert!(!is_valid_capability_slug("bank..a"));
1355 assert!(!is_valid_capability_slug(".admin"));
1356 assert!(!is_valid_capability_slug("admin."));
1357 }
1358
1359 #[test]
1360 fn rejects_special_chars() {
1361 assert!(!is_valid_capability_slug("admin@read"));
1362 assert!(!is_valid_capability_slug("admin/read"));
1363 assert!(!is_valid_capability_slug("admin read"));
1364 }
1365}
1366
1367pub struct Parser {
1370 tokens: Vec<Token>,
1371 pos: usize,
1372 leading_trivia: Vec<Vec<Trivia>>,
1377 trailing_trivia: Vec<Vec<Trivia>>,
1381 last_let_value_kind: String,
1385 loop_depth: u32,
1390 source: Option<String>,
1396 filename: String,
1397}
1398
1399impl Parser {
1400 pub fn new(raw_tokens: Vec<Token>) -> Self {
1401 let mut effective: Vec<Token> = Vec::with_capacity(raw_tokens.len());
1411 let mut leading: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1412 let mut trailing: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1413
1414 let mut pending_leading: Vec<Trivia> = Vec::new();
1415 let mut last_effective_line: i64 = -1;
1416 for tok in raw_tokens {
1417 if is_comment_token(&tok.ttype) {
1418 let kind = token_to_trivia_kind(&tok.ttype)
1419 .expect("comment token must map to a trivia kind");
1420 let triv = Trivia {
1421 kind,
1422 text: tok.value,
1423 line: tok.line,
1424 column: tok.column,
1425 };
1426 if !effective.is_empty() && (tok.line as i64) == last_effective_line {
1427 trailing.last_mut().unwrap().push(triv);
1428 } else {
1429 pending_leading.push(triv);
1430 }
1431 } else {
1432 last_effective_line = tok.line as i64;
1433 effective.push(tok);
1434 leading.push(std::mem::take(&mut pending_leading));
1435 trailing.push(Vec::new());
1436 }
1437 }
1438
1439 Parser {
1440 tokens: effective,
1441 pos: 0,
1442 leading_trivia: leading,
1443 trailing_trivia: trailing,
1444 last_let_value_kind: "literal".to_string(),
1445 loop_depth: 0,
1446 source: None,
1447 filename: "<source>".to_string(),
1448 }
1449 }
1450
1451 #[must_use]
1463 pub fn with_source(mut self, source: &str, filename: &str) -> Self {
1464 self.source = Some(source.to_string());
1465 self.filename = filename.to_string();
1466 self
1467 }
1468
1469 pub fn parse(&mut self) -> Result<Program, ParseError> {
1472 let mut program = Program {
1473 declarations: Vec::new(),
1474 declaration_trivia: Vec::new(),
1475 loc: Loc { line: 1, column: 1 },
1476 };
1477 while !self.check(TokenType::Eof) {
1478 let start_pos = self.pos;
1484 let mut decl = match self.parse_declaration() {
1485 Ok(d) => d,
1486 Err(e) => return Err(self.attach_source_to_error(e)),
1487 };
1488 let end_pos = self.pos.saturating_sub(1);
1489 let leading = self
1490 .leading_trivia
1491 .get(start_pos)
1492 .cloned()
1493 .unwrap_or_default();
1494 let trailing = self
1495 .trailing_trivia
1496 .get(end_pos)
1497 .cloned()
1498 .unwrap_or_default();
1499 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1505 program.declarations.push(decl);
1506 program
1507 .declaration_trivia
1508 .push(DeclarationTrivia { leading, trailing });
1509 }
1510 Ok(program)
1511 }
1512
1513 pub fn parse_with_recovery(&mut self) -> ParseResult {
1546 let mut program = Program {
1547 declarations: Vec::new(),
1548 declaration_trivia: Vec::new(),
1549 loc: Loc { line: 1, column: 1 },
1550 };
1551 let mut errors: Vec<ParseError> = Vec::new();
1552
1553 while !self.check(TokenType::Eof) {
1554 let start_pos = self.pos;
1555 match self.parse_declaration() {
1556 Ok(mut decl) => {
1557 let end_pos = self.pos.saturating_sub(1);
1558 let leading = self
1559 .leading_trivia
1560 .get(start_pos)
1561 .cloned()
1562 .unwrap_or_default();
1563 let trailing = self
1564 .trailing_trivia
1565 .get(end_pos)
1566 .cloned()
1567 .unwrap_or_default();
1568 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1569 program.declarations.push(decl);
1570 program
1571 .declaration_trivia
1572 .push(DeclarationTrivia { leading, trailing });
1573 }
1574 Err(err) => {
1575 errors.push(self.attach_source_to_error(err));
1579 if self.pos == start_pos && !self.check(TokenType::Eof) {
1584 self.advance();
1585 }
1586 self.advance_to_sync_point();
1587 }
1588 }
1589 }
1590
1591 ParseResult { program, errors }
1592 }
1593
1594 fn attach_source_to_error(&self, err: ParseError) -> ParseError {
1599 match &self.source {
1600 Some(src) if err.line >= 1 => err.attach_source(src, &self.filename),
1601 _ => err,
1602 }
1603 }
1604
1605 fn advance_to_sync_point(&mut self) {
1610 let mut depth: i32 = 0;
1611 while !self.check(TokenType::Eof) {
1612 let tt = self.current().ttype.clone();
1613 if is_top_level_decl_kw_for_recovery(&tt) && depth <= 0 {
1617 return;
1618 }
1619 if matches!(tt, TokenType::LBrace) {
1620 depth += 1;
1621 } else if matches!(tt, TokenType::RBrace) {
1622 depth -= 1;
1623 }
1624 self.advance();
1625 }
1626 }
1627
1628 fn current(&self) -> &Token {
1631 if self.pos >= self.tokens.len() {
1632 self.tokens.last().unwrap() } else {
1634 &self.tokens[self.pos]
1635 }
1636 }
1637
1638 fn advance(&mut self) -> &Token {
1639 let idx = self.pos;
1640 if self.pos < self.tokens.len() {
1641 self.pos += 1;
1642 }
1643 &self.tokens[idx]
1644 }
1645
1646 fn check(&self, tt: TokenType) -> bool {
1647 self.current().ttype == tt
1648 }
1649
1650 fn consume(&mut self, expected: TokenType) -> Result<Token, ParseError> {
1651 let tok = self.current().clone();
1652 if tok.ttype != expected {
1653 return Err(ParseError {
1654 message: format!(
1655 "Expected {:?}, found {:?}('{}')",
1656 expected, tok.ttype, tok.value
1657 ),
1658 line: tok.line,
1659 column: tok.column,
1660 ..Default::default()
1661 });
1662 }
1663 self.pos += 1;
1664 Ok(tok)
1665 }
1666
1667 fn error(&self, message: &str) -> ParseError {
1669 let tok = self.current();
1670 ParseError { message: message.to_string(), line: tok.line, column: tok.column, ..Default::default() }
1671 }
1672
1673 fn consume_any_ident_or_kw(&mut self) -> Result<Token, ParseError> {
1675 let tok = self.current().clone();
1676 match tok.ttype {
1677 TokenType::Identifier
1678 | TokenType::Bool
1679 | TokenType::StringLit
1680 | TokenType::Integer
1681 | TokenType::Float => {
1682 self.pos += 1;
1683 Ok(tok)
1684 }
1685 _ => {
1686 if !tok.value.is_empty()
1688 && tok.value.chars().all(|c| c.is_alphanumeric() || c == '_')
1689 && tok.ttype != TokenType::Eof
1690 {
1691 self.pos += 1;
1692 Ok(tok)
1693 } else {
1694 Err(ParseError {
1695 message: format!(
1696 "Expected identifier or keyword value, found {:?}('{}')",
1697 tok.ttype, tok.value
1698 ),
1699 line: tok.line,
1700 column: tok.column,
1701 ..Default::default()
1702 })
1703 }
1704 }
1705 }
1706 }
1707
1708 fn consume_number(&mut self) -> Result<f64, ParseError> {
1709 let tok = self.current().clone();
1710 match tok.ttype {
1711 TokenType::Float | TokenType::Integer => {
1712 self.pos += 1;
1713 tok.value.parse::<f64>().map_err(|_| ParseError {
1714 message: format!("Invalid number '{}'", tok.value),
1715 line: tok.line,
1716 column: tok.column,
1717 ..Default::default()
1718 })
1719 }
1720 _ => Err(ParseError {
1721 message: format!("Expected number, found {:?}('{}')", tok.ttype, tok.value),
1722 line: tok.line,
1723 column: tok.column,
1724 ..Default::default()
1725 }),
1726 }
1727 }
1728
1729 fn parse_bool(&mut self) -> Result<bool, ParseError> {
1730 let tok = self.consume(TokenType::Bool)?;
1731 Ok(tok.value == "true")
1732 }
1733
1734 fn loc_of(&self, tok: &Token) -> Loc {
1735 Loc {
1736 line: tok.line,
1737 column: tok.column,
1738 }
1739 }
1740
1741 fn check_comparison(&self) -> bool {
1742 matches!(
1743 self.current().ttype,
1744 TokenType::Lt
1745 | TokenType::Gt
1746 | TokenType::Lte
1747 | TokenType::Gte
1748 | TokenType::Eq
1749 | TokenType::Neq
1750 )
1751 }
1752
1753 fn check_run_modifier(&self) -> bool {
1754 matches!(
1755 self.current().ttype,
1756 TokenType::As
1757 | TokenType::Within
1758 | TokenType::ConstrainedBy
1759 | TokenType::OnFailure
1760 | TokenType::OutputTo
1761 | TokenType::Effort
1762 )
1763 }
1764
1765 fn parse_string_list(&mut self) -> Result<Vec<String>, ParseError> {
1768 self.consume(TokenType::LBracket)?;
1769 let mut items = Vec::new();
1770 items.push(self.consume(TokenType::StringLit)?.value);
1771 while self.check(TokenType::Comma) {
1772 self.advance();
1773 items.push(self.consume(TokenType::StringLit)?.value);
1774 }
1775 self.consume(TokenType::RBracket)?;
1776 Ok(items)
1777 }
1778
1779 fn parse_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1780 let mut names = Vec::new();
1781 names.push(self.consume(TokenType::Identifier)?.value);
1782 while self.check(TokenType::Comma) {
1783 self.advance();
1784 names.push(self.consume(TokenType::Identifier)?.value);
1785 }
1786 Ok(names)
1787 }
1788
1789 fn parse_bracketed_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1790 self.consume(TokenType::LBracket)?;
1791 let items = self.parse_extended_identifier_list()?;
1792 self.consume(TokenType::RBracket)?;
1793 Ok(items)
1794 }
1795
1796 fn parse_extended_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1797 let mut items = Vec::new();
1798 items.push(self.consume_any_ident_or_kw()?.value);
1799 while self.check(TokenType::Comma) {
1800 self.advance();
1801 items.push(self.consume_any_ident_or_kw()?.value);
1802 }
1803 Ok(items)
1804 }
1805
1806 fn parse_dotted_identifier(&mut self) -> Result<String, ParseError> {
1807 let mut parts = vec![self.consume_any_ident_or_kw()?.value];
1808 while self.check(TokenType::Dot) {
1809 self.advance();
1810 parts.push(self.consume_any_ident_or_kw()?.value);
1811 }
1812 Ok(parts.join("."))
1813 }
1814
1815 fn parse_expression_string(&mut self) -> Result<String, ParseError> {
1816 if self.check(TokenType::LBracket) {
1817 let items = self.parse_bracketed_dot_identifiers()?;
1818 return Ok(format!("[{}]", items.join(", ")));
1819 }
1820 self.parse_dotted_identifier()
1821 }
1822
1823 fn parse_bracketed_dot_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1824 self.consume(TokenType::LBracket)?;
1825 let mut items = vec![self.parse_dotted_identifier()?];
1826 while self.check(TokenType::Comma) {
1827 self.advance();
1828 items.push(self.parse_dotted_identifier()?);
1829 }
1830 self.consume(TokenType::RBracket)?;
1831 Ok(items)
1832 }
1833
1834 fn parse_argument_list(&mut self) -> Result<Vec<String>, ParseError> {
1835 let mut args = Vec::new();
1836 while !self.check(TokenType::RParen) {
1837 let tok = self.current().clone();
1838 match tok.ttype {
1839 TokenType::StringLit | TokenType::Integer | TokenType::Float => {
1840 self.advance();
1841 args.push(tok.value);
1842 }
1843 TokenType::Identifier => {
1844 self.advance();
1845 let mut val = tok.value;
1846 if self.check(TokenType::Dot) {
1847 self.advance();
1848 val.push('.');
1849 val.push_str(&self.consume_any_ident_or_kw()?.value);
1850 }
1851 args.push(val);
1852 }
1853 _ => {
1854 self.advance();
1855 let key = tok.value;
1856 if self.check(TokenType::Colon) {
1857 self.advance();
1858 let v = self.advance().value.clone();
1859 args.push(format!("{key}:{v}"));
1860 } else {
1861 args.push(key);
1862 }
1863 }
1864 }
1865 if self.check(TokenType::Comma) {
1866 self.advance();
1867 }
1868 }
1869 Ok(args)
1870 }
1871
1872 fn skip_value(&mut self) {
1874 match self.current().ttype {
1875 TokenType::LBracket => {
1876 self.advance();
1877 let mut depth = 1u32;
1878 while depth > 0 && !self.check(TokenType::Eof) {
1879 if self.check(TokenType::LBracket) {
1880 depth += 1;
1881 } else if self.check(TokenType::RBracket) {
1882 depth -= 1;
1883 }
1884 self.advance();
1885 }
1886 }
1887 TokenType::LBrace => {
1888 self.advance();
1889 let mut depth = 1u32;
1890 while depth > 0 && !self.check(TokenType::Eof) {
1891 if self.check(TokenType::LBrace) {
1892 depth += 1;
1893 } else if self.check(TokenType::RBrace) {
1894 depth -= 1;
1895 }
1896 self.advance();
1897 }
1898 }
1899 TokenType::Lt => {
1900 self.advance();
1902 let mut depth = 1u32;
1903 while depth > 0 && !self.check(TokenType::Eof) {
1904 if self.check(TokenType::Lt) {
1905 depth += 1;
1906 } else if self.check(TokenType::Gt) {
1907 depth -= 1;
1908 }
1909 self.advance();
1910 }
1911 }
1912 _ => {
1913 self.advance();
1914 while self.check(TokenType::Dot) {
1915 self.advance();
1916 self.advance();
1917 }
1918 }
1919 }
1920 }
1921
1922 fn skip_braced_block(&mut self) -> Result<(), ParseError> {
1924 self.consume(TokenType::LBrace)?;
1925 let mut depth = 1u32;
1926 while depth > 0 {
1927 if self.check(TokenType::Eof) {
1928 let tok = self.current();
1929 return Err(ParseError {
1930 message: "Unterminated block — expected '}'".to_string(),
1931 line: tok.line,
1932 column: tok.column,
1933 ..Default::default()
1934 });
1935 }
1936 if self.check(TokenType::LBrace) {
1937 depth += 1;
1938 } else if self.check(TokenType::RBrace) {
1939 depth -= 1;
1940 }
1941 self.advance();
1942 }
1943 Ok(())
1944 }
1945
1946 fn at_declaration_start(&self) -> bool {
1947 is_declaration_keyword(&self.current().ttype) || self.check(TokenType::Eof)
1948 }
1949
1950 fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
1953 let tok = self.current().clone();
1954
1955 match tok.ttype {
1956 TokenType::Import => self.parse_import().map(Declaration::Import),
1957 TokenType::Persona => self.parse_persona().map(Declaration::Persona),
1958 TokenType::Context => self.parse_context().map(Declaration::Context),
1959 TokenType::Anchor => self.parse_anchor().map(Declaration::Anchor),
1960 TokenType::Memory => self.parse_memory().map(Declaration::Memory),
1961 TokenType::Tool => self.parse_tool().map(Declaration::Tool),
1962 TokenType::Type => self.parse_type_def().map(Declaration::Type),
1963 TokenType::Flow => self.parse_flow().map(Declaration::Flow),
1964 TokenType::Intent => self.parse_intent().map(Declaration::Intent),
1965 TokenType::Run => self.parse_run().map(Declaration::Run),
1966 TokenType::Let => self.parse_let().map(Declaration::Let),
1967 TokenType::Know | TokenType::Believe | TokenType::Speculate | TokenType::Doubt => {
1968 self.parse_epistemic_block().map(Declaration::Epistemic)
1969 }
1970 TokenType::Lambda => self.parse_lambda_data().map(Declaration::LambdaData),
1971
1972 TokenType::Agent => self.parse_agent().map(Declaration::Agent),
1974 TokenType::Shield => self.parse_shield().map(Declaration::Shield),
1975 TokenType::Pix => self.parse_pix().map(Declaration::Pix),
1976 TokenType::Ledger => self.parse_ledger().map(Declaration::Ledger),
1977 TokenType::Psyche => self.parse_psyche().map(Declaration::Psyche),
1978 TokenType::Corpus => self.parse_corpus().map(Declaration::Corpus),
1979 TokenType::Dataspace => self.parse_dataspace().map(Declaration::Dataspace),
1980 TokenType::Ots => self.parse_ots().map(Declaration::Ots),
1981 TokenType::Mandate => self.parse_mandate().map(Declaration::Mandate),
1982 TokenType::Compute => self.parse_compute().map(Declaration::Compute),
1983 TokenType::Daemon => self.parse_daemon().map(Declaration::Daemon),
1984 TokenType::Extension => self.parse_extension().map(Declaration::Extension),
1985 TokenType::AxonStore => self.parse_axonstore().map(Declaration::AxonStore),
1986 TokenType::AxonEndpoint => self.parse_axonendpoint().map(Declaration::AxonEndpoint),
1987
1988 TokenType::Resource => self.parse_resource().map(Declaration::Resource),
1990 TokenType::Fabric => self.parse_fabric().map(Declaration::Fabric),
1991 TokenType::Manifest => self.parse_manifest().map(Declaration::Manifest),
1992 TokenType::Observe => self.parse_observe().map(Declaration::Observe),
1993
1994 TokenType::Reconcile => self.parse_reconcile().map(Declaration::Reconcile),
1996 TokenType::Lease => self.parse_lease().map(Declaration::Lease),
1997 TokenType::Ensemble => self.parse_ensemble().map(Declaration::Ensemble),
1998
1999 TokenType::Session => self.parse_session_definition().map(Declaration::Session),
2001 TokenType::Topology => self.parse_topology().map(Declaration::Topology),
2002
2003 TokenType::Socket => self.parse_socket().map(Declaration::Socket),
2005
2006 TokenType::Observable => self.parse_observable().map(Declaration::Observable),
2008
2009 TokenType::Witness => self.parse_witness().map(Declaration::Witness),
2011
2012 TokenType::Immune => self.parse_immune().map(Declaration::Immune),
2014 TokenType::Reflex => self.parse_reflex().map(Declaration::Reflex),
2015 TokenType::Heal => self.parse_heal().map(Declaration::Heal),
2016
2017 TokenType::Component => self.parse_component().map(Declaration::Component),
2019 TokenType::View => self.parse_view().map(Declaration::View),
2020
2021 TokenType::Channel => self.parse_channel().map(Declaration::Channel),
2023
2024 TokenType::Ingest
2027 | TokenType::Persist
2028 | TokenType::Retrieve
2029 | TokenType::Mutate
2030 | TokenType::Purge
2031 | TokenType::Transact => self.parse_generic_declaration(),
2032
2033 TokenType::Mcp => self.parse_generic_declaration(),
2035
2036 _ => {
2037 let hint = crate::smart_suggest::suggest_for(
2041 &tok.value,
2042 crate::smart_suggest::TOP_LEVEL_KEYWORD_NAMES,
2043 );
2044 let base = format!(
2045 "Unexpected token at top level: '{}' — expected declaration \
2046 (persona, context, anchor, flow, run, ...)",
2047 tok.value
2048 );
2049 let message = if hint.is_empty() {
2050 base
2051 } else {
2052 format!("{base}. {hint}")
2053 };
2054 Err(ParseError {
2055 message,
2056 line: tok.line,
2057 column: tok.column,
2058 ..Default::default()
2059 })
2060 }
2061 }
2062 }
2063
2064 fn parse_import(&mut self) -> Result<ImportNode, ParseError> {
2067 let tok = self.consume(TokenType::Import)?;
2068 let loc = self.loc_of(&tok);
2069
2070 let mut path_parts = Vec::new();
2071
2072 if self.check(TokenType::At) {
2074 self.advance();
2075 let first = self.consume(TokenType::Identifier)?;
2076 path_parts.push(format!("@{}", first.value));
2077 } else {
2078 let first = self.consume(TokenType::Identifier)?;
2079 path_parts.push(first.value);
2080 }
2081
2082 while self.check(TokenType::Dot) {
2083 self.advance();
2084 if self.check(TokenType::LBrace) {
2085 break;
2086 }
2087 let part = self.consume(TokenType::Identifier)?;
2088 path_parts.push(part.value);
2089 }
2090
2091 let mut names = Vec::new();
2092 if self.check(TokenType::LBrace) {
2093 self.advance();
2094 names = self.parse_identifier_list()?;
2095 self.consume(TokenType::RBrace)?;
2096 }
2097
2098 if self.current().value == "with" {
2100 self.advance();
2101 self.advance(); if self.check(TokenType::LBrace) {
2103 self.skip_braced_block()?;
2104 }
2105 }
2106
2107 Ok(ImportNode {
2108 module_path: path_parts,
2109 names,
2110 loc,
2111 leading_trivia: Vec::new(),
2112 trailing_trivia: Vec::new(),
2113 })
2114 }
2115
2116 fn parse_persona(&mut self) -> Result<PersonaDefinition, ParseError> {
2119 let tok = self.consume(TokenType::Persona)?;
2120 let loc = self.loc_of(&tok);
2121 let name = self.consume(TokenType::Identifier)?.value;
2122 self.consume(TokenType::LBrace)?;
2123
2124 let mut node = PersonaDefinition {
2125 name,
2126 domain: Vec::new(),
2127 tone: String::new(),
2128 confidence_threshold: None,
2129 cite_sources: None,
2130 refuse_if: Vec::new(),
2131 language: String::new(),
2132 description: String::new(),
2133 loc,
2134 leading_trivia: Vec::new(),
2135 trailing_trivia: Vec::new(),
2136 };
2137
2138 while !self.check(TokenType::RBrace) {
2139 let field_name = self.current().value.clone();
2140 self.advance();
2141 self.consume(TokenType::Colon)?;
2142
2143 match field_name.as_str() {
2144 "domain" => node.domain = self.parse_string_list()?,
2145 "tone" => node.tone = self.consume_any_ident_or_kw()?.value,
2146 "confidence_threshold" => node.confidence_threshold = Some(self.consume_number()?),
2147 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2148 "refuse_if" => node.refuse_if = self.parse_bracketed_identifiers()?,
2149 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2150 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2151 _ => self.skip_value(),
2152 }
2153 }
2154 self.consume(TokenType::RBrace)?;
2155 Ok(node)
2156 }
2157
2158 fn parse_context(&mut self) -> Result<ContextDefinition, ParseError> {
2161 let tok = self.consume(TokenType::Context)?;
2162 let loc = self.loc_of(&tok);
2163 let name = self.consume(TokenType::Identifier)?.value;
2164 self.consume(TokenType::LBrace)?;
2165
2166 let mut node = ContextDefinition {
2167 name,
2168 memory_scope: String::new(),
2169 language: String::new(),
2170 depth: String::new(),
2171 max_tokens: None,
2172 temperature: None,
2173 cite_sources: None,
2174 loc,
2175 leading_trivia: Vec::new(),
2176 trailing_trivia: Vec::new(),
2177 };
2178
2179 while !self.check(TokenType::RBrace) {
2180 let field_name = self.current().value.clone();
2181 self.advance();
2182 self.consume(TokenType::Colon)?;
2183
2184 match field_name.as_str() {
2185 "memory" => node.memory_scope = self.consume_any_ident_or_kw()?.value,
2186 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2187 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
2188 "max_tokens" => {
2189 node.max_tokens = Some(
2190 self.consume(TokenType::Integer)?
2191 .value
2192 .parse::<i64>()
2193 .unwrap_or(0),
2194 )
2195 }
2196 "temperature" => node.temperature = Some(self.consume_number()?),
2197 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2198 _ => self.skip_value(),
2199 }
2200 }
2201 self.consume(TokenType::RBrace)?;
2202 Ok(node)
2203 }
2204
2205 fn parse_anchor(&mut self) -> Result<AnchorConstraint, ParseError> {
2208 let tok = self.consume(TokenType::Anchor)?;
2209 let loc = self.loc_of(&tok);
2210 let name = self.consume(TokenType::Identifier)?.value;
2211 self.consume(TokenType::LBrace)?;
2212
2213 let mut node = AnchorConstraint {
2214 name,
2215 require: String::new(),
2216 reject: Vec::new(),
2217 enforce: String::new(),
2218 description: String::new(),
2219 confidence_floor: None,
2220 unknown_response: String::new(),
2221 on_violation: String::new(),
2222 on_violation_target: String::new(),
2223 loc,
2224 leading_trivia: Vec::new(),
2225 trailing_trivia: Vec::new(),
2226 };
2227
2228 while !self.check(TokenType::RBrace) {
2229 let field_name = self.current().value.clone();
2230 self.advance();
2231 self.consume(TokenType::Colon)?;
2232
2233 match field_name.as_str() {
2234 "require" => node.require = self.consume_any_ident_or_kw()?.value,
2235 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2236 "reject" => node.reject = self.parse_bracketed_identifiers()?,
2237 "enforce" => node.enforce = self.consume_any_ident_or_kw()?.value,
2238 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
2239 "unknown_response" => {
2240 node.unknown_response = self.consume(TokenType::StringLit)?.value
2241 }
2242 "on_violation" => {
2243 let action = self.consume_any_ident_or_kw()?.value;
2245 node.on_violation = action.clone();
2246 if action == "raise" || action == "fallback" {
2247 node.on_violation_target = self.consume_any_ident_or_kw()?.value;
2248 }
2249 }
2250 _ => self.skip_value(),
2251 }
2252 }
2253 self.consume(TokenType::RBrace)?;
2254 Ok(node)
2255 }
2256
2257 fn parse_memory(&mut self) -> Result<MemoryDefinition, ParseError> {
2260 let tok = self.consume(TokenType::Memory)?;
2261 let loc = self.loc_of(&tok);
2262 let name = self.consume(TokenType::Identifier)?.value;
2263 self.consume(TokenType::LBrace)?;
2264
2265 let mut node = MemoryDefinition {
2266 name,
2267 store: String::new(),
2268 backend: String::new(),
2269 retrieval: String::new(),
2270 decay: String::new(),
2271 loc,
2272 leading_trivia: Vec::new(),
2273 trailing_trivia: Vec::new(),
2274 };
2275
2276 while !self.check(TokenType::RBrace) {
2277 let field_name = self.current().value.clone();
2278 self.advance();
2279 self.consume(TokenType::Colon)?;
2280
2281 match field_name.as_str() {
2282 "store" => node.store = self.consume_any_ident_or_kw()?.value,
2283 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
2284 "retrieval" => node.retrieval = self.consume_any_ident_or_kw()?.value,
2285 "decay" => {
2286 if self.check(TokenType::Duration) {
2287 node.decay = self.advance().value.clone();
2288 } else {
2289 node.decay = self.consume_any_ident_or_kw()?.value;
2290 }
2291 }
2292 _ => self.skip_value(),
2293 }
2294 }
2295 self.consume(TokenType::RBrace)?;
2296 Ok(node)
2297 }
2298
2299 fn parse_tool(&mut self) -> Result<ToolDefinition, ParseError> {
2302 let tok = self.consume(TokenType::Tool)?;
2303 let loc = self.loc_of(&tok);
2304 let name = self.consume(TokenType::Identifier)?.value;
2305 self.consume(TokenType::LBrace)?;
2306
2307 let mut node = ToolDefinition {
2308 name,
2309 provider: String::new(),
2310 max_results: None,
2311 filter_expr: String::new(),
2312 timeout: String::new(),
2313 runtime: String::new(),
2314 sandbox: None,
2315 effects: None,
2316 parameters: Vec::new(),
2317 output_type: None,
2318 loc,
2319 leading_trivia: Vec::new(),
2320 trailing_trivia: Vec::new(),
2321 };
2322
2323 while !self.check(TokenType::RBrace) {
2324 let field_name = self.current().value.clone();
2325 self.advance();
2326 self.consume(TokenType::Colon)?;
2327
2328 match field_name.as_str() {
2329 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
2330 "max_results" => {
2331 node.max_results = Some(
2332 self.consume(TokenType::Integer)?
2333 .value
2334 .parse::<i64>()
2335 .unwrap_or(0),
2336 )
2337 }
2338 "filter" => node.filter_expr = self.parse_filter_expression()?,
2339 "timeout" => node.timeout = self.consume(TokenType::Duration)?.value,
2340 "runtime" => node.runtime = self.consume_any_ident_or_kw()?.value,
2341 "sandbox" => node.sandbox = Some(self.parse_bool()?),
2342 "effects" => node.effects = Some(self.parse_effect_row()?),
2343 "parameters" => node.parameters = self.parse_tool_param_schema()?,
2345 "output_type" => node.output_type = Some(self.parse_output_type_string()?),
2346 _ => self.skip_value(),
2347 }
2348 }
2349 self.consume(TokenType::RBrace)?;
2350 Ok(node)
2351 }
2352
2353 fn parse_tool_param_schema(&mut self) -> Result<Vec<Parameter>, ParseError> {
2359 self.consume(TokenType::LBrace)?;
2360 let mut params = Vec::new();
2361 while !self.check(TokenType::RBrace) {
2362 let name = self.consume_any_ident_or_kw()?;
2366 let ploc = self.loc_of(&name);
2367 self.consume(TokenType::Colon)?;
2368 let type_expr = self.parse_type_expr()?;
2369 params.push(Parameter {
2370 name: name.value,
2371 type_expr,
2372 loc: ploc,
2373 });
2374 if self.check(TokenType::Comma) {
2375 self.advance();
2376 } else {
2377 break;
2378 }
2379 }
2380 self.consume(TokenType::RBrace)?;
2381 Ok(params)
2382 }
2383
2384 fn parse_filter_expression(&mut self) -> Result<String, ParseError> {
2385 let name = self.consume_any_ident_or_kw()?.value;
2386 if self.check(TokenType::LParen) {
2387 self.advance();
2388 let mut parts = vec![name, "(".to_string()];
2389 while !self.check(TokenType::RParen) {
2390 parts.push(self.advance().value.clone());
2391 }
2392 self.consume(TokenType::RParen)?;
2393 parts.push(")".to_string());
2394 Ok(parts.join(""))
2395 } else {
2396 Ok(name)
2397 }
2398 }
2399
2400 fn parse_effect_row(&mut self) -> Result<EffectRow, ParseError> {
2401 let tok = self.consume(TokenType::Lt)?;
2402 let loc = self.loc_of(&tok);
2403 let mut effects = Vec::new();
2404 let mut epistemic_level = String::new();
2405
2406 while !self.check(TokenType::Gt) {
2407 let name = self.consume_any_ident_or_kw()?.value;
2408 if self.check(TokenType::Colon) {
2409 self.advance();
2410 let level = self.parse_qualifier_value()?;
2427 if name == "epistemic" {
2428 epistemic_level = level;
2429 } else {
2430 effects.push(format!("{name}:{level}"));
2431 }
2432 } else {
2433 effects.push(name);
2434 }
2435 if self.check(TokenType::Comma) {
2436 self.advance();
2437 }
2438 }
2439 self.consume(TokenType::Gt)?;
2440
2441 Ok(EffectRow {
2442 effects,
2443 epistemic_level,
2444 loc,
2445 })
2446 }
2447
2448 fn parse_qualifier_value(&mut self) -> Result<String, ParseError> {
2457 let mut buf = self.consume_dotted_slug_segment()?;
2458 loop {
2459 let sep = if self.check(TokenType::Dot) {
2460 '.'
2461 } else if self.check(TokenType::Colon) {
2462 ':'
2463 } else {
2464 break;
2465 };
2466 self.advance();
2467 let part = self.consume_dotted_slug_segment()?;
2468 buf.push(sep);
2469 buf.push_str(&part);
2470 }
2471 Ok(buf)
2472 }
2473
2474 fn consume_dotted_slug_segment(&mut self) -> Result<String, ParseError> {
2486 let first = self.consume_any_ident_or_kw()?;
2487 let mut buf = first.value.clone();
2488 let mut next_line = first.line;
2489 let mut next_col = first.column + first.value.chars().count() as u32;
2490 loop {
2491 let cur = self.current();
2492 let is_segment_token = matches!(cur.ttype, TokenType::Identifier | TokenType::Integer,);
2493 if !is_segment_token {
2494 break;
2495 }
2496 if cur.line != next_line || cur.column != next_col {
2497 break;
2498 }
2499 buf.push_str(&cur.value);
2500 next_col = cur.column + cur.value.chars().count() as u32;
2501 next_line = cur.line;
2502 self.pos += 1;
2503 }
2504 Ok(buf)
2505 }
2506
2507 fn parse_type_def(&mut self) -> Result<TypeDefinition, ParseError> {
2510 let tok = self.consume(TokenType::Type)?;
2511 let loc = self.loc_of(&tok);
2512 let name = self.consume(TokenType::Identifier)?.value;
2513
2514 let mut node = TypeDefinition {
2515 name,
2516 fields: Vec::new(),
2517 range_constraint: None,
2518 where_clause: None,
2519 compliance: Vec::new(),
2520 loc: loc.clone(),
2521 leading_trivia: Vec::new(),
2522 trailing_trivia: Vec::new(),
2523 };
2524
2525 if self.check(TokenType::LParen) {
2527 self.advance();
2528 let min_val = self.consume_number()?;
2529 self.consume(TokenType::DotDot)?;
2530 let max_val = self.consume_number()?;
2531 self.consume(TokenType::RParen)?;
2532 node.range_constraint = Some(RangeConstraint {
2533 min_value: min_val,
2534 max_value: max_val,
2535 loc: loc.clone(),
2536 });
2537 }
2538
2539 if self.check(TokenType::Where) {
2541 self.advance();
2542 let mut expr_parts = Vec::new();
2543 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
2544 if self.check(TokenType::Eof) {
2545 break;
2546 }
2547 expr_parts.push(self.advance().value.clone());
2548 }
2549 node.where_clause = Some(WhereClause {
2550 expression: expr_parts.join(" "),
2551 loc: loc.clone(),
2552 });
2553 }
2554
2555 if self.check(TokenType::Identifier) && self.current().value == "compliance" {
2558 self.advance();
2559 node.compliance = self.parse_bracketed_identifiers()?;
2560 }
2561
2562 if self.check(TokenType::LBrace) {
2564 self.advance();
2565 while !self.check(TokenType::RBrace) {
2566 let field_name = self.consume(TokenType::Identifier)?;
2567 let field_loc = self.loc_of(&field_name);
2568 self.consume(TokenType::Colon)?;
2569 let type_expr = self.parse_type_expr()?;
2570 node.fields.push(TypeField {
2571 name: field_name.value,
2572 type_expr,
2573 loc: field_loc,
2574 });
2575 if self.check(TokenType::Comma) {
2576 self.advance();
2577 }
2578 }
2579 self.consume(TokenType::RBrace)?;
2580 }
2581
2582 Ok(node)
2583 }
2584
2585 fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
2586 let name_tok = self.consume(TokenType::Identifier)?;
2587 let loc = self.loc_of(&name_tok);
2588 let mut generic_param = String::new();
2589 let mut optional = false;
2590
2591 if self.check(TokenType::Lt) {
2592 self.advance();
2593 let inner = self.parse_type_expr()?;
2603 generic_param = if inner.generic_param.is_empty() {
2604 inner.name
2605 } else {
2606 format!("{}<{}>", inner.name, inner.generic_param)
2607 };
2608 self.consume(TokenType::Gt)?;
2609 }
2610 if self.check(TokenType::LBracket) {
2614 self.advance();
2615 if matches!(self.current().ttype, TokenType::Integer | TokenType::Float) {
2616 generic_param = self.advance().value.clone();
2617 } else {
2618 let inner = self.parse_type_expr()?;
2619 generic_param = if inner.generic_param.is_empty() {
2620 inner.name
2621 } else {
2622 format!("{}[{}]", inner.name, inner.generic_param)
2623 };
2624 }
2625 self.consume(TokenType::RBracket)?;
2626 }
2627 if self.check(TokenType::Question) {
2628 self.advance();
2629 optional = true;
2630 }
2631
2632 Ok(TypeExpr {
2633 name: name_tok.value,
2634 generic_param,
2635 optional,
2636 loc,
2637 })
2638 }
2639
2640 fn parse_output_type_string(&mut self) -> Result<String, ParseError> {
2665 let expr = self.parse_type_expr()?;
2666 let mut s = expr.name;
2667 if !expr.generic_param.is_empty() {
2668 s.push('<');
2669 s.push_str(&expr.generic_param);
2670 s.push('>');
2671 }
2672 if expr.optional {
2673 s.push('?');
2674 }
2675 Ok(s)
2676 }
2677
2678 fn parse_flow(&mut self) -> Result<FlowDefinition, ParseError> {
2681 let tok = self.consume(TokenType::Flow)?;
2682 let loc = self.loc_of(&tok);
2683 let name = self.consume(TokenType::Identifier)?.value;
2684
2685 self.consume(TokenType::LParen)?;
2686 let mut parameters = Vec::new();
2687 if !self.check(TokenType::RParen) {
2688 parameters = self.parse_param_list()?;
2689 }
2690 self.consume(TokenType::RParen)?;
2691
2692 let mut return_type = None;
2693 if self.check(TokenType::Arrow) {
2694 self.advance();
2695 return_type = Some(self.parse_type_expr()?);
2696 }
2697
2698 self.consume(TokenType::LBrace)?;
2699 let mut body = Vec::new();
2700 while !self.check(TokenType::RBrace) {
2701 body.push(self.parse_flow_step()?);
2702 }
2703 self.consume(TokenType::RBrace)?;
2704
2705 Ok(FlowDefinition {
2706 name,
2707 parameters,
2708 return_type,
2709 body,
2710 loc,
2711 leading_trivia: Vec::new(),
2712 trailing_trivia: Vec::new(),
2713 })
2714 }
2715
2716 fn parse_param_list(&mut self) -> Result<Vec<Parameter>, ParseError> {
2717 let mut params = Vec::new();
2718
2719 let name = self.consume(TokenType::Identifier)?;
2720 let ploc = self.loc_of(&name);
2721 self.consume(TokenType::Colon)?;
2722 let type_expr = self.parse_type_expr()?;
2723 params.push(Parameter {
2724 name: name.value,
2725 type_expr,
2726 loc: ploc,
2727 });
2728
2729 while self.check(TokenType::Comma) {
2730 self.advance();
2731 let name = self.consume(TokenType::Identifier)?;
2732 let ploc = self.loc_of(&name);
2733 self.consume(TokenType::Colon)?;
2734 let type_expr = self.parse_type_expr()?;
2735 params.push(Parameter {
2736 name: name.value,
2737 type_expr,
2738 loc: ploc,
2739 });
2740 }
2741 Ok(params)
2742 }
2743
2744 fn parse_flow_step(&mut self) -> Result<FlowStep, ParseError> {
2747 let tok = self.current().clone();
2748
2749 match tok.ttype {
2750 TokenType::Step => self.parse_step().map(FlowStep::Step),
2751 TokenType::If => self.parse_if().map(FlowStep::If),
2752 TokenType::For => self.parse_for_in().map(FlowStep::ForIn),
2753 TokenType::Let => self.parse_let().map(FlowStep::Let),
2754 TokenType::Return => self.parse_return().map(FlowStep::Return),
2755 TokenType::Break => self.parse_break().map(FlowStep::Break),
2756 TokenType::Continue => self.parse_continue().map(FlowStep::Continue),
2757 TokenType::Lambda => self.parse_lambda_data_apply().map(FlowStep::LambdaDataApply),
2758
2759 TokenType::Probe => self.parse_flow_step_simple("probe").map(|l| FlowStep::Probe(ProbeStep { target: l.1, loc: l.0 })),
2761 TokenType::Reason => self.parse_flow_step_simple("reason").map(|l| FlowStep::Reason(ReasonStep { strategy: String::new(), target: l.1, loc: l.0 })),
2762 TokenType::Validate => self.parse_flow_step_simple("validate").map(|l| FlowStep::Validate(ValidateStep { target: l.1, rule: String::new(), loc: l.0 })),
2763 TokenType::Refine => self.parse_flow_step_simple("refine").map(|l| FlowStep::Refine(RefineStep { target: l.1, strategy: String::new(), loc: l.0 })),
2764 TokenType::Weave => self.parse_weave_step(),
2765 TokenType::Use => self.parse_use_step(),
2766 TokenType::Remember => self.parse_remember_step(),
2767 TokenType::Recall => self.parse_recall_step(),
2768 TokenType::Par => self.parse_par_block().map(FlowStep::Par),
2769 TokenType::Hibernate => self.parse_hibernate_step(),
2770 TokenType::Deliberate => self.parse_block_step("deliberate").map(|l| FlowStep::Deliberate(DeliberateBlock { loc: l })),
2771 TokenType::Consensus => self.parse_block_step("consensus").map(|l| FlowStep::Consensus(ConsensusBlock { loc: l })),
2772 TokenType::Forge => self.parse_block_step("forge").map(|l| FlowStep::Forge(ForgeBlock { loc: l })),
2773 TokenType::Focus => self.parse_flow_step_simple("focus").map(|l| FlowStep::Focus(FocusStep { expression: l.1, loc: l.0 })),
2774 TokenType::Associate => self.parse_associate_step(),
2775 TokenType::Aggregate => self.parse_aggregate_step(),
2776 TokenType::Explore => self.parse_explore_step(),
2777 TokenType::Ingest => self.parse_ingest_step(),
2778 TokenType::Shield => self.parse_apply_step("shield").map(|l| FlowStep::ShieldApply(ShieldApplyStep { shield_name: l.1, target: l.2, output_type: l.3, loc: l.0 })),
2779 TokenType::Stream => self.parse_block_step("stream").map(|l| FlowStep::Stream(StreamBlock { loc: l })),
2780 TokenType::Navigate => self.parse_navigate_step(),
2781 TokenType::Drill => self.parse_drill_step(),
2782 TokenType::Trail => self.parse_flow_step_simple("trail").map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 })),
2783 TokenType::Corroborate => self.parse_corroborate_step(),
2784 TokenType::Ots => self.parse_apply_step("ots").map(|l| FlowStep::OtsApply(OtsApplyStep { ots_name: l.1, target: l.2, output_type: l.3, loc: l.0 })),
2785 TokenType::Mandate => self.parse_apply_step("mandate").map(|l| FlowStep::MandateApply(MandateApplyStep { mandate_name: l.1, target: l.2, output_type: l.3, loc: l.0 })),
2786 TokenType::Compute => self.parse_apply_step("compute").map(|l| FlowStep::ComputeApply(ComputeApplyStep { compute_name: l.1, arguments: Vec::new(), output_name: l.3, loc: l.0 })),
2787 TokenType::Listen => self.parse_listen_step(),
2788 TokenType::Daemon => self.parse_flow_step_simple("daemon").map(|l| FlowStep::DaemonStep(DaemonStepNode { daemon_ref: l.1, loc: l.0 })),
2789 TokenType::Emit => self.parse_emit_step(),
2791 TokenType::Publish => self.parse_publish_step(),
2792 TokenType::Discover => self.parse_discover_step(),
2793 TokenType::Persist => self.parse_persist_step(),
2794 TokenType::Retrieve => self.parse_retrieve_step(),
2795 TokenType::Mutate => self.parse_mutate_step(),
2796 TokenType::Purge => self.parse_store_where_step().map(|(loc, store_name, where_expr)| FlowStep::Purge(PurgeStep { store_name, where_expr, loc })),
2797 TokenType::Transact => self.parse_block_step("transact").map(|l| FlowStep::Transact(TransactBlock { loc: l })),
2798 TokenType::Quant => self.parse_quant().map(FlowStep::Quant),
2800 TokenType::Yield => self.parse_yield().map(FlowStep::Yield),
2802 TokenType::Run => self.parse_run().map(FlowStep::Run),
2806
2807 _ => {
2808 let hint = crate::smart_suggest::suggest_for(
2812 &tok.value,
2813 crate::smart_suggest::FLOW_BODY_KEYWORD_NAMES,
2814 );
2815 let base = format!(
2816 "Unexpected token in flow body: '{}' — expected step, if, for, let, return, ...",
2817 tok.value
2818 );
2819 let message = if hint.is_empty() {
2820 base
2821 } else {
2822 format!("{base}. {hint}")
2823 };
2824 Err(ParseError {
2825 message,
2826 line: tok.line,
2827 column: tok.column,
2828 ..Default::default()
2829 })
2830 }
2831 }
2832 }
2833
2834 fn parse_step(&mut self) -> Result<StepNode, ParseError> {
2837 let tok = self.consume(TokenType::Step)?;
2838 let loc = self.loc_of(&tok);
2839 let name = self.consume(TokenType::Identifier)?.value;
2840
2841 let mut persona_ref = String::new();
2842 if self.check(TokenType::Use) {
2843 self.advance();
2844 persona_ref = self.consume_any_ident_or_kw()?.value;
2845 }
2846
2847 self.consume(TokenType::LBrace)?;
2848
2849 let mut node = StepNode {
2850 name,
2851 persona_ref,
2852 given: String::new(),
2853 ask: String::new(),
2854 output_type: String::new(),
2855 confidence_floor: None,
2856 navigate_ref: String::new(),
2857 apply_ref: String::new(),
2858 requires_context: None,
2859 loc,
2860 };
2861
2862 while !self.check(TokenType::RBrace) {
2863 let inner = self.current().clone();
2864
2865 match inner.ttype {
2866 TokenType::Given => {
2867 self.advance();
2868 self.consume(TokenType::Colon)?;
2869 node.given = self.parse_expression_string()?;
2870 }
2871 TokenType::Ask => {
2872 self.advance();
2873 self.consume(TokenType::Colon)?;
2874 node.ask = self.consume(TokenType::StringLit)?.value;
2875 }
2876 TokenType::Output => {
2877 self.advance();
2887 self.consume(TokenType::Colon)?;
2888 node.output_type = self.parse_output_type_string()?;
2889 }
2890 TokenType::Navigate => {
2891 self.advance();
2892 self.consume(TokenType::Colon)?;
2893 node.navigate_ref = self.parse_dotted_identifier()?;
2894 }
2895 TokenType::Identifier if inner.value == "confidence_floor" => {
2896 self.advance();
2897 self.consume(TokenType::Colon)?;
2898 node.confidence_floor = Some(self.consume_number()?);
2899 }
2900 TokenType::Identifier if inner.value == "apply" => {
2901 self.advance();
2902 self.consume(TokenType::Colon)?;
2903 node.apply_ref = self.consume_any_ident_or_kw()?.value;
2904 }
2905 TokenType::Identifier if inner.value == "requires_context" => {
2913 self.advance();
2914 self.consume(TokenType::Colon)?;
2915 let num = self.current().clone();
2916 let bad = |tok: &crate::tokens::Token| ParseError {
2917 message: format!(
2918 "`requires_context:` must be a positive integer token count \
2919 (got '{}')",
2920 tok.value
2921 ),
2922 line: tok.line,
2923 column: tok.column,
2924 ..Default::default()
2925 };
2926 if num.ttype != TokenType::Integer {
2927 return Err(bad(&num));
2928 }
2929 let value = num.value.parse::<u32>().map_err(|_| bad(&num))?;
2930 self.advance();
2931 node.requires_context = Some(value);
2932 }
2933 TokenType::Use => {
2943 let tool = self
2944 .tokens
2945 .get(self.pos + 1)
2946 .map(|t| t.value.as_str())
2947 .filter(|v| !v.is_empty())
2948 .unwrap_or("<Tool>");
2949 return Err(ParseError {
2950 message: format!(
2951 "`use` is not valid inside a `step {{ }}` body — the tool dispatch \
2952 would be silently dropped. To invoke a tool, either write the \
2953 flow-level step `use {tool} on <arg>` (outside this block), or bind \
2954 it inside this step with `apply: {tool}`. To attach a persona, put \
2955 it in the step header: `step <name> use <Persona> {{ … }}`."
2956 ),
2957 line: inner.line,
2958 column: inner.column,
2959 ..Default::default()
2960 });
2961 }
2962 TokenType::Probe
2964 | TokenType::Reason
2965 | TokenType::Weave
2966 | TokenType::Stream => {
2967 self.skip_flow_step_structural()?;
2968 }
2969 _ => {
2970 return Err(ParseError {
2971 message: format!(
2972 "Unexpected token in step body: '{}' — expected given, ask, \
2973 probe, reason, weave, stream, output, confidence_floor, navigate, \
2974 apply, requires_context",
2975 inner.value
2976 ),
2977 line: inner.line,
2978 column: inner.column,
2979 ..Default::default()
2980 });
2981 }
2982 }
2983 }
2984 self.consume(TokenType::RBrace)?;
2985 Ok(node)
2986 }
2987
2988 fn skip_flow_step_structural(&mut self) -> Result<(), ParseError> {
2990 self.advance();
2992 while !self.check(TokenType::LBrace)
2994 && !self.check(TokenType::RBrace)
2995 && !self.check(TokenType::Eof)
2996 {
2997 let tt = &self.current().ttype;
2999 if matches!(
3000 tt,
3001 TokenType::Step
3002 | TokenType::Given
3003 | TokenType::Ask
3004 | TokenType::Output
3005 | TokenType::Navigate
3006 | TokenType::Use
3007 | TokenType::Probe
3008 | TokenType::Reason
3009 | TokenType::Weave
3010 | TokenType::Stream
3011 | TokenType::If
3012 | TokenType::For
3013 | TokenType::Let
3014 | TokenType::Return
3015 ) {
3016 return Ok(());
3017 }
3018 self.advance();
3019 }
3020 if self.check(TokenType::LBrace) {
3022 self.skip_braced_block()?;
3023 }
3024 Ok(())
3025 }
3026
3027 fn parse_intent(&mut self) -> Result<IntentNode, ParseError> {
3030 let tok = self.consume(TokenType::Intent)?;
3031 let loc = self.loc_of(&tok);
3032 let name = self.consume(TokenType::Identifier)?.value;
3033 self.consume(TokenType::LBrace)?;
3034
3035 let mut node = IntentNode {
3036 name,
3037 given: String::new(),
3038 ask: String::new(),
3039 output_type: None,
3040 confidence_floor: None,
3041 loc,
3042 leading_trivia: Vec::new(),
3043 trailing_trivia: Vec::new(),
3044 };
3045
3046 while !self.check(TokenType::RBrace) {
3047 let field_name = self.current().value.clone();
3048 self.advance();
3049 self.consume(TokenType::Colon)?;
3050
3051 match field_name.as_str() {
3052 "given" => node.given = self.consume(TokenType::Identifier)?.value,
3053 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
3054 "output" => node.output_type = Some(self.parse_type_expr()?),
3055 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
3056 _ => self.skip_value(),
3057 }
3058 }
3059 self.consume(TokenType::RBrace)?;
3060 Ok(node)
3061 }
3062
3063 fn parse_run(&mut self) -> Result<RunStatement, ParseError> {
3066 let tok = self.consume(TokenType::Run)?;
3067 let loc = self.loc_of(&tok);
3068 let flow_name = self.consume(TokenType::Identifier)?.value;
3069
3070 self.consume(TokenType::LParen)?;
3071 let mut arguments = Vec::new();
3072 if !self.check(TokenType::RParen) {
3073 arguments = self.parse_argument_list()?;
3074 }
3075 self.consume(TokenType::RParen)?;
3076
3077 let mut node = RunStatement {
3078 flow_name,
3079 arguments,
3080 persona: String::new(),
3081 context: String::new(),
3082 anchors: Vec::new(),
3083 on_failure: String::new(),
3084 on_failure_params: Vec::new(),
3085 output_to: String::new(),
3086 effort: String::new(),
3087 loc,
3088 leading_trivia: Vec::new(),
3089 trailing_trivia: Vec::new(),
3090 };
3091
3092 while self.check_run_modifier() {
3093 let mod_tok = self.current().clone();
3094 match mod_tok.ttype {
3095 TokenType::As => {
3096 self.advance();
3097 node.persona = self.consume(TokenType::Identifier)?.value;
3098 }
3099 TokenType::Within => {
3100 self.advance();
3101 node.context = self.consume(TokenType::Identifier)?.value;
3102 }
3103 TokenType::ConstrainedBy => {
3104 self.advance();
3105 node.anchors = self.parse_bracketed_identifiers()?;
3106 }
3107 TokenType::OnFailure => {
3108 self.advance();
3109 self.consume(TokenType::Colon)?;
3110 node.on_failure = self.consume_any_ident_or_kw()?.value;
3111 if self.check(TokenType::LParen) {
3113 self.advance();
3114 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3115 let key = self.consume_any_ident_or_kw()?.value;
3116 self.consume(TokenType::Colon)?;
3117 let val = self.consume_any_ident_or_kw()?.value;
3118 node.on_failure_params.push((key, val));
3119 if self.check(TokenType::Comma) {
3120 self.advance();
3121 }
3122 }
3123 if self.check(TokenType::RParen) {
3124 self.advance();
3125 }
3126 }
3127 }
3128 TokenType::OutputTo => {
3129 self.advance();
3130 self.consume(TokenType::Colon)?;
3131 node.output_to = self.consume(TokenType::StringLit)?.value;
3132 }
3133 TokenType::Effort => {
3134 self.advance();
3135 self.consume(TokenType::Colon)?;
3136 node.effort = self.consume_any_ident_or_kw()?.value;
3137 }
3138 _ => break,
3139 }
3140 }
3141
3142 Ok(node)
3143 }
3144
3145 fn parse_epistemic_block(&mut self) -> Result<EpistemicBlock, ParseError> {
3148 let tok = self.current().clone();
3149 let mode = match tok.ttype {
3150 TokenType::Know => "know",
3151 TokenType::Believe => "believe",
3152 TokenType::Speculate => "speculate",
3153 TokenType::Doubt => "doubt",
3154 _ => unreachable!(),
3155 };
3156 self.advance();
3157 let loc = self.loc_of(&tok);
3158
3159 self.consume(TokenType::LBrace)?;
3160 let mut body = Vec::new();
3161 while !self.check(TokenType::RBrace) {
3162 body.push(self.parse_declaration()?);
3163 }
3164 self.consume(TokenType::RBrace)?;
3165
3166 Ok(EpistemicBlock {
3167 mode: mode.to_string(),
3168 body,
3169 loc,
3170 leading_trivia: Vec::new(),
3171 trailing_trivia: Vec::new(),
3172 })
3173 }
3174
3175 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
3183 self.parse_expr_bp(0)
3184 }
3185
3186 fn parse_expr_bp(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
3187 let mut lhs = match self.current().ttype {
3190 TokenType::Minus => {
3191 self.advance();
3192 Expr::Unary(UnOp::Neg, Box::new(self.parse_expr_bp(6)?))
3193 }
3194 TokenType::Not => {
3195 self.advance();
3196 Expr::Unary(UnOp::Not, Box::new(self.parse_expr_bp(6)?))
3197 }
3198 _ => self.parse_postfix()?,
3199 };
3200 while let Some((op, lbp)) = Self::binop_of(self.current().ttype.clone()) {
3202 if lbp < min_bp {
3203 break;
3204 }
3205 self.advance();
3206 let rhs = self.parse_expr_bp(lbp + 1)?;
3207 lhs = Expr::Binary(op, Box::new(lhs), Box::new(rhs));
3208 }
3209 Ok(lhs)
3210 }
3211
3212 fn binop_of(t: TokenType) -> Option<(BinOp, u8)> {
3215 Some(match t {
3216 TokenType::Or => (BinOp::Or, 1),
3217 TokenType::And => (BinOp::And, 2),
3218 TokenType::Eq => (BinOp::Eq, 3),
3219 TokenType::Neq => (BinOp::Ne, 3),
3220 TokenType::Lt => (BinOp::Lt, 3),
3221 TokenType::Lte => (BinOp::Le, 3),
3222 TokenType::Gt => (BinOp::Gt, 3),
3223 TokenType::Gte => (BinOp::Ge, 3),
3224 TokenType::Plus => (BinOp::Add, 4),
3225 TokenType::Minus => (BinOp::Sub, 4),
3226 TokenType::Star => (BinOp::Mul, 5),
3227 TokenType::Slash => (BinOp::Div, 5),
3228 TokenType::Percent => (BinOp::Mod, 5),
3229 _ => return None,
3230 })
3231 }
3232
3233 fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
3239 let mut expr = self.parse_expr_atom()?;
3240 loop {
3241 if self.check(TokenType::Dot) {
3242 self.advance();
3243 let name = self.consume_any_ident_or_kw()?.value;
3244 if let Some(builtin) = Builtin::from_name(&name) {
3245 let mut args = vec![expr];
3246 if self.check(TokenType::LParen) {
3247 self.advance();
3248 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3249 args.push(self.parse_expr_bp(0)?);
3250 if self.check(TokenType::Comma) {
3251 self.advance();
3252 } else {
3253 break;
3254 }
3255 }
3256 self.consume(TokenType::RParen)?;
3257 }
3258 expr = Expr::Call(builtin, args);
3259 } else {
3260 expr = match expr {
3264 Expr::Ref(p) => Expr::Ref(format!("{p}.{name}")),
3265 other => Expr::Field(Box::new(other), name),
3266 };
3267 }
3268 } else if self.check(TokenType::LBracket) {
3269 self.advance();
3271 let index = self.parse_expr_bp(0)?;
3272 self.consume(TokenType::RBracket)?;
3273 expr = Expr::Index(Box::new(expr), Box::new(index));
3274 } else {
3275 break;
3276 }
3277 }
3278 Ok(expr)
3279 }
3280
3281 fn parse_expr_atom(&mut self) -> Result<Expr, ParseError> {
3282 let tok = self.current().clone();
3283 match tok.ttype {
3284 TokenType::Integer => {
3285 self.advance();
3286 let lit = tok
3287 .value
3288 .parse::<i64>()
3289 .map(ExprLit::Int)
3290 .or_else(|_| tok.value.parse::<f64>().map(ExprLit::Float))
3291 .map_err(|_| ParseError {
3292 message: format!("invalid integer literal '{}'", tok.value),
3293 line: tok.line,
3294 column: tok.column,
3295 ..Default::default()
3296 })?;
3297 Ok(Expr::Lit(lit))
3298 }
3299 TokenType::Float => {
3300 self.advance();
3301 let f = tok.value.parse::<f64>().map_err(|_| ParseError {
3302 message: format!("invalid float literal '{}'", tok.value),
3303 line: tok.line,
3304 column: tok.column,
3305 ..Default::default()
3306 })?;
3307 Ok(Expr::Lit(ExprLit::Float(f)))
3308 }
3309 TokenType::Bool => {
3310 self.advance();
3311 Ok(Expr::Lit(ExprLit::Bool(tok.value == "true")))
3312 }
3313 TokenType::StringLit => {
3314 self.advance();
3315 Ok(Expr::Lit(ExprLit::Str(tok.value)))
3316 }
3317 TokenType::LParen => {
3318 self.advance();
3319 let inner = self.parse_expr_bp(0)?;
3320 self.consume(TokenType::RParen)?;
3321 Ok(inner)
3322 }
3323 _ => {
3324 Ok(Expr::Ref(self.consume_any_ident_or_kw()?.value))
3328 }
3329 }
3330 }
3331
3332 fn expr_lit_surface(lit: &ExprLit) -> String {
3337 match lit {
3338 ExprLit::Int(i) => i.to_string(),
3339 ExprLit::Float(f) => f.to_string(),
3340 ExprLit::Bool(b) => b.to_string(),
3341 ExprLit::Str(s) => s.clone(),
3342 }
3343 }
3344
3345 fn expr_leaf_surface(expr: &Expr) -> Option<String> {
3346 match expr {
3347 Expr::Ref(p) => Some(p.clone()),
3348 Expr::Lit(l) => Some(Self::expr_lit_surface(l)),
3349 _ => None,
3350 }
3351 }
3352
3353 fn expr_legacy_leaf(expr: &Expr) -> Option<(String, String, String)> {
3357 match expr {
3358 Expr::Ref(p) => Some((p.clone(), String::new(), String::new())),
3359 Expr::Binary(op, l, r) => {
3360 let op_s = match op {
3361 BinOp::Eq => "==",
3362 BinOp::Ne => "!=",
3363 BinOp::Lt => "<",
3364 BinOp::Le => "<=",
3365 BinOp::Gt => ">",
3366 BinOp::Ge => ">=",
3367 _ => return None,
3368 };
3369 let lhs = match &**l {
3370 Expr::Ref(p) => p.clone(),
3371 _ => return None,
3372 };
3373 let rhs = Self::expr_leaf_surface(r)?;
3374 Some((lhs, op_s.to_string(), rhs))
3375 }
3376 _ => None,
3377 }
3378 }
3379
3380 fn collect_or_leaves(expr: &Expr, out: &mut Vec<(String, String, String)>) -> bool {
3383 match expr {
3384 Expr::Binary(BinOp::Or, l, r) => {
3385 Self::collect_or_leaves(l, out) && Self::collect_or_leaves(r, out)
3386 }
3387 _ => match Self::expr_legacy_leaf(expr) {
3388 Some(t) => {
3389 out.push(t);
3390 true
3391 }
3392 None => false,
3393 },
3394 }
3395 }
3396
3397 #[allow(clippy::type_complexity)]
3403 fn cond_as_legacy(
3404 expr: &Expr,
3405 ) -> Option<(String, String, String, Vec<(String, String, String)>, String)> {
3406 let mut leaves = Vec::new();
3407 if !Self::collect_or_leaves(expr, &mut leaves) || leaves.is_empty() {
3408 return None;
3409 }
3410 let (c0, o0, v0) = leaves[0].clone();
3411 let rest = leaves[1..].to_vec();
3412 let conjunctor = if rest.is_empty() {
3413 String::new()
3414 } else {
3415 "or".to_string()
3416 };
3417 Some((c0, o0, v0, rest, conjunctor))
3418 }
3419
3420 fn parse_if(&mut self) -> Result<ConditionalNode, ParseError> {
3421 let tok = self.consume(TokenType::If)?;
3422 let loc = self.loc_of(&tok);
3423
3424 let expr = self.parse_expr()?;
3429 let (condition, comparison_op, comparison_value, conditions, conjunctor, cond) =
3430 match Self::cond_as_legacy(&expr) {
3431 Some((c, o, v, more, conj)) => (c, o, v, more, conj, None),
3432 None => (
3433 String::new(),
3434 String::new(),
3435 String::new(),
3436 Vec::new(),
3437 String::new(),
3438 Some(expr),
3439 ),
3440 };
3441
3442 let mut then_body = Vec::new();
3443 let mut else_body = Vec::new();
3444
3445 if self.check(TokenType::Arrow) {
3447 self.advance();
3448 then_body.push(self.parse_flow_step()?);
3449 } else if self.check(TokenType::LBrace) {
3450 self.advance();
3451 while !self.check(TokenType::RBrace) {
3452 then_body.push(self.parse_flow_step()?);
3453 }
3454 self.consume(TokenType::RBrace)?;
3455 }
3456
3457 if self.check(TokenType::Else) {
3459 self.advance();
3460 if self.check(TokenType::Arrow) {
3461 self.advance();
3462 else_body.push(self.parse_flow_step()?);
3463 } else if self.check(TokenType::LBrace) {
3464 self.advance();
3465 while !self.check(TokenType::RBrace) {
3466 else_body.push(self.parse_flow_step()?);
3467 }
3468 self.consume(TokenType::RBrace)?;
3469 }
3470 }
3471
3472 Ok(ConditionalNode {
3473 condition,
3474 comparison_op,
3475 comparison_value,
3476 then_body,
3477 else_body,
3478 conditions,
3479 conjunctor,
3480 cond,
3481 loc,
3482 })
3483 }
3484
3485 fn parse_for_in(&mut self) -> Result<ForInStatement, ParseError> {
3488 let tok = self.consume(TokenType::For)?;
3489 let loc = self.loc_of(&tok);
3490 let variable = self.consume(TokenType::Identifier)?.value;
3491 self.consume(TokenType::In)?;
3492 let iterable = self.parse_dotted_identifier()?;
3493
3494 self.consume(TokenType::LBrace)?;
3495 self.loop_depth += 1;
3502 let body_result = (|| -> Result<Vec<FlowStep>, ParseError> {
3503 let mut body = Vec::new();
3504 while !self.check(TokenType::RBrace) {
3505 body.push(self.parse_flow_step()?);
3506 }
3507 Ok(body)
3508 })();
3509 self.loop_depth -= 1;
3510 let body = body_result?;
3511 self.consume(TokenType::RBrace)?;
3512
3513 Ok(ForInStatement {
3514 variable,
3515 iterable,
3516 body,
3517 loc,
3518 })
3519 }
3520
3521 fn parse_break(&mut self) -> Result<BreakStatement, ParseError> {
3524 let tok = self.consume(TokenType::Break)?;
3525 let loc = self.loc_of(&tok);
3526 if self.loop_depth == 0 {
3527 return Err(ParseError {
3528 message: "'break' outside of a for-in loop body".to_string(),
3529 line: tok.line,
3530 column: tok.column,
3531 ..Default::default()
3532 });
3533 }
3534 Ok(BreakStatement { loc })
3535 }
3536
3537 fn parse_continue(&mut self) -> Result<ContinueStatement, ParseError> {
3540 let tok = self.consume(TokenType::Continue)?;
3541 let loc = self.loc_of(&tok);
3542 if self.loop_depth == 0 {
3543 return Err(ParseError {
3544 message: "'continue' outside of a for-in loop body".to_string(),
3545 line: tok.line,
3546 column: tok.column,
3547 ..Default::default()
3548 });
3549 }
3550 Ok(ContinueStatement { loc })
3551 }
3552
3553 fn parse_let(&mut self) -> Result<LetStatement, ParseError> {
3556 let tok = self.consume(TokenType::Let)?;
3557 let loc = self.loc_of(&tok);
3558
3559 let name = self.consume_any_ident_or_kw()?.value;
3561 let type_annotation = if self.check(TokenType::Colon) {
3563 self.advance();
3564 Some(self.parse_type_expr()?)
3565 } else {
3566 None
3567 };
3568 self.consume(TokenType::Assign)?;
3569 self.last_let_value_kind = "literal".to_string();
3572 let (value, value_ast) = self.parse_let_value_expr_with_ast()?;
3573
3574 Ok(LetStatement {
3575 identifier: name,
3576 value_expr: value,
3577 value_kind: self.last_let_value_kind.clone(),
3578 type_annotation,
3579 value_ast,
3580 loc,
3581 leading_trivia: Vec::new(),
3582 trailing_trivia: Vec::new(),
3583 })
3584 }
3585
3586 fn parse_let_value_expr(&mut self) -> Result<String, ParseError> {
3587 let atom = self.parse_let_atom()?;
3588
3589 if matches!(
3591 self.current().ttype,
3592 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3593 ) {
3594 let mut parts = vec![atom];
3595 while matches!(
3596 self.current().ttype,
3597 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3598 ) {
3599 parts.push(self.advance().value.clone());
3600 parts.push(self.parse_let_atom()?);
3601 }
3602 self.last_let_value_kind = "expression".to_string();
3603 return Ok(parts.join(" "));
3604 }
3605 Ok(atom)
3606 }
3607
3608 fn parse_let_value_expr_with_ast(&mut self) -> Result<(String, Option<Expr>), ParseError> {
3618 if self.check(TokenType::LBracket) {
3619 self.last_let_value_kind = "literal".to_string();
3620 return Ok((self.parse_let_list_literal()?, None));
3621 }
3622 let expr = self.parse_expr()?;
3623 Ok(match expr {
3624 Expr::Lit(lit) => {
3625 self.last_let_value_kind = "literal".to_string();
3626 (Self::expr_lit_surface(&lit), None)
3627 }
3628 Expr::Ref(p) => {
3629 self.last_let_value_kind = "reference".to_string();
3630 (p, None)
3631 }
3632 other => {
3633 self.last_let_value_kind = "expression".to_string();
3634 (Self::render_expr(&other), Some(other))
3635 }
3636 })
3637 }
3638
3639 fn render_expr(e: &Expr) -> String {
3642 match e {
3643 Expr::Lit(l) => Self::expr_lit_surface(l),
3644 Expr::Ref(p) => p.clone(),
3645 Expr::Unary(UnOp::Neg, x) => format!("-{}", Self::render_expr(x)),
3646 Expr::Unary(UnOp::Not, x) => format!("not {}", Self::render_expr(x)),
3647 Expr::Binary(op, l, r) => {
3648 let sym = match op {
3649 BinOp::Add => "+",
3650 BinOp::Sub => "-",
3651 BinOp::Mul => "*",
3652 BinOp::Div => "/",
3653 BinOp::Mod => "%",
3654 BinOp::Eq => "==",
3655 BinOp::Ne => "!=",
3656 BinOp::Lt => "<",
3657 BinOp::Le => "<=",
3658 BinOp::Gt => ">",
3659 BinOp::Ge => ">=",
3660 BinOp::And => "and",
3661 BinOp::Or => "or",
3662 };
3663 format!("({} {sym} {})", Self::render_expr(l), Self::render_expr(r))
3664 }
3665 Expr::Call(b, args) => {
3666 let recv = args.first().map(Self::render_expr).unwrap_or_default();
3667 let rest: Vec<String> = args.iter().skip(1).map(Self::render_expr).collect();
3668 if rest.is_empty() {
3669 format!("{recv}.{}", b.surface())
3670 } else {
3671 format!("{recv}.{}({})", b.surface(), rest.join(", "))
3672 }
3673 }
3674 Expr::Field(b, f) => format!("{}.{f}", Self::render_expr(b)),
3675 Expr::Index(b, i) => format!("{}[{}]", Self::render_expr(b), Self::render_expr(i)),
3676 }
3677 }
3678
3679 fn parse_let_atom(&mut self) -> Result<String, ParseError> {
3680 let tok = self.current().clone();
3681
3682 match tok.ttype {
3683 TokenType::StringLit => {
3684 self.last_let_value_kind = "literal".to_string();
3685 self.advance();
3686 Ok(tok.value)
3687 }
3688 TokenType::Integer | TokenType::Float => {
3689 self.last_let_value_kind = "literal".to_string();
3690 self.advance();
3691 Ok(tok.value)
3692 }
3693 TokenType::Bool => {
3694 self.last_let_value_kind = "literal".to_string();
3695 self.advance();
3696 Ok(tok.value)
3697 }
3698 TokenType::Identifier => {
3699 self.last_let_value_kind = "reference".to_string();
3700 self.parse_dotted_identifier()
3701 }
3702 TokenType::LBracket => {
3703 self.last_let_value_kind = "literal".to_string();
3704 self.parse_let_list_literal()
3705 }
3706 _ => {
3707 if self.pos + 1 < self.tokens.len()
3709 && self.tokens[self.pos + 1].ttype == TokenType::Dot
3710 {
3711 self.last_let_value_kind = "reference".to_string();
3712 return self.parse_dotted_identifier();
3713 }
3714 Err(ParseError {
3715 message: format!(
3716 "Expected value expression, found {:?}('{}')",
3717 tok.ttype, tok.value
3718 ),
3719 line: tok.line,
3720 column: tok.column,
3721 ..Default::default()
3722 })
3723 }
3724 }
3725 }
3726
3727 fn parse_let_list_literal(&mut self) -> Result<String, ParseError> {
3728 self.consume(TokenType::LBracket)?;
3729 let mut items = Vec::new();
3730 if !self.check(TokenType::RBracket) {
3731 items.push(self.parse_let_value_expr()?);
3732 while self.check(TokenType::Comma) {
3733 self.advance();
3734 if self.check(TokenType::RBracket) {
3735 break; }
3737 items.push(self.parse_let_value_expr()?);
3738 }
3739 }
3740 self.consume(TokenType::RBracket)?;
3741 Ok(format!("[{}]", items.join(", ")))
3742 }
3743
3744 fn parse_return(&mut self) -> Result<ReturnStatement, ParseError> {
3747 let tok = self.consume(TokenType::Return)?;
3748 let loc = self.loc_of(&tok);
3749 let value = self.parse_let_value_expr()?;
3750 Ok(ReturnStatement {
3751 value_expr: value,
3752 loc,
3753 })
3754 }
3755
3756 fn parse_flow_step_simple(&mut self, _kw: &str) -> Result<(Loc, String), ParseError> {
3760 let tok = self.current().clone();
3761 self.advance(); let target = if self.at_declaration_start()
3763 || self.check(TokenType::RBrace)
3764 || self.check(TokenType::Eof)
3765 {
3766 String::new()
3767 } else {
3768 self.consume_any_ident_or_kw()?.value.clone()
3769 };
3770 if self.check(TokenType::LBrace) {
3772 self.skip_braced_block()?;
3773 }
3774 Ok((
3775 Loc {
3776 line: tok.line,
3777 column: tok.column,
3778 },
3779 target,
3780 ))
3781 }
3782
3783 fn parse_block_step(&mut self, _kw: &str) -> Result<Loc, ParseError> {
3785 let tok = self.current().clone();
3786 self.advance();
3787 while !self.check(TokenType::LBrace)
3789 && !self.check(TokenType::RBrace)
3790 && !self.check(TokenType::Eof)
3791 && !self.at_declaration_start()
3792 {
3793 self.advance();
3794 }
3795 if self.check(TokenType::LBrace) {
3796 self.skip_braced_block()?;
3797 }
3798 Ok(Loc {
3799 line: tok.line,
3800 column: tok.column,
3801 })
3802 }
3803
3804 fn parse_par_block(&mut self) -> Result<ParBlock, ParseError> {
3813 let tok = self.current().clone();
3814 self.advance(); self.consume(TokenType::LBrace)?;
3816 let mut branches: Vec<Vec<FlowStep>> = Vec::new();
3817 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3818 branches.push(vec![self.parse_flow_step()?]);
3819 }
3820 self.consume(TokenType::RBrace)?;
3821 Ok(ParBlock {
3822 branches,
3823 loc: Loc {
3824 line: tok.line,
3825 column: tok.column,
3826 },
3827 })
3828 }
3829
3830 fn parse_quant(&mut self) -> Result<QuantBlock, ParseError> {
3843 let tok = self.current().clone();
3844 self.advance(); let mut block = QuantBlock {
3847 encoding: None,
3848 observable: None,
3849 qubits: None,
3850 depth: None,
3851 bandwidth: None,
3852 reupload: None,
3853 effect: "quant_sim".to_string(),
3856 body: Vec::new(),
3857 loc: Loc {
3858 line: tok.line,
3859 column: tok.column,
3860 },
3861 };
3862
3863 if self.check(TokenType::LParen) {
3865 self.advance();
3866 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3867 let key = self.consume_any_ident_or_kw()?.value;
3868 self.consume(TokenType::Colon)?;
3869 match key.as_str() {
3870 "encoding" => {
3871 block.encoding = Some(self.consume_any_ident_or_kw()?.value)
3872 }
3873 "observable" => {
3874 block.observable = Some(self.parse_dotted_identifier()?)
3875 }
3876 "qubits" => block.qubits = Some(self.consume_number()? as i64),
3877 "depth" => block.depth = Some(self.consume_number()? as i64),
3878 "bandwidth" => block.bandwidth = Some(self.consume_number()?),
3879 "reupload" => block.reupload = Some(self.consume_number()? as i64),
3881 "backend" => block.effect = self.consume_any_ident_or_kw()?.value,
3883 other => {
3884 return Err(ParseError {
3885 message: format!(
3886 "Unknown `quant` attribute `{other}` — expected one of \
3887 encoding, observable, qubits, depth, bandwidth, reupload, backend"
3888 ),
3889 line: self.current().line,
3890 column: self.current().column,
3891 ..Default::default()
3892 });
3893 }
3894 }
3895 if self.check(TokenType::Comma) {
3897 self.advance();
3898 }
3899 }
3900 self.consume(TokenType::RParen)?;
3901 }
3902
3903 self.consume(TokenType::LBrace)?;
3905 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3906 block.body.push(self.parse_flow_step()?);
3907 }
3908 self.consume(TokenType::RBrace)?;
3909
3910 Ok(block)
3911 }
3912
3913 fn parse_yield(&mut self) -> Result<YieldStatement, ParseError> {
3917 let tok = self.consume(TokenType::Yield)?;
3918 let loc = self.loc_of(&tok);
3919 self.last_let_value_kind = "literal".to_string();
3920 let value_expr = self.parse_let_value_expr()?;
3921 Ok(YieldStatement {
3922 value_expr,
3923 value_kind: self.last_let_value_kind.clone(),
3924 loc,
3925 })
3926 }
3927
3928 fn parse_apply_step(&mut self, _kw: &str) -> Result<(Loc, String, String, String), ParseError> {
3930 let tok = self.current().clone();
3931 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
3933 let mut target = String::new();
3934 let mut output_type = String::new();
3935 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3937 let next = self.current().clone();
3938 if next.value == "on" {
3939 self.advance();
3940 target = self.consume_any_ident_or_kw()?.value.clone();
3941 }
3942 }
3943 if self.check(TokenType::Arrow) {
3945 self.advance();
3946 output_type = self.consume_any_ident_or_kw()?.value.clone();
3947 }
3948 if self.check(TokenType::LBrace) {
3950 self.skip_braced_block()?;
3951 }
3952 Ok((
3953 Loc {
3954 line: tok.line,
3955 column: tok.column,
3956 },
3957 name,
3958 target,
3959 output_type,
3960 ))
3961 }
3962
3963 fn parse_weave_step(&mut self) -> Result<FlowStep, ParseError> {
3964 let tok = self.current().clone();
3965 self.advance();
3966 let mut node = WeaveStep {
3967 sources: Vec::new(),
3968 target: String::new(),
3969 format_type: String::new(),
3970 priority: Vec::new(),
3971 style: String::new(),
3972 loc: Loc {
3973 line: tok.line,
3974 column: tok.column,
3975 },
3976 };
3977 if self.check(TokenType::LBrace) {
3978 self.advance();
3979 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3980 let f = self.current().value.clone();
3981 self.advance();
3982 if self.check(TokenType::Colon) {
3983 self.advance();
3984 match f.as_str() {
3985 "sources" => node.sources = self.parse_bracketed_identifiers()?,
3986 "target" => node.target = self.consume_any_ident_or_kw()?.value.clone(),
3987 "format" => {
3988 node.format_type = self.consume_any_ident_or_kw()?.value.clone()
3989 }
3990 "priority" => node.priority = self.parse_bracketed_identifiers()?,
3991 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
3992 _ => self.skip_value(),
3993 }
3994 }
3995 }
3996 if self.check(TokenType::RBrace) {
3997 self.advance();
3998 }
3999 }
4000 Ok(FlowStep::Weave(node))
4001 }
4002
4003 fn parse_use_step(&mut self) -> Result<FlowStep, ParseError> {
4004 let tok = self.current().clone();
4005 self.advance();
4006 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
4007 let args = if self.check(TokenType::LParen) {
4018 UseArgs::Named(self.parse_named_arg_list()?)
4019 } else {
4020 let mut argument = String::new();
4021 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4022 let next = self.current().clone();
4023 if next.value == "on" {
4024 self.advance();
4025 argument = self.consume_any_ident_or_kw()?.value.clone();
4026 }
4027 }
4028 UseArgs::LegacyPositional(argument)
4029 };
4030 if self.check(TokenType::LBrace) {
4031 self.skip_braced_block()?;
4032 }
4033 Ok(FlowStep::UseTool(UseToolStep {
4034 tool_name,
4035 args,
4036 loc: Loc {
4037 line: tok.line,
4038 column: tok.column,
4039 },
4040 }))
4041 }
4042
4043 fn parse_named_arg_list(&mut self) -> Result<Vec<(String, String, String)>, ParseError> {
4049 self.consume(TokenType::LParen)?;
4050 let mut args = Vec::new();
4051 while !self.check(TokenType::RParen) {
4052 let name = self.consume_any_ident_or_kw()?.value;
4055 self.consume(TokenType::Assign)?;
4056 let value = self.parse_let_atom()?;
4057 let value_kind = self.last_let_value_kind.clone();
4061 args.push((name, value, value_kind));
4062 if self.check(TokenType::Comma) {
4063 self.advance();
4064 } else {
4065 break;
4066 }
4067 }
4068 self.consume(TokenType::RParen)?;
4069 Ok(args)
4070 }
4071
4072 fn parse_remember_step(&mut self) -> Result<FlowStep, ParseError> {
4073 let tok = self.current().clone();
4074 self.advance();
4075 let expr = self.consume_any_ident_or_kw()?.value.clone();
4076 let mut mem = String::new();
4077 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4078 let next = self.current().clone();
4079 if next.value == "in" || next.ttype == TokenType::In {
4080 self.advance();
4081 mem = self.consume_any_ident_or_kw()?.value.clone();
4082 }
4083 }
4084 Ok(FlowStep::Remember(RememberStep {
4085 expression: expr,
4086 memory_target: mem,
4087 loc: Loc {
4088 line: tok.line,
4089 column: tok.column,
4090 },
4091 }))
4092 }
4093
4094 fn parse_recall_step(&mut self) -> Result<FlowStep, ParseError> {
4095 let tok = self.current().clone();
4096 self.advance();
4097 let query = if self.check(TokenType::StringLit) {
4098 self.consume(TokenType::StringLit)?.value.clone()
4099 } else {
4100 self.consume_any_ident_or_kw()?.value.clone()
4101 };
4102 let mut mem = String::new();
4103 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4104 let next = self.current().clone();
4105 if next.value == "from" || next.ttype == TokenType::From {
4106 self.advance();
4107 mem = self.consume_any_ident_or_kw()?.value.clone();
4108 }
4109 }
4110 Ok(FlowStep::Recall(RecallStep {
4111 query,
4112 memory_source: mem,
4113 loc: Loc {
4114 line: tok.line,
4115 column: tok.column,
4116 },
4117 }))
4118 }
4119
4120 fn parse_hibernate_step(&mut self) -> Result<FlowStep, ParseError> {
4121 let tok = self.current().clone();
4122 self.advance();
4123 let mut event = String::new();
4124 let mut timeout = String::new();
4125 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4126 event = self.consume_any_ident_or_kw()?.value.clone();
4127 }
4128 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4129 let next = self.current().clone();
4130 if next.ttype == TokenType::Duration {
4131 self.advance();
4132 timeout = next.value.clone();
4133 }
4134 }
4135 Ok(FlowStep::Hibernate(HibernateStep {
4136 event_name: event,
4137 timeout,
4138 loc: Loc {
4139 line: tok.line,
4140 column: tok.column,
4141 },
4142 }))
4143 }
4144
4145 fn parse_associate_step(&mut self) -> Result<FlowStep, ParseError> {
4146 let tok = self.current().clone();
4147 self.advance();
4148 let left = self.consume_any_ident_or_kw()?.value.clone();
4149 let mut right = String::new();
4150 let mut using = String::new();
4151 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4152 right = self.consume_any_ident_or_kw()?.value.clone();
4153 }
4154 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4155 let next = self.current().clone();
4156 if next.value == "using" {
4157 self.advance();
4158 using = self.consume_any_ident_or_kw()?.value.clone();
4159 }
4160 }
4161 Ok(FlowStep::Associate(AssociateStep {
4162 left,
4163 right,
4164 using_field: using,
4165 loc: Loc {
4166 line: tok.line,
4167 column: tok.column,
4168 },
4169 }))
4170 }
4171
4172 fn parse_aggregate_step(&mut self) -> Result<FlowStep, ParseError> {
4173 let tok = self.current().clone();
4174 self.advance();
4175 let target = self.consume_any_ident_or_kw()?.value.clone();
4176 let mut group_by = Vec::new();
4177 let mut alias = String::new();
4178 if self.check(TokenType::LBrace) {
4179 self.advance();
4180 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4181 let f = self.current().value.clone();
4182 self.advance();
4183 if self.check(TokenType::Colon) {
4184 self.advance();
4185 match f.as_str() {
4186 "group_by" => group_by = self.parse_bracketed_identifiers()?,
4187 "alias" | "as" => alias = self.consume_any_ident_or_kw()?.value.clone(),
4188 _ => self.skip_value(),
4189 }
4190 }
4191 }
4192 if self.check(TokenType::RBrace) {
4193 self.advance();
4194 }
4195 }
4196 Ok(FlowStep::Aggregate(AggregateStep {
4197 target,
4198 group_by,
4199 alias,
4200 loc: Loc {
4201 line: tok.line,
4202 column: tok.column,
4203 },
4204 }))
4205 }
4206
4207 fn parse_explore_step(&mut self) -> Result<FlowStep, ParseError> {
4208 let tok = self.current().clone();
4209 self.advance();
4210 let target = self.consume_any_ident_or_kw()?.value.clone();
4211 let mut limit = None;
4212 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4213 if self.current().ttype == TokenType::Integer {
4214 limit = self.current().value.parse::<i64>().ok();
4215 self.advance();
4216 }
4217 }
4218 Ok(FlowStep::ExploreStep(ExploreStepNode {
4219 target,
4220 limit,
4221 loc: Loc {
4222 line: tok.line,
4223 column: tok.column,
4224 },
4225 }))
4226 }
4227
4228 fn parse_ingest_step(&mut self) -> Result<FlowStep, ParseError> {
4229 let tok = self.current().clone();
4230 self.advance();
4231 let source = self.consume_any_ident_or_kw()?.value.clone();
4232 let mut target = String::new();
4233 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4234 let next = self.current().clone();
4235 if next.value == "into" || next.ttype == TokenType::Into {
4236 self.advance();
4237 target = self.consume_any_ident_or_kw()?.value.clone();
4238 }
4239 }
4240 if self.check(TokenType::LBrace) {
4241 self.skip_braced_block()?;
4242 }
4243 Ok(FlowStep::Ingest(IngestStep {
4244 source,
4245 target,
4246 loc: Loc {
4247 line: tok.line,
4248 column: tok.column,
4249 },
4250 }))
4251 }
4252
4253 fn parse_navigate_step(&mut self) -> Result<FlowStep, ParseError> {
4254 let tok = self.current().clone();
4255 self.advance();
4256 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
4257 let mut node = NavigateStep {
4258 pix_name,
4259 corpus_name: String::new(),
4260 query_expr: String::new(),
4261 trail_enabled: false,
4262 output_name: String::new(),
4263 seed: String::new(),
4264 budget: None,
4265 where_expr: String::new(),
4266 loc: Loc {
4267 line: tok.line,
4268 column: tok.column,
4269 },
4270 };
4271 if self.check(TokenType::LBrace) {
4272 self.advance();
4273 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4274 let f = self.current().value.clone();
4275 self.advance();
4276 if self.check(TokenType::Colon) {
4277 self.advance();
4278 match f.as_str() {
4279 "corpus" => {
4280 node.corpus_name = self.consume_any_ident_or_kw()?.value.clone()
4281 }
4282 "query" => {
4283 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
4284 }
4285 "trail" => {
4286 node.trail_enabled = self.consume_any_ident_or_kw()?.value == "true"
4287 }
4288 "output" | "as" => {
4289 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
4290 }
4291 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
4293 "budget" => node.budget = self.parse_optional_int(),
4294 "where" => {
4302 node.where_expr = self.consume(TokenType::StringLit)?.value.clone()
4303 }
4304 _ => self.skip_value(),
4305 }
4306 }
4307 }
4308 if self.check(TokenType::RBrace) {
4309 self.advance();
4310 }
4311 }
4312 Ok(FlowStep::Navigate(node))
4313 }
4314
4315 fn parse_drill_step(&mut self) -> Result<FlowStep, ParseError> {
4316 let tok = self.current().clone();
4317 self.advance();
4318 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
4319 let mut node = DrillStep {
4320 pix_name,
4321 subtree_path: String::new(),
4322 query_expr: String::new(),
4323 output_name: String::new(),
4324 loc: Loc {
4325 line: tok.line,
4326 column: tok.column,
4327 },
4328 };
4329 if self.check(TokenType::LBrace) {
4330 self.advance();
4331 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4332 let f = self.current().value.clone();
4333 self.advance();
4334 if self.check(TokenType::Colon) {
4335 self.advance();
4336 match f.as_str() {
4337 "subtree" | "path" => {
4338 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
4339 }
4340 "query" => {
4341 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
4342 }
4343 "output" | "as" => {
4344 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
4345 }
4346 _ => self.skip_value(),
4347 }
4348 }
4349 }
4350 if self.check(TokenType::RBrace) {
4351 self.advance();
4352 }
4353 }
4354 Ok(FlowStep::Drill(node))
4355 }
4356
4357 fn parse_corroborate_step(&mut self) -> Result<FlowStep, ParseError> {
4358 let tok = self.current().clone();
4359 self.advance();
4360 let nav_ref = self.consume_any_ident_or_kw()?.value.clone();
4361 let mut output = String::new();
4362 if self.check(TokenType::Arrow) {
4363 self.advance();
4364 output = self.consume_any_ident_or_kw()?.value.clone();
4365 }
4366 Ok(FlowStep::Corroborate(CorroborateStep {
4367 navigate_ref: nav_ref,
4368 output_name: output,
4369 loc: Loc {
4370 line: tok.line,
4371 column: tok.column,
4372 },
4373 }))
4374 }
4375
4376 fn parse_listen_step(&mut self) -> Result<FlowStep, ParseError> {
4377 let tok = self.current().clone();
4378 self.advance();
4379 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
4383 (self.consume(TokenType::StringLit)?.value.clone(), false)
4384 } else {
4385 (self.consume_any_ident_or_kw()?.value.clone(), true)
4386 };
4387 let mut alias = String::new();
4388 if !self.at_declaration_start()
4389 && !self.check(TokenType::RBrace)
4390 && !self.check(TokenType::LBrace)
4391 {
4392 let next = self.current().clone();
4393 if next.value == "as" || next.ttype == TokenType::As {
4394 self.advance();
4395 alias = self.consume_any_ident_or_kw()?.value.clone();
4396 }
4397 }
4398 let body = self.parse_listener_body()?;
4402 Ok(FlowStep::Listen(ListenStep {
4403 channel,
4404 channel_is_ref,
4405 event_alias: alias,
4406 body,
4407 loc: Loc {
4408 line: tok.line,
4409 column: tok.column,
4410 },
4411 }))
4412 }
4413
4414 fn parse_listener_body(&mut self) -> Result<Vec<FlowStep>, ParseError> {
4419 let mut body = Vec::new();
4420 if self.check(TokenType::LBrace) {
4421 self.advance(); while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4423 body.push(self.parse_flow_step()?);
4424 }
4425 self.consume(TokenType::RBrace)?;
4426 }
4427 Ok(body)
4428 }
4429
4430 fn parse_retrieve_step(&mut self) -> Result<FlowStep, ParseError> {
4431 let tok = self.current().clone();
4432 self.advance();
4433 let store = self.consume_any_ident_or_kw()?.value.clone();
4434 let mut where_expr = String::new();
4435 let mut alias = String::new();
4436 let mut order_by = String::new();
4437 let mut limit_expr = String::new();
4438 if self.check(TokenType::LBrace) {
4439 self.advance();
4440 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4441 let f = self.current().value.clone();
4442 self.advance();
4443 if self.check(TokenType::Colon) {
4444 self.advance();
4445 match f.as_str() {
4446 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
4447 "as" | "alias" => alias = self.consume_any_ident_or_kw()?.value.clone(),
4448 "order_by" => {
4451 order_by = self.consume(TokenType::StringLit)?.value.clone()
4452 }
4453 "limit" => {
4458 let t = self.current().clone();
4459 match t.ttype {
4460 TokenType::Integer | TokenType::StringLit => {
4461 limit_expr = t.value.clone();
4462 self.advance();
4463 }
4464 _ => self.skip_value(),
4465 }
4466 }
4467 _ => self.skip_value(),
4468 }
4469 }
4470 }
4471 if self.check(TokenType::RBrace) {
4472 self.advance();
4473 }
4474 }
4475 Ok(FlowStep::Retrieve(RetrieveStep {
4476 store_name: store,
4477 where_expr,
4478 alias,
4479 order_by,
4480 limit_expr,
4481 loc: Loc {
4482 line: tok.line,
4483 column: tok.column,
4484 },
4485 }))
4486 }
4487
4488 fn parse_store_where_step(
4501 &mut self,
4502 ) -> Result<(Loc, String, String), ParseError> {
4503 let tok = self.current().clone();
4504 self.advance(); let store = if self.at_declaration_start()
4506 || self.check(TokenType::RBrace)
4507 || self.check(TokenType::Eof)
4508 {
4509 String::new()
4510 } else {
4511 self.consume_any_ident_or_kw()?.value.clone()
4512 };
4513 let mut where_expr = String::new();
4514 if self.check(TokenType::LBrace) {
4515 self.advance();
4516 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4517 let field = self.current().value.clone();
4518 self.advance();
4519 if self.check(TokenType::Colon) {
4520 self.advance();
4521 match field.as_str() {
4522 "where" => {
4523 where_expr =
4524 self.consume(TokenType::StringLit)?.value.clone()
4525 }
4526 _ => self.skip_value(),
4527 }
4528 }
4529 }
4530 if self.check(TokenType::RBrace) {
4531 self.advance();
4532 }
4533 }
4534 Ok((
4535 Loc {
4536 line: tok.line,
4537 column: tok.column,
4538 },
4539 store,
4540 where_expr,
4541 ))
4542 }
4543
4544 fn parse_persist_step(&mut self) -> Result<FlowStep, ParseError> {
4563 let tok = self.current().clone();
4564 self.advance(); if self.current().value == "into" && !self.check(TokenType::LBrace) {
4568 self.advance();
4569 }
4570 let store = if self.at_declaration_start()
4571 || self.check(TokenType::LBrace)
4572 || self.check(TokenType::RBrace)
4573 || self.check(TokenType::Eof)
4574 {
4575 String::new()
4576 } else {
4577 self.consume_any_ident_or_kw()?.value.clone()
4578 };
4579 let mut fields: Vec<(String, String)> = Vec::new();
4580 if self.check(TokenType::LBrace) {
4581 self.advance();
4582 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4583 let col = self.current().value.clone();
4584 self.advance();
4585 if self.check(TokenType::Colon) {
4586 self.advance();
4587 let value = if self.check(TokenType::StringLit) {
4588 self.consume(TokenType::StringLit)?.value.clone()
4589 } else if self.check(TokenType::RBrace)
4590 || self.check(TokenType::Eof)
4591 || self.check(TokenType::Colon)
4592 {
4593 String::new()
4594 } else {
4595 let v = self.current().clone();
4596 self.advance();
4597 v.value.clone()
4598 };
4599 fields.push((col, value));
4600 }
4601 }
4602 if self.check(TokenType::RBrace) {
4603 self.advance();
4604 }
4605 }
4606 Ok(FlowStep::Persist(PersistStep {
4607 store_name: store,
4608 fields,
4609 loc: Loc {
4610 line: tok.line,
4611 column: tok.column,
4612 },
4613 }))
4614 }
4615
4616 fn parse_mutate_step(&mut self) -> Result<FlowStep, ParseError> {
4630 let tok = self.current().clone();
4631 self.advance(); let store = if self.at_declaration_start()
4633 || self.check(TokenType::LBrace)
4634 || self.check(TokenType::RBrace)
4635 || self.check(TokenType::Eof)
4636 {
4637 String::new()
4638 } else {
4639 self.consume_any_ident_or_kw()?.value.clone()
4640 };
4641 let mut where_expr = String::new();
4642 let mut fields: Vec<(String, String)> = Vec::new();
4643 if self.check(TokenType::LBrace) {
4644 self.advance();
4645 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4646 let key = self.current().value.clone();
4647 self.advance();
4648 if self.check(TokenType::Colon) {
4649 self.advance();
4650 if key == "where" {
4651 where_expr =
4652 self.consume(TokenType::StringLit)?.value.clone();
4653 } else {
4654 let value = if self.check(TokenType::StringLit) {
4655 self.consume(TokenType::StringLit)?.value.clone()
4656 } else if self.check(TokenType::RBrace)
4657 || self.check(TokenType::Eof)
4658 || self.check(TokenType::Colon)
4659 {
4660 String::new()
4661 } else {
4662 let v = self.current().clone();
4663 self.advance();
4664 v.value.clone()
4665 };
4666 fields.push((key, value));
4667 }
4668 }
4669 }
4670 if self.check(TokenType::RBrace) {
4671 self.advance();
4672 }
4673 }
4674 Ok(FlowStep::Mutate(MutateStep {
4675 store_name: store,
4676 where_expr,
4677 fields,
4678 loc: Loc {
4679 line: tok.line,
4680 column: tok.column,
4681 },
4682 }))
4683 }
4684
4685 fn parse_agent(&mut self) -> Result<AgentDefinition, ParseError> {
4688 let tok = self.consume(TokenType::Agent)?;
4689 let name = self.consume(TokenType::Identifier)?.value;
4690 let mut node = AgentDefinition {
4691 name,
4692 goal: String::new(),
4693 tools: Vec::new(),
4694 memory_ref: String::new(),
4695 strategy: String::new(),
4696 on_stuck: String::new(),
4697 shield_ref: String::new(),
4698 max_iterations: None,
4699 max_tokens: None,
4700 max_time: String::new(),
4701 max_cost: None,
4702 loc: Loc {
4703 line: tok.line,
4704 column: tok.column,
4705 },
4706 leading_trivia: Vec::new(),
4707 trailing_trivia: Vec::new(),
4708 };
4709 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
4711 self.advance();
4712 }
4713 self.consume(TokenType::LBrace)?;
4714 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4715 let field = self.current().clone();
4716 let field_name = field.value.clone();
4717 self.advance();
4718 if self.check(TokenType::Colon) {
4719 self.advance();
4720 match field_name.as_str() {
4721 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
4722 "tools" => node.tools = self.parse_bracketed_identifiers()?,
4723 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
4724 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
4725 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
4726 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
4727 "max_iterations" => node.max_iterations = self.parse_optional_int(),
4728 "max_tokens" => node.max_tokens = self.parse_optional_int(),
4729 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
4730 "max_cost" => node.max_cost = self.parse_optional_float(),
4731 _ => self.skip_value(),
4732 }
4733 } else if self.check(TokenType::LBrace) {
4734 self.skip_braced_block()?;
4735 }
4736 }
4737 self.consume(TokenType::RBrace)?;
4738 Ok(node)
4739 }
4740
4741 fn parse_extension(&mut self) -> Result<ExtensionDefinition, ParseError> {
4746 let tok = self.consume(TokenType::Extension)?;
4747 let name = self.consume(TokenType::Identifier)?.value;
4748 let mut node = ExtensionDefinition {
4749 name,
4750 category: String::new(),
4751 members: Vec::new(),
4752 loc: Loc {
4753 line: tok.line,
4754 column: tok.column,
4755 },
4756 leading_trivia: Vec::new(),
4757 trailing_trivia: Vec::new(),
4758 };
4759 self.consume(TokenType::LBrace)?;
4760 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4761 let field_name = self.current().value.clone();
4762 self.advance();
4763 if self.check(TokenType::Colon) {
4764 self.advance();
4765 match field_name.as_str() {
4766 "category" => {
4767 node.category = self.consume_any_ident_or_kw()?.value.clone()
4768 }
4769 "members" => node.members = self.parse_extension_members()?,
4770 _ => self.skip_value(),
4771 }
4772 } else if self.check(TokenType::LBrace) {
4773 self.skip_braced_block()?;
4774 }
4775 }
4776 self.consume(TokenType::RBrace)?;
4777 Ok(node)
4778 }
4779
4780 fn parse_extension_members(&mut self) -> Result<Vec<ExtensionMember>, ParseError> {
4784 let mut members = Vec::new();
4785 self.consume(TokenType::LBracket)?;
4786 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4787 let name_tok = self.consume(TokenType::StringLit)?;
4788 let mut member = ExtensionMember {
4789 name: name_tok.value.clone(),
4790 semantics: None,
4791 default_confidence: None,
4792 loc: Loc {
4793 line: name_tok.line,
4794 column: name_tok.column,
4795 },
4796 };
4797 if self.check(TokenType::Colon) {
4799 self.advance();
4800 self.consume(TokenType::LBrace)?;
4801 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4802 let mkey = self.current().value.clone();
4803 self.advance();
4804 if self.check(TokenType::Colon) {
4805 self.advance();
4806 match mkey.as_str() {
4807 "semantics" => {
4808 member.semantics =
4809 Some(self.consume(TokenType::StringLit)?.value.clone())
4810 }
4811 "default_confidence" => {
4812 member.default_confidence = self.parse_optional_float()
4813 }
4814 _ => self.skip_value(),
4815 }
4816 }
4817 if self.check(TokenType::Comma) {
4818 self.advance();
4819 }
4820 }
4821 self.consume(TokenType::RBrace)?;
4822 }
4823 members.push(member);
4824 if self.check(TokenType::Comma) {
4825 self.advance();
4826 }
4827 }
4828 self.consume(TokenType::RBracket)?;
4829 Ok(members)
4830 }
4831
4832 fn parse_shield(&mut self) -> Result<ShieldDefinition, ParseError> {
4833 let tok = self.consume(TokenType::Shield)?;
4834 let name = self.consume(TokenType::Identifier)?.value;
4835 let mut node = ShieldDefinition {
4836 name,
4837 scan: Vec::new(),
4838 strategy: String::new(),
4839 on_breach: String::new(),
4840 severity: String::new(),
4841 quarantine: String::new(),
4842 max_retries: None,
4843 confidence_threshold: None,
4844 allow_tools: Vec::new(),
4845 deny_tools: Vec::new(),
4846 sandbox: None,
4847 redact: Vec::new(),
4848 log: String::new(),
4849 deflect_message: String::new(),
4850 taint: String::new(),
4851 compliance: Vec::new(),
4852 loc: Loc {
4853 line: tok.line,
4854 column: tok.column,
4855 },
4856 leading_trivia: Vec::new(),
4857 trailing_trivia: Vec::new(),
4858 };
4859 self.consume(TokenType::LBrace)?;
4860 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4861 let field_name = self.current().value.clone();
4862 self.advance();
4863 if self.check(TokenType::Colon) {
4864 self.advance();
4865 match field_name.as_str() {
4866 "scan" => node.scan = self.parse_bracketed_identifiers()?,
4867 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
4868 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
4869 "severity" => node.severity = self.consume_any_ident_or_kw()?.value.clone(),
4870 "quarantine" => {
4871 node.quarantine = self.consume(TokenType::StringLit)?.value.clone()
4872 }
4873 "max_retries" => node.max_retries = self.parse_optional_int(),
4874 "confidence_threshold" => {
4875 node.confidence_threshold = self.parse_optional_float()
4876 }
4877 "allow_tools" => node.allow_tools = self.parse_bracketed_identifiers()?,
4878 "deny_tools" => node.deny_tools = self.parse_bracketed_identifiers()?,
4879 "sandbox" => {
4880 node.sandbox = Some(self.consume_any_ident_or_kw()?.value == "true")
4881 }
4882 "redact" => node.redact = self.parse_bracketed_identifiers()?,
4883 "log" => node.log = self.consume_any_ident_or_kw()?.value.clone(),
4884 "deflect_message" => {
4885 node.deflect_message = self.consume(TokenType::StringLit)?.value.clone()
4886 }
4887 "taint" => node.taint = self.consume_any_ident_or_kw()?.value.clone(),
4888 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
4890 _ => self.skip_value(),
4891 }
4892 } else if self.check(TokenType::LBrace) {
4893 self.skip_braced_block()?;
4894 }
4895 }
4896 self.consume(TokenType::RBrace)?;
4897 Ok(node)
4898 }
4899
4900 fn parse_pix(&mut self) -> Result<PixDefinition, ParseError> {
4901 let tok = self.consume(TokenType::Pix)?;
4902 let name = self.consume(TokenType::Identifier)?.value;
4903 let mut node = PixDefinition {
4904 name,
4905 source: String::new(),
4906 depth: None,
4907 branching: None,
4908 model: String::new(),
4909 loc: Loc {
4910 line: tok.line,
4911 column: tok.column,
4912 },
4913 leading_trivia: Vec::new(),
4914 trailing_trivia: Vec::new(),
4915 };
4916 self.consume(TokenType::LBrace)?;
4917 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4918 let field_name = self.current().value.clone();
4919 self.advance();
4920 if self.check(TokenType::Colon) {
4921 self.advance();
4922 match field_name.as_str() {
4923 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
4924 "depth" => node.depth = self.parse_optional_int(),
4925 "branching" => node.branching = self.parse_optional_int(),
4926 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
4927 _ => self.skip_value(),
4928 }
4929 } else if self.check(TokenType::LBrace) {
4930 self.skip_braced_block()?;
4931 }
4932 }
4933 self.consume(TokenType::RBrace)?;
4934 Ok(node)
4935 }
4936
4937 fn parse_ledger(&mut self) -> Result<LedgerDefinition, ParseError> {
4943 let tok = self.consume(TokenType::Ledger)?;
4944 let name = self.consume(TokenType::Identifier)?.value;
4945 let mut node = LedgerDefinition {
4946 name,
4947 source: String::new(),
4948 depth: None,
4949 branching: None,
4950 model: String::new(),
4951 loc: Loc {
4952 line: tok.line,
4953 column: tok.column,
4954 },
4955 leading_trivia: Vec::new(),
4956 trailing_trivia: Vec::new(),
4957 };
4958 self.consume(TokenType::LBrace)?;
4959 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4960 let field_name = self.current().value.clone();
4961 self.advance();
4962 if self.check(TokenType::Colon) {
4963 self.advance();
4964 match field_name.as_str() {
4965 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
4966 "depth" => node.depth = self.parse_optional_int(),
4967 "branching" => node.branching = self.parse_optional_int(),
4968 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
4969 _ => self.skip_value(),
4970 }
4971 } else if self.check(TokenType::LBrace) {
4972 self.skip_braced_block()?;
4973 }
4974 }
4975 self.consume(TokenType::RBrace)?;
4976 Ok(node)
4977 }
4978
4979 fn parse_psyche(&mut self) -> Result<PsycheDefinition, ParseError> {
4980 let tok = self.consume(TokenType::Psyche)?;
4981 let name = self.consume(TokenType::Identifier)?.value;
4982 let mut node = PsycheDefinition {
4983 name,
4984 dimensions: Vec::new(),
4985 manifold_noise: None,
4986 manifold_momentum: None,
4987 safety_constraints: Vec::new(),
4988 quantum_enabled: None,
4989 inference_mode: String::new(),
4990 loc: Loc {
4991 line: tok.line,
4992 column: tok.column,
4993 },
4994 leading_trivia: Vec::new(),
4995 trailing_trivia: Vec::new(),
4996 };
4997 self.consume(TokenType::LBrace)?;
4998 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4999 let field_name = self.current().value.clone();
5000 self.advance();
5001 if self.check(TokenType::Colon) {
5002 self.advance();
5003 match field_name.as_str() {
5004 "dimensions" => node.dimensions = self.parse_bracketed_identifiers()?,
5005 "manifold_noise" => node.manifold_noise = self.parse_optional_float(),
5006 "manifold_momentum" => node.manifold_momentum = self.parse_optional_float(),
5007 "safety_constraints" => {
5008 node.safety_constraints = self.parse_bracketed_identifiers()?
5009 }
5010 "quantum_enabled" => {
5011 node.quantum_enabled = Some(self.consume_any_ident_or_kw()?.value == "true")
5012 }
5013 "inference_mode" => {
5014 node.inference_mode = self.consume_any_ident_or_kw()?.value.clone()
5015 }
5016 _ => self.skip_value(),
5017 }
5018 } else if self.check(TokenType::LBrace) {
5019 self.skip_braced_block()?;
5020 }
5021 }
5022 self.consume(TokenType::RBrace)?;
5023 Ok(node)
5024 }
5025
5026 fn parse_corpus(&mut self) -> Result<CorpusDefinition, ParseError> {
5027 let tok = self.consume(TokenType::Corpus)?;
5028 let name = self.consume(TokenType::Identifier)?.value;
5029 let mut node = CorpusDefinition {
5030 name,
5031 documents: Vec::new(),
5032 relations: Vec::new(),
5033 adaptive: false,
5034 mcp_server: String::new(),
5035 mcp_resource_uri: String::new(),
5036 store_source: None,
5037 loc: Loc {
5038 line: tok.line,
5039 column: tok.column,
5040 },
5041 leading_trivia: Vec::new(),
5042 trailing_trivia: Vec::new(),
5043 };
5044 let mut dynamic = false;
5048 if self.check(TokenType::From) {
5049 self.advance();
5050 if self.check(TokenType::AxonStore) {
5051 self.advance();
5052 dynamic = true;
5053 } else {
5054 self.consume(TokenType::Mcp)?;
5055 self.consume(TokenType::LParen)?;
5056 node.mcp_server = self.consume(TokenType::StringLit)?.value.clone();
5057 self.consume(TokenType::Comma)?;
5058 node.mcp_resource_uri = self.consume(TokenType::StringLit)?.value.clone();
5059 self.consume(TokenType::RParen)?;
5060 return Ok(node);
5061 }
5062 }
5063 self.consume(TokenType::LBrace)?;
5064 let mut src = CorpusStoreSource {
5067 doc_store: String::new(),
5068 doc_id_col: String::new(),
5069 doc_title_col: String::new(),
5070 edge_store: String::new(),
5071 edge_from_col: String::new(),
5072 edge_to_col: String::new(),
5073 edge_type_col: String::new(),
5074 edge_weight_col: String::new(),
5075 loc: Loc {
5076 line: tok.line,
5077 column: tok.column,
5078 },
5079 };
5080 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5081 let field_name = self.current().value.clone();
5082 self.advance();
5083 if self.check(TokenType::Colon) {
5084 self.advance();
5085 match field_name.as_str() {
5086 "documents" if dynamic => {
5088 let (store, cols) = self.parse_corpus_store_mapping(2)?;
5089 src.doc_store = store;
5090 src.doc_id_col = cols[0].clone();
5091 src.doc_title_col = cols[1].clone();
5092 }
5093 "documents" => node.documents = self.parse_bracketed_identifiers()?,
5094 "relations" if dynamic => {
5096 let (store, cols) = self.parse_corpus_store_mapping(4)?;
5097 src.edge_store = store;
5098 src.edge_from_col = cols[0].clone();
5099 src.edge_to_col = cols[1].clone();
5100 src.edge_type_col = cols[2].clone();
5101 src.edge_weight_col = cols[3].clone();
5102 }
5103 "relations" => node.relations = self.parse_corpus_relations()?,
5105 "adaptive" => node.adaptive = self.consume_any_ident_or_kw()?.value == "true",
5107 _ => self.skip_value(),
5108 }
5109 } else if self.check(TokenType::LBrace) {
5110 self.skip_braced_block()?;
5111 }
5112 }
5113 self.consume(TokenType::RBrace)?;
5114 if dynamic {
5115 node.store_source = Some(src);
5116 }
5117 Ok(node)
5118 }
5119
5120 fn parse_corpus_store_mapping(&mut self, n: usize) -> Result<(String, Vec<String>), ParseError> {
5127 let store = self.consume(TokenType::Identifier)?.value.clone();
5128 self.consume(TokenType::LParen)?;
5129 let mut cols = Vec::with_capacity(n);
5130 for i in 0..n {
5131 if i > 0 {
5132 self.consume(TokenType::Comma)?;
5133 }
5134 cols.push(self.consume_any_ident_or_kw()?.value.clone());
5135 }
5136 self.consume(TokenType::RParen)?;
5137 Ok((store, cols))
5138 }
5139
5140 fn parse_corpus_relations(&mut self) -> Result<Vec<CorpusRelation>, ParseError> {
5146 let mut out = Vec::new();
5147 self.consume(TokenType::LBracket)?;
5148 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5149 if self.check(TokenType::Comma) {
5150 self.advance();
5151 continue;
5152 }
5153 let tok = self.current().clone();
5154 let etype = self.consume_any_ident_or_kw()?.value.clone();
5155 self.consume(TokenType::LParen)?;
5156 let from = self.consume_any_ident_or_kw()?.value.clone();
5157 self.consume(TokenType::Comma)?;
5158 let to = self.consume_any_ident_or_kw()?.value.clone();
5159 self.consume(TokenType::Comma)?;
5160 let weight = self.consume_number()?;
5161 self.consume(TokenType::RParen)?;
5162 out.push(CorpusRelation {
5163 etype,
5164 from,
5165 to,
5166 weight,
5167 loc: Loc { line: tok.line, column: tok.column },
5168 });
5169 }
5170 self.consume(TokenType::RBracket)?;
5171 Ok(out)
5172 }
5173
5174 fn parse_dataspace(&mut self) -> Result<DataspaceDefinition, ParseError> {
5175 let tok = self.consume(TokenType::Dataspace)?;
5176 let name = self.consume(TokenType::Identifier)?.value;
5177 let node = DataspaceDefinition {
5178 name,
5179 loc: Loc {
5180 line: tok.line,
5181 column: tok.column,
5182 },
5183 leading_trivia: Vec::new(),
5184 trailing_trivia: Vec::new(),
5185 };
5186 if self.check(TokenType::LBrace) {
5187 self.skip_braced_block()?;
5188 }
5189 Ok(node)
5190 }
5191
5192 fn parse_ots(&mut self) -> Result<OtsDefinition, ParseError> {
5193 let tok = self.consume(TokenType::Ots)?;
5194 let name = self.consume(TokenType::Identifier)?.value;
5195 let mut node = OtsDefinition {
5196 name,
5197 teleology: String::new(),
5198 homotopy_search: String::new(),
5199 loss_function: String::new(),
5200 loc: Loc {
5201 line: tok.line,
5202 column: tok.column,
5203 },
5204 leading_trivia: Vec::new(),
5205 trailing_trivia: Vec::new(),
5206 };
5207 if self.check(TokenType::Lt) {
5209 while !self.check(TokenType::Gt) && !self.check(TokenType::Eof) {
5210 self.advance();
5211 }
5212 if self.check(TokenType::Gt) {
5213 self.advance();
5214 }
5215 }
5216 self.consume(TokenType::LBrace)?;
5217 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5218 let field_name = self.current().value.clone();
5219 self.advance();
5220 if self.check(TokenType::Colon) {
5221 self.advance();
5222 match field_name.as_str() {
5223 "teleology" => {
5224 node.teleology = self.consume(TokenType::StringLit)?.value.clone()
5225 }
5226 "homotopy_search" => {
5227 node.homotopy_search = self.consume_any_ident_or_kw()?.value.clone()
5228 }
5229 "loss_function" => {
5230 node.loss_function = self.consume(TokenType::StringLit)?.value.clone()
5231 }
5232 _ => self.skip_value(),
5233 }
5234 } else if self.check(TokenType::LBrace) {
5235 self.skip_braced_block()?;
5236 }
5237 }
5238 self.consume(TokenType::RBrace)?;
5239 Ok(node)
5240 }
5241
5242 fn parse_mandate(&mut self) -> Result<MandateDefinition, ParseError> {
5243 let tok = self.consume(TokenType::Mandate)?;
5244 let name = self.consume(TokenType::Identifier)?.value;
5245 let mut node = MandateDefinition {
5246 name,
5247 constraint: String::new(),
5248 kp: None,
5249 ki: None,
5250 kd: None,
5251 tolerance: None,
5252 max_steps: None,
5253 on_violation: String::new(),
5254 loc: Loc {
5255 line: tok.line,
5256 column: tok.column,
5257 },
5258 leading_trivia: Vec::new(),
5259 trailing_trivia: Vec::new(),
5260 };
5261 self.consume(TokenType::LBrace)?;
5262 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5263 let field_name = self.current().value.clone();
5264 self.advance();
5265 if self.check(TokenType::Colon) {
5266 self.advance();
5267 match field_name.as_str() {
5268 "constraint" => {
5269 node.constraint = self.consume(TokenType::StringLit)?.value.clone()
5270 }
5271 "kp" | "Kp" => node.kp = self.parse_optional_float(),
5272 "ki" | "Ki" => node.ki = self.parse_optional_float(),
5273 "kd" | "Kd" => node.kd = self.parse_optional_float(),
5274 "tolerance" => node.tolerance = self.parse_optional_float(),
5275 "max_steps" => node.max_steps = self.parse_optional_int(),
5276 "on_violation" => {
5277 node.on_violation = self.consume_any_ident_or_kw()?.value.clone()
5278 }
5279 _ => self.skip_value(),
5280 }
5281 } else if self.check(TokenType::LBrace) {
5282 self.skip_braced_block()?;
5283 }
5284 }
5285 self.consume(TokenType::RBrace)?;
5286 Ok(node)
5287 }
5288
5289 fn parse_compute(&mut self) -> Result<ComputeDefinition, ParseError> {
5290 let tok = self.consume(TokenType::Compute)?;
5291 let name = self.consume(TokenType::Identifier)?.value;
5292 let mut node = ComputeDefinition {
5293 name,
5294 shield_ref: String::new(),
5295 loc: Loc {
5296 line: tok.line,
5297 column: tok.column,
5298 },
5299 leading_trivia: Vec::new(),
5300 trailing_trivia: Vec::new(),
5301 };
5302 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
5304 self.advance();
5305 }
5306 self.consume(TokenType::LBrace)?;
5307 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5308 let field_name = self.current().value.clone();
5309 self.advance();
5310 if self.check(TokenType::Colon) {
5311 self.advance();
5312 match field_name.as_str() {
5313 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
5314 _ => self.skip_value(),
5315 }
5316 } else if self.check(TokenType::LBrace) {
5317 self.skip_braced_block()?;
5318 }
5319 }
5320 self.consume(TokenType::RBrace)?;
5321 Ok(node)
5322 }
5323
5324 fn parse_daemon(&mut self) -> Result<DaemonDefinition, ParseError> {
5325 let tok = self.consume(TokenType::Daemon)?;
5326 let name = self.consume(TokenType::Identifier)?.value;
5327 let mut node = DaemonDefinition {
5328 name,
5329 goal: String::new(),
5330 tools: Vec::new(),
5331 memory_ref: String::new(),
5332 strategy: String::new(),
5333 on_stuck: String::new(),
5334 shield_ref: String::new(),
5335 max_tokens: None,
5336 max_time: String::new(),
5337 max_cost: None,
5338 listeners: Vec::new(),
5339 requires_capabilities: Vec::new(),
5340 loc: Loc {
5341 line: tok.line,
5342 column: tok.column,
5343 },
5344 leading_trivia: Vec::new(),
5345 trailing_trivia: Vec::new(),
5346 };
5347 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
5349 self.advance();
5350 }
5351 self.consume(TokenType::LBrace)?;
5352 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5353 let field = self.current().clone();
5354 let field_name = field.value.clone();
5355 self.advance();
5356 if self.check(TokenType::Colon) {
5357 self.advance();
5358 match field_name.as_str() {
5359 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
5360 "tools" => node.tools = self.parse_bracketed_identifiers()?,
5361 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
5362 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
5363 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
5364 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
5365 "max_tokens" => node.max_tokens = self.parse_optional_int(),
5366 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
5367 "max_cost" => node.max_cost = self.parse_optional_float(),
5368 "requires" => {
5373 let bracket_tok = self.current().clone();
5374 let items = self.parse_bracketed_dot_identifiers()?;
5375 for slug in &items {
5376 if !is_valid_capability_slug(slug) {
5377 return Err(ParseError {
5378 message: format!(
5379 "Invalid capability slug '{slug}' in daemon '{}' \
5380 `requires:`. Capability slugs must match \
5381 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
5382 lowercase identifiers. Examples: `daemon.run`, \
5383 `memory.write`, `flow.execute`.",
5384 node.name
5385 ),
5386 line: bracket_tok.line,
5387 column: bracket_tok.column,
5388 ..Default::default()
5389 });
5390 }
5391 }
5392 node.requires_capabilities = items;
5393 }
5394 _ => self.skip_value(),
5395 }
5396 } else if field.ttype == TokenType::Listen {
5397 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
5403 (self.consume(TokenType::StringLit)?.value.clone(), false)
5404 } else {
5405 (self.consume_any_ident_or_kw()?.value.clone(), true)
5406 };
5407 let mut alias = String::new();
5408 if !self.at_declaration_start()
5409 && !self.check(TokenType::RBrace)
5410 && !self.check(TokenType::LBrace)
5411 {
5412 let next = self.current().clone();
5413 if next.value == "as" || next.ttype == TokenType::As {
5414 self.advance();
5415 alias = self.consume_any_ident_or_kw()?.value.clone();
5416 }
5417 }
5418 let listen_loc = Loc {
5419 line: field.line,
5420 column: field.column,
5421 };
5422 let body = self.parse_listener_body()?;
5426 node.listeners.push(ListenStep {
5427 channel,
5428 channel_is_ref,
5429 event_alias: alias,
5430 body,
5431 loc: listen_loc,
5432 });
5433 } else if self.check(TokenType::LBrace) {
5434 self.skip_braced_block()?;
5435 }
5436 }
5437 self.consume(TokenType::RBrace)?;
5438 Ok(node)
5439 }
5440
5441 fn parse_axonstore(&mut self) -> Result<AxonStoreDefinition, ParseError> {
5442 let tok = self.consume(TokenType::AxonStore)?;
5443 let name = self.consume(TokenType::Identifier)?.value;
5444 let mut node = AxonStoreDefinition {
5445 name,
5446 backend: String::new(),
5447 connection: String::new(),
5448 confidence_floor: None,
5449 isolation: String::new(),
5450 on_breach: String::new(),
5451 capability: String::new(),
5452 column_schema: None,
5453 loc: Loc {
5454 line: tok.line,
5455 column: tok.column,
5456 },
5457 leading_trivia: Vec::new(),
5458 trailing_trivia: Vec::new(),
5459 };
5460 self.consume(TokenType::LBrace)?;
5461 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5462 let field = self.current().clone();
5463 let field_name = field.value.clone();
5464 if field.ttype == TokenType::Schema {
5470 self.advance();
5471 let parsed = self.parse_store_schema_declaration(&node.name, field.line, field.column)?;
5472 node.column_schema = Some(parsed);
5473 continue;
5474 }
5475 self.advance();
5476 if self.check(TokenType::Colon) {
5477 self.advance();
5478 match field_name.as_str() {
5479 "backend" => node.backend = self.consume_any_ident_or_kw()?.value.clone(),
5480 "connection" => {
5481 node.connection = self.consume(TokenType::StringLit)?.value.clone()
5482 }
5483 "confidence_floor" => node.confidence_floor = self.parse_optional_float(),
5484 "isolation" => node.isolation = self.consume_any_ident_or_kw()?.value.clone(),
5485 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
5486 "capability" => {
5490 let slug_tok = self.consume(TokenType::StringLit)?.clone();
5491 if !is_valid_capability_slug(&slug_tok.value) {
5492 return Err(ParseError {
5493 message: format!(
5494 "Invalid capability slug '{}' in axonstore '{}' \
5495 `capability:`. Capability slugs must match \
5496 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
5497 lowercase identifiers starting with a letter. Examples: \
5498 `admin`, `tenant.read`, `hipaa.phi.read`.",
5499 slug_tok.value, node.name
5500 ),
5501 line: slug_tok.line,
5502 column: slug_tok.column,
5503 ..Default::default()
5504 });
5505 }
5506 node.capability = slug_tok.value.clone();
5507 }
5508 _ => self.skip_value(),
5509 }
5510 } else if self.check(TokenType::LBrace) {
5511 self.skip_braced_block()?;
5512 }
5513 }
5514 self.consume(TokenType::RBrace)?;
5515 Ok(node)
5516 }
5517
5518 fn parse_store_schema_declaration(
5530 &mut self,
5531 store_name: &str,
5532 sch_line: u32,
5533 sch_col: u32,
5534 ) -> Result<crate::store_schema::StoreColumnSchema, ParseError> {
5535 use crate::store_schema::{StoreColumn, StoreColumnSchema, StoreColumnType};
5536
5537 if self.check(TokenType::LBrace) {
5539 self.consume(TokenType::LBrace)?;
5540 let mut columns: Vec<StoreColumn> = Vec::new();
5541 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5542 let col_tok = self.current().clone();
5543 let col_name = self.consume_any_ident_or_kw()?.value.clone();
5544 self.consume(TokenType::Colon)?;
5545 let type_tok = self.consume_any_ident_or_kw()?.clone();
5546 let col_type = StoreColumnType::from_token(&type_tok.value).ok_or_else(|| {
5547 let names = StoreColumnType::all_canonical_names();
5548 let suggestion =
5549 crate::smart_suggest::suggest_for(&type_tok.value, &names);
5550 let suggest_suffix = if suggestion.is_empty() {
5551 String::new()
5552 } else {
5553 format!(" {suggestion}")
5554 };
5555 let known = names.join(", ");
5556 ParseError {
5557 message: format!(
5558 "Unknown column type `{}` for column `{}` in \
5559 axonstore `{}` `schema:` block. The closed \
5560 v1.38.0 column-type catalog (Fase 38.b D1) \
5561 is {{{known}}} (plus common lowercase \
5562 aliases — `int`/`integer`/`int4` for \
5563 `Int`, `bool`/`boolean` for `Bool`, etc.).\
5564 {suggest_suffix}",
5565 type_tok.value, col_name, store_name
5566 ),
5567 line: type_tok.line,
5568 column: type_tok.column,
5569 ..Default::default()
5570 }
5571 })?;
5572
5573 let mut col = StoreColumn {
5574 name: col_name,
5575 col_type,
5576 primary_key: false,
5577 auto_increment: false,
5578 not_null: false,
5579 unique: false,
5580 default_value: String::new(),
5581 identity: false,
5586 line: col_tok.line,
5587 column: col_tok.column,
5588 };
5589
5590 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5593 if self.current().ttype != TokenType::Identifier {
5594 break;
5597 }
5598 let constraint = self.current().value.clone();
5599 match constraint.as_str() {
5600 "primary_key" => {
5601 col.primary_key = true;
5602 self.advance();
5603 }
5604 "auto_increment" => {
5605 col.auto_increment = true;
5606 self.advance();
5607 }
5608 "not_null" => {
5609 col.not_null = true;
5610 self.advance();
5611 }
5612 "unique" => {
5613 col.unique = true;
5614 self.advance();
5615 }
5616 "identity" => {
5627 col.identity = true;
5628 self.advance();
5629 }
5630 "default" => {
5631 self.advance();
5632 let dv = self.current().clone();
5633 if matches!(
5634 dv.ttype,
5635 TokenType::StringLit
5636 | TokenType::Integer
5637 | TokenType::Float
5638 ) {
5639 col.default_value = dv.value.clone();
5640 self.advance();
5641 } else {
5642 col.default_value =
5643 self.consume_any_ident_or_kw()?.value.clone();
5644 }
5645 }
5646 _ => break,
5647 }
5648 }
5649
5650 columns.push(col);
5651 }
5652 self.consume(TokenType::RBrace)?;
5653 return Ok(StoreColumnSchema::Inline {
5654 columns,
5655 leading_trivia: Vec::new(),
5656 line: sch_line,
5657 column: sch_col,
5658 });
5659 }
5660
5661 if !self.check(TokenType::Colon) {
5663 let cur = self.current().clone();
5664 return Err(ParseError {
5665 message: format!(
5666 "axonstore `{store_name}` `schema:` declaration expects \
5667 `{{ … }}` (inline columns), `: \"manifest.ref\"` \
5668 (manifest reference), or `: env:VAR` (per-tenant schema \
5669 namespace). Got `{}` instead.",
5670 cur.value
5671 ),
5672 line: cur.line,
5673 column: cur.column,
5674 ..Default::default()
5675 });
5676 }
5677 self.consume(TokenType::Colon)?;
5678
5679 if self.check(TokenType::StringLit) {
5681 let lit = self.consume(TokenType::StringLit)?.clone();
5682 let value = lit.value.clone();
5683 if let Some(var) = value.strip_prefix("env:") {
5684 let var = var.trim();
5685 if var.is_empty() {
5686 return Err(ParseError {
5687 message: format!(
5688 "axonstore `{store_name}` `schema: \"env:\"` is \
5689 missing the variable name after the `env:` \
5690 prefix."
5691 ),
5692 line: lit.line,
5693 column: lit.column,
5694 ..Default::default()
5695 });
5696 }
5697 return Ok(StoreColumnSchema::EnvVar {
5698 var_name: var.to_string(),
5699 line: sch_line,
5700 column: sch_col,
5701 });
5702 }
5703 if value.trim().is_empty() {
5705 return Err(ParseError {
5706 message: format!(
5707 "axonstore `{store_name}` `schema:` manifest reference \
5708 is empty. Expected `\"qualified.name\"` — e.g. \
5709 `\"public.tenants\"`."
5710 ),
5711 line: lit.line,
5712 column: lit.column,
5713 ..Default::default()
5714 });
5715 }
5716 return Ok(StoreColumnSchema::ManifestRef {
5717 qualified_name: value,
5718 line: sch_line,
5719 column: sch_col,
5720 });
5721 }
5722
5723 let env_tok = self.current().clone();
5726 if env_tok.value == "env" {
5727 self.advance();
5728 if !self.check(TokenType::Colon) {
5729 return Err(ParseError {
5730 message: format!(
5731 "axonstore `{store_name}` `schema: env` is missing the \
5732 `:` separator. Expected `schema: env:VAR`."
5733 ),
5734 line: env_tok.line,
5735 column: env_tok.column,
5736 ..Default::default()
5737 });
5738 }
5739 self.advance(); let var_tok = self.consume_any_ident_or_kw()?.clone();
5741 if var_tok.value.trim().is_empty() {
5742 return Err(ParseError {
5743 message: format!(
5744 "axonstore `{store_name}` `schema: env:` is missing \
5745 the variable name."
5746 ),
5747 line: var_tok.line,
5748 column: var_tok.column,
5749 ..Default::default()
5750 });
5751 }
5752 return Ok(StoreColumnSchema::EnvVar {
5753 var_name: var_tok.value.clone(),
5754 line: sch_line,
5755 column: sch_col,
5756 });
5757 }
5758
5759 Err(ParseError {
5760 message: format!(
5761 "axonstore `{store_name}` `schema:` declaration expects \
5762 `{{ … }}` (inline columns), `\"manifest.ref\"` (manifest \
5763 reference), or `env:VAR` (per-tenant schema namespace). \
5764 Got `{}` instead.",
5765 env_tok.value
5766 ),
5767 line: env_tok.line,
5768 column: env_tok.column,
5769 ..Default::default()
5770 })
5771 }
5772
5773 fn parse_resource(&mut self) -> Result<ResourceDefinition, ParseError> {
5780 let tok = self.consume(TokenType::Resource)?;
5781 let name = self.consume(TokenType::Identifier)?.value;
5782 let mut node = ResourceDefinition {
5783 name,
5784 kind: String::new(),
5785 endpoint: String::new(),
5786 capacity: None,
5787 lifetime: "affine".to_string(),
5788 certainty_floor: None,
5789 shield_ref: String::new(),
5790 loc: Loc {
5791 line: tok.line,
5792 column: tok.column,
5793 },
5794 leading_trivia: Vec::new(),
5795 trailing_trivia: Vec::new(),
5796 };
5797 self.consume(TokenType::LBrace)?;
5798 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5799 let field_tok = self.current().clone();
5800 let field_name = field_tok.value.clone();
5801 self.advance();
5802 if !self.check(TokenType::Colon) {
5803 if self.check(TokenType::LBrace) {
5805 self.skip_braced_block()?;
5806 }
5807 continue;
5808 }
5809 self.advance(); match field_name.as_str() {
5811 "kind" => node.kind = self.consume_any_ident_or_kw()?.value,
5812 "endpoint" => node.endpoint = self.consume(TokenType::StringLit)?.value,
5813 "capacity" => {
5814 node.capacity = self.parse_optional_int();
5815 }
5816 "lifetime" => {
5817 let lt_tok = self.consume_any_ident_or_kw()?;
5818 let lt = lt_tok.value;
5819 if !matches!(lt.as_str(), "linear" | "affine" | "persistent") {
5820 return Err(ParseError {
5821 message: format!(
5822 "Invalid lifetime '{lt}' in resource '{}' — \
5823 expected linear | affine | persistent",
5824 node.name
5825 ),
5826 line: lt_tok.line,
5827 column: lt_tok.column,
5828 ..Default::default()
5829 });
5830 }
5831 node.lifetime = lt;
5832 }
5833 "certainty_floor" => {
5834 node.certainty_floor = self.parse_optional_float();
5835 }
5836 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
5837 _ => self.skip_value(),
5838 }
5839 }
5840 self.consume(TokenType::RBrace)?;
5841 Ok(node)
5842 }
5843
5844 fn parse_fabric(&mut self) -> Result<FabricDefinition, ParseError> {
5846 let tok = self.consume(TokenType::Fabric)?;
5847 let name = self.consume(TokenType::Identifier)?.value;
5848 let mut node = FabricDefinition {
5849 name,
5850 provider: String::new(),
5851 region: String::new(),
5852 zones: None,
5853 ephemeral: None,
5854 shield_ref: String::new(),
5855 loc: Loc {
5856 line: tok.line,
5857 column: tok.column,
5858 },
5859 leading_trivia: Vec::new(),
5860 trailing_trivia: Vec::new(),
5861 };
5862 self.consume(TokenType::LBrace)?;
5863 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5864 let field_name = self.current().value.clone();
5865 self.advance();
5866 if !self.check(TokenType::Colon) {
5867 if self.check(TokenType::LBrace) {
5868 self.skip_braced_block()?;
5869 }
5870 continue;
5871 }
5872 self.advance(); match field_name.as_str() {
5874 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
5875 "region" => node.region = self.consume(TokenType::StringLit)?.value,
5876 "zones" => node.zones = self.parse_optional_int(),
5877 "ephemeral" => {
5878 let b = self.parse_bool()?;
5879 node.ephemeral = Some(b);
5880 }
5881 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
5882 _ => self.skip_value(),
5883 }
5884 }
5885 self.consume(TokenType::RBrace)?;
5886 Ok(node)
5887 }
5888
5889 fn parse_manifest(&mut self) -> Result<ManifestDefinition, ParseError> {
5891 let tok = self.consume(TokenType::Manifest)?;
5892 let name = self.consume(TokenType::Identifier)?.value;
5893 let mut node = ManifestDefinition {
5894 name,
5895 resources: Vec::new(),
5896 fabric_ref: String::new(),
5897 region: String::new(),
5898 zones: None,
5899 compliance: Vec::new(),
5900 loc: Loc {
5901 line: tok.line,
5902 column: tok.column,
5903 },
5904 leading_trivia: Vec::new(),
5905 trailing_trivia: Vec::new(),
5906 };
5907 self.consume(TokenType::LBrace)?;
5908 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5909 let field_name = self.current().value.clone();
5910 self.advance();
5911 if !self.check(TokenType::Colon) {
5912 if self.check(TokenType::LBrace) {
5913 self.skip_braced_block()?;
5914 }
5915 continue;
5916 }
5917 self.advance();
5918 match field_name.as_str() {
5919 "resources" => node.resources = self.parse_bracketed_identifiers()?,
5920 "fabric" => node.fabric_ref = self.consume_any_ident_or_kw()?.value,
5921 "region" => node.region = self.consume(TokenType::StringLit)?.value,
5922 "zones" => node.zones = self.parse_optional_int(),
5923 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
5924 _ => self.skip_value(),
5925 }
5926 }
5927 self.consume(TokenType::RBrace)?;
5928 Ok(node)
5929 }
5930
5931 fn parse_observe(&mut self) -> Result<ObserveDefinition, ParseError> {
5933 let tok = self.consume(TokenType::Observe)?;
5934 let name = self.consume(TokenType::Identifier)?.value;
5935 self.consume(TokenType::From)?;
5937 let target = self.consume(TokenType::Identifier)?.value;
5938 let mut node = ObserveDefinition {
5939 name,
5940 target,
5941 sources: Vec::new(),
5942 quorum: None,
5943 timeout: String::new(),
5944 on_partition: "fail".to_string(),
5945 certainty_floor: None,
5946 loc: Loc {
5947 line: tok.line,
5948 column: tok.column,
5949 },
5950 leading_trivia: Vec::new(),
5951 trailing_trivia: Vec::new(),
5952 };
5953 self.consume(TokenType::LBrace)?;
5954 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5955 let field_name = self.current().value.clone();
5956 self.advance();
5957 if !self.check(TokenType::Colon) {
5958 if self.check(TokenType::LBrace) {
5959 self.skip_braced_block()?;
5960 }
5961 continue;
5962 }
5963 self.advance();
5964 match field_name.as_str() {
5965 "sources" => node.sources = self.parse_bracketed_identifiers()?,
5966 "quorum" => node.quorum = self.parse_optional_int(),
5967 "timeout" => {
5968 let t = self.current().clone();
5969 match t.ttype {
5970 TokenType::Duration | TokenType::StringLit => {
5971 self.advance();
5972 node.timeout = t.value;
5973 }
5974 _ => node.timeout = self.consume_any_ident_or_kw()?.value,
5975 }
5976 }
5977 "on_partition" => {
5978 let p_tok = self.consume_any_ident_or_kw()?;
5979 let p = p_tok.value;
5980 if !matches!(p.as_str(), "fail" | "shield_quarantine") {
5981 return Err(ParseError {
5982 message: format!(
5983 "Invalid on_partition '{p}' in observe '{}' — \
5984 expected fail | shield_quarantine",
5985 node.name
5986 ),
5987 line: p_tok.line,
5988 column: p_tok.column,
5989 ..Default::default()
5990 });
5991 }
5992 node.on_partition = p;
5993 }
5994 "certainty_floor" => node.certainty_floor = self.parse_optional_float(),
5995 _ => self.skip_value(),
5996 }
5997 }
5998 self.consume(TokenType::RBrace)?;
5999 Ok(node)
6000 }
6001
6002 fn parse_reconcile(&mut self) -> Result<ReconcileDefinition, ParseError> {
6006 let tok = self.consume(TokenType::Reconcile)?;
6007 let name = self.consume(TokenType::Identifier)?.value;
6008 let mut node = ReconcileDefinition {
6009 name,
6010 observe_ref: String::new(),
6011 threshold: None,
6012 tolerance: None,
6013 on_drift: "provision".to_string(),
6014 shield_ref: String::new(),
6015 mandate_ref: String::new(),
6016 max_retries: 3,
6017 loc: Loc {
6018 line: tok.line,
6019 column: tok.column,
6020 },
6021 leading_trivia: Vec::new(),
6022 trailing_trivia: Vec::new(),
6023 };
6024 self.consume(TokenType::LBrace)?;
6025 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6026 let field_name = self.current().value.clone();
6027 self.advance();
6028 if !self.check(TokenType::Colon) {
6029 if self.check(TokenType::LBrace) {
6030 self.skip_braced_block()?;
6031 }
6032 continue;
6033 }
6034 self.advance();
6035 match field_name.as_str() {
6036 "observe" => node.observe_ref = self.consume_any_ident_or_kw()?.value,
6037 "threshold" => node.threshold = self.parse_optional_float(),
6038 "tolerance" => node.tolerance = self.parse_optional_float(),
6039 "on_drift" => {
6040 let d_tok = self.consume_any_ident_or_kw()?;
6041 let d = d_tok.value;
6042 if !matches!(d.as_str(), "provision" | "alert" | "refine") {
6043 return Err(ParseError {
6044 message: format!(
6045 "Invalid on_drift '{d}' in reconcile '{}' — \
6046 expected provision | alert | refine",
6047 node.name
6048 ),
6049 line: d_tok.line,
6050 column: d_tok.column,
6051 ..Default::default()
6052 });
6053 }
6054 node.on_drift = d;
6055 }
6056 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
6057 "mandate" => node.mandate_ref = self.consume_any_ident_or_kw()?.value,
6058 "max_retries" => {
6059 if let Some(v) = self.parse_optional_int() {
6060 node.max_retries = v;
6061 }
6062 }
6063 _ => self.skip_value(),
6064 }
6065 }
6066 self.consume(TokenType::RBrace)?;
6067 Ok(node)
6068 }
6069
6070 fn parse_lease(&mut self) -> Result<LeaseDefinition, ParseError> {
6072 let tok = self.consume(TokenType::Lease)?;
6073 let name = self.consume(TokenType::Identifier)?.value;
6074 let mut node = LeaseDefinition {
6075 name,
6076 resource_ref: String::new(),
6077 duration: String::new(),
6078 acquire: "on_start".to_string(),
6079 on_expire: "anchor_breach".to_string(),
6080 loc: Loc {
6081 line: tok.line,
6082 column: tok.column,
6083 },
6084 leading_trivia: Vec::new(),
6085 trailing_trivia: Vec::new(),
6086 };
6087 self.consume(TokenType::LBrace)?;
6088 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6089 let field_name = self.current().value.clone();
6090 self.advance();
6091 if !self.check(TokenType::Colon) {
6092 if self.check(TokenType::LBrace) {
6093 self.skip_braced_block()?;
6094 }
6095 continue;
6096 }
6097 self.advance();
6098 match field_name.as_str() {
6099 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
6100 "duration" => {
6101 let t = self.current().clone();
6102 match t.ttype {
6103 TokenType::Duration | TokenType::StringLit => {
6104 self.advance();
6105 node.duration = t.value;
6106 }
6107 _ => node.duration = self.consume_any_ident_or_kw()?.value,
6108 }
6109 }
6110 "acquire" => {
6111 let a_tok = self.consume_any_ident_or_kw()?;
6112 let a = a_tok.value;
6113 if !matches!(a.as_str(), "on_start" | "on_demand") {
6114 return Err(ParseError {
6115 message: format!(
6116 "Invalid acquire '{a}' in lease '{}' — \
6117 expected on_start | on_demand",
6118 node.name
6119 ),
6120 line: a_tok.line,
6121 column: a_tok.column,
6122 ..Default::default()
6123 });
6124 }
6125 node.acquire = a;
6126 }
6127 "on_expire" => {
6128 let e_tok = self.consume_any_ident_or_kw()?;
6129 let e = e_tok.value;
6130 if !matches!(e.as_str(), "anchor_breach" | "release" | "extend") {
6131 return Err(ParseError {
6132 message: format!(
6133 "Invalid on_expire '{e}' in lease '{}' — \
6134 expected anchor_breach | release | extend",
6135 node.name
6136 ),
6137 line: e_tok.line,
6138 column: e_tok.column,
6139 ..Default::default()
6140 });
6141 }
6142 node.on_expire = e;
6143 }
6144 _ => self.skip_value(),
6145 }
6146 }
6147 self.consume(TokenType::RBrace)?;
6148 Ok(node)
6149 }
6150
6151 fn parse_ensemble(&mut self) -> Result<EnsembleDefinition, ParseError> {
6153 let tok = self.consume(TokenType::Ensemble)?;
6154 let name = self.consume(TokenType::Identifier)?.value;
6155 let mut node = EnsembleDefinition {
6156 name,
6157 observations: Vec::new(),
6158 quorum: None,
6159 aggregation: "majority".to_string(),
6160 certainty_mode: "min".to_string(),
6161 loc: Loc {
6162 line: tok.line,
6163 column: tok.column,
6164 },
6165 leading_trivia: Vec::new(),
6166 trailing_trivia: Vec::new(),
6167 };
6168 self.consume(TokenType::LBrace)?;
6169 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6170 let field_name = self.current().value.clone();
6171 self.advance();
6172 if !self.check(TokenType::Colon) {
6173 if self.check(TokenType::LBrace) {
6174 self.skip_braced_block()?;
6175 }
6176 continue;
6177 }
6178 self.advance();
6179 match field_name.as_str() {
6180 "observations" => node.observations = self.parse_bracketed_identifiers()?,
6181 "quorum" => node.quorum = self.parse_optional_int(),
6182 "aggregation" => {
6183 let a_tok = self.consume_any_ident_or_kw()?;
6184 let a = a_tok.value;
6185 if !matches!(a.as_str(), "majority" | "weighted" | "byzantine") {
6186 return Err(ParseError {
6187 message: format!(
6188 "Invalid aggregation '{a}' in ensemble '{}' — \
6189 expected majority | weighted | byzantine",
6190 node.name
6191 ),
6192 line: a_tok.line,
6193 column: a_tok.column,
6194 ..Default::default()
6195 });
6196 }
6197 node.aggregation = a;
6198 }
6199 "certainty_mode" => {
6200 let c_tok = self.consume_any_ident_or_kw()?;
6201 let c = c_tok.value;
6202 if !matches!(c.as_str(), "min" | "weighted" | "harmonic") {
6203 return Err(ParseError {
6204 message: format!(
6205 "Invalid certainty_mode '{c}' in ensemble '{}' — \
6206 expected min | weighted | harmonic",
6207 node.name
6208 ),
6209 line: c_tok.line,
6210 column: c_tok.column,
6211 ..Default::default()
6212 });
6213 }
6214 node.certainty_mode = c;
6215 }
6216 _ => self.skip_value(),
6217 }
6218 }
6219 self.consume(TokenType::RBrace)?;
6220 Ok(node)
6221 }
6222
6223 fn parse_session_definition(&mut self) -> Result<SessionDefinition, ParseError> {
6231 let tok = self.consume(TokenType::Session)?;
6232 let name = self.consume(TokenType::Identifier)?.value;
6233 let mut node = SessionDefinition {
6234 name,
6235 roles: Vec::new(),
6236 loc: Loc {
6237 line: tok.line,
6238 column: tok.column,
6239 },
6240 leading_trivia: Vec::new(),
6241 trailing_trivia: Vec::new(),
6242 };
6243 self.consume(TokenType::LBrace)?;
6244 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6245 let role_tok = self.consume_any_ident_or_kw()?;
6246 self.consume(TokenType::Colon)?;
6247 let steps = self.parse_session_steps()?;
6248 node.roles.push(SessionRole {
6249 name: role_tok.value,
6250 steps,
6251 loc: Loc {
6252 line: role_tok.line,
6253 column: role_tok.column,
6254 },
6255 });
6256 }
6257 self.consume(TokenType::RBrace)?;
6258 Ok(node)
6259 }
6260
6261 fn parse_observable(&mut self) -> Result<ObservableDefinition, ParseError> {
6274 let tok = self.consume(TokenType::Observable)?;
6275 let name = self.consume(TokenType::Identifier)?.value;
6276 let mut node = ObservableDefinition {
6277 name,
6278 qubits: None,
6279 terms: Vec::new(),
6280 loc: Loc {
6281 line: tok.line,
6282 column: tok.column,
6283 },
6284 leading_trivia: Vec::new(),
6285 trailing_trivia: Vec::new(),
6286 };
6287 self.consume(TokenType::LBrace)?;
6288 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6289 let key_tok = self.consume_any_ident_or_kw()?;
6290 self.consume(TokenType::Colon)?;
6291 match key_tok.value.as_str() {
6292 "qubits" => node.qubits = Some(self.consume_number()? as i64),
6293 "term" => {
6294 let term_loc = Loc {
6295 line: key_tok.line,
6296 column: key_tok.column,
6297 };
6298 let mut negative = false;
6300 if self.check(TokenType::Minus) {
6301 self.advance();
6302 negative = true;
6303 } else if self.check(TokenType::Plus) {
6304 self.advance();
6305 }
6306 let mag = self.consume_number()?;
6307 let coefficient = if negative { -mag } else { mag };
6308 self.consume(TokenType::Star)?;
6310 let pauli = self.consume(TokenType::StringLit)?.value;
6311 node.terms.push(PauliTerm {
6312 coefficient,
6313 pauli,
6314 loc: term_loc,
6315 });
6316 }
6317 _ => self.skip_value(),
6318 }
6319 }
6320 self.consume(TokenType::RBrace)?;
6321 Ok(node)
6322 }
6323
6324 fn parse_witness(&mut self) -> Result<WitnessDefinition, ParseError> {
6332 let tok = self.consume(TokenType::Witness)?;
6333 let name = self.consume(TokenType::Identifier)?.value;
6334 let mut node = WitnessDefinition {
6335 name,
6336 claim: String::new(),
6337 baseline: String::new(),
6338 metric: String::new(),
6339 threshold: 0.0,
6340 data: String::new(),
6341 loc: Loc {
6342 line: tok.line,
6343 column: tok.column,
6344 },
6345 leading_trivia: Vec::new(),
6346 trailing_trivia: Vec::new(),
6347 };
6348 self.consume(TokenType::LBrace)?;
6349 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6350 let key_tok = self.consume_any_ident_or_kw()?;
6351 self.consume(TokenType::Colon)?;
6352 match key_tok.value.as_str() {
6353 "claim" => node.claim = self.consume_any_ident_or_kw()?.value,
6354 "against" => node.baseline = self.consume_any_ident_or_kw()?.value,
6357 "metric" => node.metric = self.consume_any_ident_or_kw()?.value,
6358 "threshold" => node.threshold = self.consume_number()?,
6359 "data" => node.data = self.consume_any_ident_or_kw()?.value,
6360 _ => self.skip_value(),
6361 }
6362 }
6363 self.consume(TokenType::RBrace)?;
6364 Ok(node)
6365 }
6366
6367 fn parse_socket(&mut self) -> Result<SocketDefinition, ParseError> {
6372 let tok = self.consume(TokenType::Socket)?;
6373 let name = self.consume(TokenType::Identifier)?.value;
6374 let mut node = SocketDefinition {
6375 name,
6376 loc: Loc { line: tok.line, column: tok.column },
6377 ..Default::default()
6378 };
6379 self.consume(TokenType::LBrace)?;
6380 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6381 let key = self.consume_any_ident_or_kw()?.value;
6382 self.consume(TokenType::Colon)?;
6383 match key.as_str() {
6384 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
6385 "backpressure" => {
6386 let kind = self.consume_any_ident_or_kw()?.value;
6388 if kind != "credit" {
6389 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
6390 }
6391 self.consume(TokenType::LParen)?;
6392 let n = self
6393 .consume(TokenType::Integer)?
6394 .value
6395 .parse::<i64>()
6396 .map_err(|_| self.error("backpressure credit must be an integer"))?;
6397 self.consume(TokenType::RParen)?;
6398 node.backpressure_credit = Some(n);
6399 }
6400 "reconnect" => {
6401 let mode = self.consume_any_ident_or_kw()?.value;
6402 node.reconnect = mode == "cognitive_state";
6403 }
6404 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
6405 other => return Err(self.error(&format!("unknown socket field `{other}`"))),
6406 }
6407 if self.check(TokenType::Comma) {
6409 self.consume(TokenType::Comma)?;
6410 }
6411 }
6412 self.consume(TokenType::RBrace)?;
6413 Ok(node)
6414 }
6415
6416 fn parse_session_steps(&mut self) -> Result<Vec<SessionStep>, ParseError> {
6418 self.consume(TokenType::LBracket)?;
6419 let mut steps = Vec::new();
6420 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
6421 steps.push(self.parse_session_step()?);
6422 if self.check(TokenType::Comma) {
6423 self.advance();
6424 }
6425 }
6426 self.consume(TokenType::RBracket)?;
6427 Ok(steps)
6428 }
6429
6430 fn parse_session_step(&mut self) -> Result<SessionStep, ParseError> {
6431 let tok = self.current().clone();
6432 let loc = Loc { line: tok.line, column: tok.column };
6433 match tok.ttype {
6434 TokenType::Send => {
6435 self.advance();
6436 let msg = self.consume_any_ident_or_kw()?;
6437 Ok(SessionStep { op: "send".into(), message_type: msg.value, loc, ..Default::default() })
6438 }
6439 TokenType::Receive => {
6440 self.advance();
6441 let msg = self.consume_any_ident_or_kw()?;
6442 Ok(SessionStep { op: "receive".into(), message_type: msg.value, loc, ..Default::default() })
6443 }
6444 TokenType::Loop => {
6445 self.advance();
6446 Ok(SessionStep { op: "loop".into(), loc, ..Default::default() })
6447 }
6448 TokenType::End => {
6449 self.advance();
6450 Ok(SessionStep { op: "end".into(), loc, ..Default::default() })
6451 }
6452 TokenType::Identifier if tok.value == "select" || tok.value == "branch" => {
6455 self.parse_session_choice(&tok.value, loc)
6456 }
6457 _ => Err(ParseError {
6458 message: format!(
6459 "Invalid session step '{}' — expected send | receive | loop | end | select | branch",
6460 tok.value
6461 ),
6462 line: tok.line,
6463 column: tok.column,
6464 ..Default::default()
6465 }),
6466 }
6467 }
6468
6469 fn parse_session_choice(&mut self, op: &str, loc: Loc) -> Result<SessionStep, ParseError> {
6472 self.advance(); self.consume(TokenType::LBrace)?;
6474 let mut branches = Vec::new();
6475 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6476 let label_tok = self.consume_any_ident_or_kw()?;
6477 self.consume(TokenType::Colon)?;
6478 let steps = self.parse_session_steps()?;
6479 branches.push(SessionBranch {
6480 label: label_tok.value,
6481 steps,
6482 loc: Loc { line: label_tok.line, column: label_tok.column },
6483 });
6484 if self.check(TokenType::Comma) {
6485 self.advance();
6486 }
6487 }
6488 self.consume(TokenType::RBrace)?;
6489 Ok(SessionStep { op: op.to_string(), branches, loc, ..Default::default() })
6490 }
6491
6492 fn parse_topology(&mut self) -> Result<TopologyDefinition, ParseError> {
6494 let tok = self.consume(TokenType::Topology)?;
6495 let name = self.consume(TokenType::Identifier)?.value;
6496 let mut node = TopologyDefinition {
6497 name,
6498 nodes: Vec::new(),
6499 edges: Vec::new(),
6500 loc: Loc {
6501 line: tok.line,
6502 column: tok.column,
6503 },
6504 leading_trivia: Vec::new(),
6505 trailing_trivia: Vec::new(),
6506 };
6507 self.consume(TokenType::LBrace)?;
6508 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6509 let field_name = self.current().value.clone();
6510 self.advance();
6511 if !self.check(TokenType::Colon) {
6512 if self.check(TokenType::LBrace) {
6513 self.skip_braced_block()?;
6514 }
6515 continue;
6516 }
6517 self.advance();
6518 match field_name.as_str() {
6519 "nodes" => node.nodes = self.parse_bracketed_identifiers()?,
6520 "edges" => node.edges = self.parse_topology_edges()?,
6521 _ => self.skip_value(),
6522 }
6523 }
6524 self.consume(TokenType::RBrace)?;
6525 Ok(node)
6526 }
6527
6528 fn parse_topology_edges(&mut self) -> Result<Vec<TopologyEdge>, ParseError> {
6529 self.consume(TokenType::LBracket)?;
6530 let mut edges = Vec::new();
6531 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
6532 edges.push(self.parse_topology_edge()?);
6533 if self.check(TokenType::Comma) {
6534 self.advance();
6535 }
6536 }
6537 self.consume(TokenType::RBracket)?;
6538 Ok(edges)
6539 }
6540
6541 fn parse_topology_edge(&mut self) -> Result<TopologyEdge, ParseError> {
6542 let src_tok = self.consume_any_ident_or_kw()?;
6543 self.consume(TokenType::Arrow)?;
6544 let tgt_tok = self.consume_any_ident_or_kw()?;
6545 self.consume(TokenType::Colon)?;
6546 let sess_tok = self.consume_any_ident_or_kw()?;
6547 Ok(TopologyEdge {
6548 source: src_tok.value,
6549 target: tgt_tok.value,
6550 session_ref: sess_tok.value,
6551 loc: Loc {
6552 line: src_tok.line,
6553 column: src_tok.column,
6554 },
6555 })
6556 }
6557
6558 fn parse_immune(&mut self) -> Result<ImmuneDefinition, ParseError> {
6562 let tok = self.consume(TokenType::Immune)?;
6563 let name = self.consume(TokenType::Identifier)?.value;
6564 let mut node = ImmuneDefinition {
6565 name,
6566 watch: Vec::new(),
6567 sensitivity: None,
6568 baseline: "learned".to_string(),
6569 window: 100,
6570 scope: String::new(),
6571 tau: String::new(),
6572 decay: "exponential".to_string(),
6573 loc: Loc {
6574 line: tok.line,
6575 column: tok.column,
6576 },
6577 leading_trivia: Vec::new(),
6578 trailing_trivia: Vec::new(),
6579 };
6580 self.consume(TokenType::LBrace)?;
6581 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6582 let field_name = self.current().value.clone();
6583 self.advance();
6584 if !self.check(TokenType::Colon) {
6585 if self.check(TokenType::LBrace) {
6586 self.skip_braced_block()?;
6587 }
6588 continue;
6589 }
6590 self.advance();
6591 match field_name.as_str() {
6592 "watch" => node.watch = self.parse_bracketed_identifiers()?,
6593 "sensitivity" => node.sensitivity = self.parse_optional_float(),
6594 "baseline" => node.baseline = self.consume_any_ident_or_kw()?.value,
6595 "window" => {
6596 if let Some(v) = self.parse_optional_int() {
6597 node.window = v;
6598 }
6599 }
6600 "scope" => {
6601 let s_tok = self.consume_any_ident_or_kw()?;
6602 let s = s_tok.value;
6603 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
6604 return Err(ParseError {
6605 message: format!(
6606 "Invalid scope '{s}' in immune '{}' — \
6607 expected tenant | flow | global",
6608 node.name
6609 ),
6610 line: s_tok.line,
6611 column: s_tok.column,
6612 ..Default::default()
6613 });
6614 }
6615 node.scope = s;
6616 }
6617 "tau" => {
6618 let t = self.current().clone();
6619 match t.ttype {
6620 TokenType::Duration | TokenType::StringLit => {
6621 self.advance();
6622 node.tau = t.value;
6623 }
6624 _ => node.tau = self.consume_any_ident_or_kw()?.value,
6625 }
6626 }
6627 "decay" => {
6628 let d_tok = self.consume_any_ident_or_kw()?;
6629 let d = d_tok.value;
6630 if !matches!(d.as_str(), "exponential" | "linear" | "none") {
6631 return Err(ParseError {
6632 message: format!(
6633 "Invalid decay '{d}' in immune '{}' — \
6634 expected exponential | linear | none",
6635 node.name
6636 ),
6637 line: d_tok.line,
6638 column: d_tok.column,
6639 ..Default::default()
6640 });
6641 }
6642 node.decay = d;
6643 }
6644 _ => self.skip_value(),
6645 }
6646 }
6647 self.consume(TokenType::RBrace)?;
6648 Ok(node)
6649 }
6650
6651 fn parse_reflex(&mut self) -> Result<ReflexDefinition, ParseError> {
6653 let tok = self.consume(TokenType::Reflex)?;
6654 let name = self.consume(TokenType::Identifier)?.value;
6655 let mut node = ReflexDefinition {
6656 name,
6657 trigger: String::new(),
6658 on_level: "doubt".to_string(),
6659 action: String::new(),
6660 scope: String::new(),
6661 sla: String::new(),
6662 loc: Loc {
6663 line: tok.line,
6664 column: tok.column,
6665 },
6666 leading_trivia: Vec::new(),
6667 trailing_trivia: Vec::new(),
6668 };
6669 self.consume(TokenType::LBrace)?;
6670 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6671 let field_name = self.current().value.clone();
6672 self.advance();
6673 if !self.check(TokenType::Colon) {
6674 if self.check(TokenType::LBrace) {
6675 self.skip_braced_block()?;
6676 }
6677 continue;
6678 }
6679 self.advance();
6680 match field_name.as_str() {
6681 "trigger" => node.trigger = self.consume_any_ident_or_kw()?.value,
6682 "on_level" => {
6683 let l_tok = self.consume_any_ident_or_kw()?;
6684 let l = l_tok.value;
6685 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
6686 return Err(ParseError {
6687 message: format!(
6688 "Invalid on_level '{l}' in reflex '{}' — \
6689 expected know | believe | speculate | doubt",
6690 node.name
6691 ),
6692 line: l_tok.line,
6693 column: l_tok.column,
6694 ..Default::default()
6695 });
6696 }
6697 node.on_level = l;
6698 }
6699 "action" => {
6700 let a_tok = self.consume_any_ident_or_kw()?;
6701 let a = a_tok.value;
6702 if !matches!(
6703 a.as_str(),
6704 "drop"
6705 | "revoke"
6706 | "emit"
6707 | "redact"
6708 | "quarantine"
6709 | "terminate"
6710 | "alert"
6711 ) {
6712 return Err(ParseError {
6713 message: format!(
6714 "Invalid action '{a}' in reflex '{}' — \
6715 expected drop | revoke | emit | redact | \
6716 quarantine | terminate | alert",
6717 node.name
6718 ),
6719 line: a_tok.line,
6720 column: a_tok.column,
6721 ..Default::default()
6722 });
6723 }
6724 node.action = a;
6725 }
6726 "scope" => {
6727 let s_tok = self.consume_any_ident_or_kw()?;
6728 let s = s_tok.value;
6729 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
6730 return Err(ParseError {
6731 message: format!(
6732 "Invalid scope '{s}' in reflex '{}' — \
6733 expected tenant | flow | global",
6734 node.name
6735 ),
6736 line: s_tok.line,
6737 column: s_tok.column,
6738 ..Default::default()
6739 });
6740 }
6741 node.scope = s;
6742 }
6743 "sla" => {
6744 let t = self.current().clone();
6745 match t.ttype {
6746 TokenType::Duration | TokenType::StringLit => {
6747 self.advance();
6748 node.sla = t.value;
6749 }
6750 _ => node.sla = self.consume_any_ident_or_kw()?.value,
6751 }
6752 }
6753 _ => self.skip_value(),
6754 }
6755 }
6756 self.consume(TokenType::RBrace)?;
6757 Ok(node)
6758 }
6759
6760 fn parse_heal(&mut self) -> Result<HealDefinition, ParseError> {
6762 let tok = self.consume(TokenType::Heal)?;
6763 let name = self.consume(TokenType::Identifier)?.value;
6764 let mut node = HealDefinition {
6765 name,
6766 source: String::new(),
6767 on_level: "doubt".to_string(),
6768 mode: "human_in_loop".to_string(),
6769 scope: String::new(),
6770 review_sla: String::new(),
6771 shield_ref: String::new(),
6772 max_patches: 3,
6773 loc: Loc {
6774 line: tok.line,
6775 column: tok.column,
6776 },
6777 leading_trivia: Vec::new(),
6778 trailing_trivia: Vec::new(),
6779 };
6780 self.consume(TokenType::LBrace)?;
6781 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6782 let field_name = self.current().value.clone();
6783 self.advance();
6784 if !self.check(TokenType::Colon) {
6785 if self.check(TokenType::LBrace) {
6786 self.skip_braced_block()?;
6787 }
6788 continue;
6789 }
6790 self.advance();
6791 match field_name.as_str() {
6792 "source" => node.source = self.consume_any_ident_or_kw()?.value,
6793 "on_level" => {
6794 let l_tok = self.consume_any_ident_or_kw()?;
6795 let l = l_tok.value;
6796 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
6797 return Err(ParseError {
6798 message: format!(
6799 "Invalid on_level '{l}' in heal '{}' — \
6800 expected know | believe | speculate | doubt",
6801 node.name
6802 ),
6803 line: l_tok.line,
6804 column: l_tok.column,
6805 ..Default::default()
6806 });
6807 }
6808 node.on_level = l;
6809 }
6810 "mode" => {
6811 let m_tok = self.consume_any_ident_or_kw()?;
6812 let m = m_tok.value;
6813 if !matches!(m.as_str(), "audit_only" | "human_in_loop" | "adversarial") {
6814 return Err(ParseError {
6815 message: format!(
6816 "Invalid mode '{m}' in heal '{}' — \
6817 expected audit_only | human_in_loop | adversarial",
6818 node.name
6819 ),
6820 line: m_tok.line,
6821 column: m_tok.column,
6822 ..Default::default()
6823 });
6824 }
6825 node.mode = m;
6826 }
6827 "scope" => {
6828 let s_tok = self.consume_any_ident_or_kw()?;
6829 let s = s_tok.value;
6830 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
6831 return Err(ParseError {
6832 message: format!(
6833 "Invalid scope '{s}' in heal '{}' — \
6834 expected tenant | flow | global",
6835 node.name
6836 ),
6837 line: s_tok.line,
6838 column: s_tok.column,
6839 ..Default::default()
6840 });
6841 }
6842 node.scope = s;
6843 }
6844 "review_sla" => {
6845 let t = self.current().clone();
6846 match t.ttype {
6847 TokenType::Duration | TokenType::StringLit => {
6848 self.advance();
6849 node.review_sla = t.value;
6850 }
6851 _ => node.review_sla = self.consume_any_ident_or_kw()?.value,
6852 }
6853 }
6854 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
6855 "max_patches" => {
6856 if let Some(v) = self.parse_optional_int() {
6857 node.max_patches = v;
6858 }
6859 }
6860 _ => self.skip_value(),
6861 }
6862 }
6863 self.consume(TokenType::RBrace)?;
6864 Ok(node)
6865 }
6866
6867 fn parse_component(&mut self) -> Result<ComponentDefinition, ParseError> {
6871 let tok = self.consume(TokenType::Component)?;
6872 let name = self.consume(TokenType::Identifier)?.value;
6873 let mut node = ComponentDefinition {
6874 name,
6875 renders: String::new(),
6876 via_shield: String::new(),
6877 on_interact: String::new(),
6878 render_hint: "custom".to_string(),
6879 loc: Loc {
6880 line: tok.line,
6881 column: tok.column,
6882 },
6883 leading_trivia: Vec::new(),
6884 trailing_trivia: Vec::new(),
6885 };
6886 self.consume(TokenType::LBrace)?;
6887 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6888 let field_name = self.current().value.clone();
6889 self.advance();
6890 if !self.check(TokenType::Colon) {
6891 if self.check(TokenType::LBrace) {
6892 self.skip_braced_block()?;
6893 }
6894 continue;
6895 }
6896 self.advance();
6897 match field_name.as_str() {
6898 "renders" => node.renders = self.consume_any_ident_or_kw()?.value,
6899 "via_shield" => node.via_shield = self.consume_any_ident_or_kw()?.value,
6900 "on_interact" => node.on_interact = self.consume_any_ident_or_kw()?.value,
6901 "render_hint" => {
6902 let h_tok = self.consume_any_ident_or_kw()?;
6903 let h = h_tok.value;
6904 if !matches!(h.as_str(), "card" | "list" | "form" | "chart" | "custom") {
6905 return Err(ParseError {
6906 message: format!(
6907 "Invalid render_hint '{h}' in component '{}' — \
6908 expected card | list | form | chart | custom",
6909 node.name
6910 ),
6911 line: h_tok.line,
6912 column: h_tok.column,
6913 ..Default::default()
6914 });
6915 }
6916 node.render_hint = h;
6917 }
6918 _ => self.skip_value(),
6919 }
6920 }
6921 self.consume(TokenType::RBrace)?;
6922 Ok(node)
6923 }
6924
6925 fn parse_view(&mut self) -> Result<ViewDefinition, ParseError> {
6927 let tok = self.consume(TokenType::View)?;
6928 let name = self.consume(TokenType::Identifier)?.value;
6929 let mut node = ViewDefinition {
6930 name,
6931 title: String::new(),
6932 components: Vec::new(),
6933 route: String::new(),
6934 loc: Loc {
6935 line: tok.line,
6936 column: tok.column,
6937 },
6938 leading_trivia: Vec::new(),
6939 trailing_trivia: Vec::new(),
6940 };
6941 self.consume(TokenType::LBrace)?;
6942 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6943 let field_name = self.current().value.clone();
6944 self.advance();
6945 if !self.check(TokenType::Colon) {
6946 if self.check(TokenType::LBrace) {
6947 self.skip_braced_block()?;
6948 }
6949 continue;
6950 }
6951 self.advance();
6952 match field_name.as_str() {
6953 "title" => node.title = self.consume(TokenType::StringLit)?.value,
6954 "components" => node.components = self.parse_bracketed_identifiers()?,
6955 "route" => node.route = self.consume(TokenType::StringLit)?.value,
6956 _ => self.skip_value(),
6957 }
6958 }
6959 self.consume(TokenType::RBrace)?;
6960 Ok(node)
6961 }
6962
6963 fn parse_axonendpoint(&mut self) -> Result<AxonEndpointDefinition, ParseError> {
6964 let tok = self.consume(TokenType::AxonEndpoint)?;
6965 let name = self.consume(TokenType::Identifier)?.value;
6966 let mut node = AxonEndpointDefinition {
6967 name,
6968 method: String::new(),
6969 path: String::new(),
6970 body_type: String::new(),
6971 execute_flow: String::new(),
6972 output_type: String::new(),
6973 shield_ref: String::new(),
6974 retries: None,
6975 timeout: String::new(),
6976 compliance: Vec::new(),
6977 transport: "json".to_string(),
6979 keepalive: String::new(),
6980 transport_explicit: false,
6986 implicit_transport: String::new(),
6987 requires_capabilities: Vec::new(),
6989 replay_explicit: false,
6993 replay: false,
6994 transport_dialect: String::new(),
6999 has_algebraic_stream_effect: false,
7005 backend: String::new(),
7010 path_params: Vec::new(),
7015 query_params: Vec::new(),
7020 loc: Loc {
7021 line: tok.line,
7022 column: tok.column,
7023 },
7024 leading_trivia: Vec::new(),
7025 trailing_trivia: Vec::new(),
7026 };
7027 self.consume(TokenType::LBrace)?;
7028 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7029 let field_name = self.current().value.clone();
7030 self.advance();
7031 if self.check(TokenType::Colon) {
7032 self.advance();
7033 match field_name.as_str() {
7034 "method" => {
7035 let value_tok = self.consume_any_ident_or_kw()?;
7042 let value_upper = value_tok.value.to_uppercase();
7043 if !axonendpoint_is_valid_method(&value_upper) {
7044 let hint = crate::smart_suggest::suggest_for(
7045 &value_upper,
7046 AXONENDPOINT_METHOD_VALUES,
7047 );
7048 let base = format!(
7049 "Invalid method '{}' in axonendpoint '{}'.",
7050 value_tok.value, node.name
7051 );
7052 let message = if hint.is_empty() {
7053 format!(
7054 "{base} expected GET | POST | PUT | DELETE | PATCH, found {}",
7055 value_tok.value
7056 )
7057 } else {
7058 format!(
7059 "{base} {hint} (expected GET | POST | PUT | DELETE | PATCH, found {})",
7060 value_tok.value
7061 )
7062 };
7063 return Err(ParseError {
7064 message,
7065 line: value_tok.line,
7066 column: value_tok.column,
7067 ..Default::default()
7068 });
7069 }
7070 node.method = value_upper;
7071 }
7072 "path" => {
7073 node.path = self.consume(TokenType::StringLit)?.value.clone();
7074 match extract_path_param_names(&node.path) {
7082 Ok(names) => node.path_params = names,
7083 Err(dup) => {
7084 let cur = self.current().clone();
7085 return Err(ParseError {
7086 message: format!(
7087 "axonendpoint '{}' declares path '{}' \
7088 containing duplicate placeholder '{{{}}}'. \
7089 Each `{{name}}` in a `path:` must be \
7090 unique — the runtime cannot bind two \
7091 path segments to the same name (Fase 37.y D1).",
7092 node.name, node.path, dup,
7093 ),
7094 line: cur.line,
7095 column: cur.column,
7096 ..Default::default()
7097 });
7098 }
7099 }
7100 },
7101 "body" => node.body_type = self.consume_any_ident_or_kw()?.value.clone(),
7102 "query" => {
7103 let lbrace_tok = self.consume(TokenType::LBrace)?;
7122 let block_line = lbrace_tok.line;
7123 if !node.query_params.is_empty() {
7124 return Err(ParseError {
7125 message: format!(
7126 "axonendpoint '{}' declares `query: {{ … }}` \
7127 more than once. The query-parameter block \
7128 is unique per endpoint; combine all params \
7129 into a single block (Fase 37.y D2).",
7130 node.name,
7131 ),
7132 line: lbrace_tok.line,
7133 column: lbrace_tok.column,
7134 ..Default::default()
7135 });
7136 }
7137 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7138 let name_tok = self.consume(TokenType::Identifier)?;
7139 let field_name = name_tok.value.clone();
7140 if node
7142 .query_params
7143 .iter()
7144 .any(|f| f.name == field_name)
7145 {
7146 return Err(ParseError {
7147 message: format!(
7148 "axonendpoint '{}' declares duplicate \
7149 query param '{}' inside `query: {{ … }}`. \
7150 Each name must appear at most once \
7151 (Fase 37.y D2).",
7152 node.name, field_name,
7153 ),
7154 line: name_tok.line,
7155 column: name_tok.column,
7156 ..Default::default()
7157 });
7158 }
7159 self.consume(TokenType::Colon)?;
7160 let type_expr = self.parse_type_expr()?;
7161 if !type_expr.generic_param.is_empty() {
7173 let canonical_hint = if type_expr.name == "Optional" {
7174 format!(
7175 " Use `{}?` (the `?` suffix) for an \
7176 optional query param instead of \
7177 `Optional<{}>`.",
7178 type_expr.generic_param,
7179 type_expr.generic_param,
7180 )
7181 } else if type_expr.name == "List" {
7182 " Multi-value query params (e.g. `?tag=a&tag=b`) \
7183 are honest-deferred from v1.38.5; bind a \
7184 single-value `Text` query param and parse \
7185 the value inside the flow."
7186 .to_string()
7187 } else {
7188 String::new()
7189 };
7190 return Err(ParseError {
7191 message: format!(
7192 "axonendpoint '{}' query param '{}' uses \
7193 a generic type `{}<{}>`. Query params \
7194 take a primitive type from the closed \
7195 catalog ({}); the `?` suffix marks \
7196 optional.{} (Fase 37.y D2).",
7197 node.name,
7198 field_name,
7199 type_expr.name,
7200 type_expr.generic_param,
7201 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | "),
7202 canonical_hint,
7203 ),
7204 line: type_expr.loc.line,
7205 column: type_expr.loc.column,
7206 ..Default::default()
7207 });
7208 }
7209 if !axonendpoint_is_valid_query_param_type(&type_expr.name) {
7213 let hint = crate::smart_suggest::suggest_for(
7220 &type_expr.name,
7221 AXONENDPOINT_QUERY_PARAM_TYPES,
7222 );
7223 let hint_text = if hint.is_empty() {
7224 format!(
7225 " Expected one of: {}.",
7226 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
7227 )
7228 } else {
7229 format!(
7230 " {} Expected one of: {}.",
7231 hint,
7232 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
7233 )
7234 };
7235 return Err(ParseError {
7236 message: format!(
7237 "axonendpoint '{}' query param '{}' has \
7238 unsupported type '{}'.{} (Fase 37.y D2).",
7239 node.name, field_name, type_expr.name,
7240 hint_text,
7241 ),
7242 line: type_expr.loc.line,
7243 column: type_expr.loc.column,
7244 ..Default::default()
7245 });
7246 }
7247 node.query_params.push(TypeField {
7248 name: field_name,
7249 type_expr,
7250 loc: Loc {
7251 line: name_tok.line,
7252 column: name_tok.column,
7253 },
7254 });
7255 if self.check(TokenType::Comma) {
7261 self.advance();
7262 }
7263 let _ = block_line; }
7265 self.consume(TokenType::RBrace)?;
7266 },
7267 "execute" => node.execute_flow = self.consume_any_ident_or_kw()?.value.clone(),
7268 "output" => {
7269 node.output_type = self.parse_output_type_string()?;
7287 }
7288 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
7289 "retries" => node.retries = self.parse_optional_int(),
7290 "timeout" => {
7291 let t = self.current().clone();
7292 self.advance();
7293 node.timeout = t.value.clone();
7294 }
7295 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
7296 "replay" => {
7297 let value_tok = self.consume(TokenType::Bool)?;
7304 node.replay = value_tok.value.eq_ignore_ascii_case("true");
7305 node.replay_explicit = true;
7306 }
7307 "requires" => {
7308 let bracket_tok = self.current().clone();
7315 let items = self.parse_bracketed_dot_identifiers()?;
7316 for slug in &items {
7317 if !is_valid_capability_slug(slug) {
7318 return Err(ParseError {
7319 message: format!(
7320 "Invalid capability slug '{slug}' in axonendpoint '{}' \
7321 `requires:`. Capability slugs must match \
7322 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
7323 lowercase identifiers starting with a letter. Examples: \
7324 `admin`, `legal.read`, `hipaa.phi.read`.",
7325 node.name
7326 ),
7327 line: bracket_tok.line,
7328 column: bracket_tok.column,
7329 ..Default::default()
7330 });
7331 }
7332 }
7333 node.requires_capabilities = items;
7334 }
7335 "transport" => {
7339 let value_tok = self.consume_any_ident_or_kw()?;
7340 let value = &value_tok.value;
7341 if !axonendpoint_is_valid_transport(value) {
7342 let hint = crate::smart_suggest::suggest_for(
7343 value,
7344 AXONENDPOINT_TRANSPORT_VALUES,
7345 );
7346 let base = format!(
7347 "Invalid transport '{}' in axonendpoint '{}'.",
7348 value, node.name
7349 );
7350 let message = if hint.is_empty() {
7351 format!("{base} expected json | sse | ndjson, found {value}")
7352 } else {
7353 format!(
7354 "{base} {hint} (expected json | sse | ndjson, found {value})"
7355 )
7356 };
7357 return Err(ParseError {
7358 message,
7359 line: value_tok.line,
7360 column: value_tok.column,
7361 ..Default::default()
7362 });
7363 }
7364 node.transport = value.clone();
7365 node.transport_explicit = true;
7370 if self.check(TokenType::LParen) {
7377 if value != "sse" {
7378 let tok = self.current().clone();
7379 return Err(ParseError {
7380 message: format!(
7381 "Dialect parametrization \
7382 `transport: {value}(<dialect>)` is \
7383 only valid for `sse`; got \
7384 `{value}` in axonendpoint '{}'.",
7385 node.name
7386 ),
7387 line: tok.line,
7388 column: tok.column,
7389 ..Default::default()
7390 });
7391 }
7392 self.advance(); let dialect_tok = self.consume_any_ident_or_kw()?;
7394 let dialect = dialect_tok.value.clone();
7395 if !AXONENDPOINT_TRANSPORT_DIALECTS
7396 .iter()
7397 .any(|&d| d == dialect)
7398 {
7399 let hint = crate::smart_suggest::suggest_for(
7400 &dialect,
7401 AXONENDPOINT_TRANSPORT_DIALECTS,
7402 );
7403 let base = format!(
7404 "Invalid SSE dialect '{dialect}' in axonendpoint '{}'.",
7405 node.name
7406 );
7407 let message = if hint.is_empty() {
7408 format!(
7409 "{base} expected axon | openai | kimi | glm | anthropic, found {dialect}"
7410 )
7411 } else {
7412 format!(
7413 "{base} {hint} (expected axon | openai | kimi | glm | anthropic, found {dialect})"
7414 )
7415 };
7416 return Err(ParseError {
7417 message,
7418 line: dialect_tok.line,
7419 column: dialect_tok.column,
7420 ..Default::default()
7421 });
7422 }
7423 let rparen_tok = self.current().clone();
7425 if !self.check(TokenType::RParen) {
7426 return Err(ParseError {
7427 message: format!(
7428 "Expected `)` after dialect name \
7429 in axonendpoint '{}' \
7430 (transport: sse(<dialect>) grammar).",
7431 node.name
7432 ),
7433 line: rparen_tok.line,
7434 column: rparen_tok.column,
7435 ..Default::default()
7436 });
7437 }
7438 self.advance(); node.transport_dialect = dialect;
7440 }
7441 }
7442 "keepalive" => {
7443 let value_tok = self.current().clone();
7447 self.advance();
7448 let value = &value_tok.value;
7449 if !axonendpoint_is_valid_keepalive(value) {
7450 let hint = crate::smart_suggest::suggest_for(
7451 value,
7452 AXONENDPOINT_KEEPALIVE_VALUES,
7453 );
7454 let base = format!(
7455 "Invalid keepalive '{}' in axonendpoint '{}'.",
7456 value, node.name
7457 );
7458 let message = if hint.is_empty() {
7459 format!("{base} expected 5s | 15s | 30s | 60s, found {value}")
7460 } else {
7461 format!(
7462 "{base} {hint} (expected 5s | 15s | 30s | 60s, found {value})"
7463 )
7464 };
7465 return Err(ParseError {
7466 message,
7467 line: value_tok.line,
7468 column: value_tok.column,
7469 ..Default::default()
7470 });
7471 }
7472 node.keepalive = value.clone();
7473 }
7474 "backend" => {
7475 let value_tok = self.consume_any_ident_or_kw()?;
7483 let value = &value_tok.value;
7484 if !axonendpoint_is_valid_backend(value) {
7485 let hint = crate::smart_suggest::suggest_for(
7486 value,
7487 AXONENDPOINT_BACKEND_VALUES,
7488 );
7489 let expected = AXONENDPOINT_BACKEND_VALUES.join(" | ");
7490 let base = format!(
7491 "Invalid backend '{}' in axonendpoint '{}'.",
7492 value, node.name
7493 );
7494 let message = if hint.is_empty() {
7495 format!("{base} expected {expected}, found {value}")
7496 } else {
7497 format!(
7498 "{base} {hint} (expected {expected}, found {value})"
7499 )
7500 };
7501 return Err(ParseError {
7502 message,
7503 line: value_tok.line,
7504 column: value_tok.column,
7505 ..Default::default()
7506 });
7507 }
7508 node.backend = value.clone();
7509 }
7510 _ => self.skip_value(),
7511 }
7512 } else if self.check(TokenType::LBrace) {
7513 self.skip_braced_block()?;
7514 }
7515 }
7516 self.consume(TokenType::RBrace)?;
7517 Ok(node)
7518 }
7519
7520 fn parse_optional_int(&mut self) -> Option<i64> {
7523 let tok = self.current().clone();
7524 match tok.ttype {
7525 TokenType::Integer => {
7526 self.advance();
7527 tok.value.parse::<i64>().ok()
7528 }
7529 _ => {
7530 self.advance();
7531 None
7532 }
7533 }
7534 }
7535
7536 fn parse_optional_float(&mut self) -> Option<f64> {
7537 let tok = self.current().clone();
7538 match tok.ttype {
7539 TokenType::Float | TokenType::Integer => {
7540 self.advance();
7541 tok.value.parse::<f64>().ok()
7542 }
7543 _ => {
7544 self.advance();
7545 None
7546 }
7547 }
7548 }
7549
7550 fn parse_lambda_data(&mut self) -> Result<LambdaDataDefinition, ParseError> {
7553 let tok = self.consume(TokenType::Lambda)?;
7554 let name = self.consume(TokenType::Identifier)?;
7555 self.consume(TokenType::LBrace)?;
7556
7557 let mut node = LambdaDataDefinition {
7558 name: name.value.clone(),
7559 ontology: String::new(),
7560 certainty: 1.0,
7561 temporal_frame_start: String::new(),
7562 temporal_frame_end: String::new(),
7563 provenance: String::new(),
7564 derivation: String::new(),
7565 loc: Loc {
7566 line: tok.line,
7567 column: tok.column,
7568 },
7569 leading_trivia: Vec::new(),
7570 trailing_trivia: Vec::new(),
7571 };
7572
7573 while !self.check(TokenType::RBrace) {
7574 let field = self.current().clone();
7575 match field.ttype {
7576 TokenType::Ontology => {
7577 self.advance();
7578 self.consume(TokenType::Colon)?;
7579 node.ontology = self.consume(TokenType::StringLit)?.value.clone();
7580 }
7581 TokenType::Certainty => {
7582 self.advance();
7583 self.consume(TokenType::Colon)?;
7584 let val = self.current().clone();
7585 match val.ttype {
7586 TokenType::Float => {
7587 self.advance();
7588 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
7589 }
7590 TokenType::Integer => {
7591 self.advance();
7592 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
7593 }
7594 _ => {
7595 return Err(ParseError {
7596 message: format!(
7597 "Expected number for certainty, got '{}'",
7598 val.value
7599 ),
7600 line: val.line,
7601 column: val.column,
7602 ..Default::default()
7603 });
7604 }
7605 }
7606 }
7607 TokenType::TemporalFrame => {
7608 self.advance();
7609 self.consume(TokenType::Colon)?;
7610 node.temporal_frame_start = self.consume(TokenType::StringLit)?.value.clone();
7611 if self.check(TokenType::StringLit) {
7613 node.temporal_frame_end = self.consume(TokenType::StringLit)?.value.clone();
7614 }
7615 }
7616 TokenType::Provenance => {
7617 self.advance();
7618 self.consume(TokenType::Colon)?;
7619 node.provenance = self.consume(TokenType::StringLit)?.value.clone();
7620 }
7621 TokenType::Derivation => {
7622 self.advance();
7623 self.consume(TokenType::Colon)?;
7624 let d = self.current().clone();
7625 self.advance();
7626 node.derivation = d.value.clone();
7627 }
7628 _ => {
7629 self.advance();
7631 if self.check(TokenType::Colon) {
7632 self.advance();
7633 self.skip_value();
7634 }
7635 }
7636 }
7637 }
7638
7639 self.consume(TokenType::RBrace)?;
7640 Ok(node)
7641 }
7642
7643 fn parse_lambda_data_apply(&mut self) -> Result<LambdaDataApplyNode, ParseError> {
7644 let tok = self.consume(TokenType::Lambda)?;
7645 let lambda_name = self.consume(TokenType::Identifier)?;
7646
7647 let on_tok = self.current().clone();
7649 self.advance();
7650 if on_tok.value != "on" {
7651 return Err(ParseError {
7652 message: format!(
7653 "Expected 'on' after lambda data name in flow step, got '{}'",
7654 on_tok.value
7655 ),
7656 line: on_tok.line,
7657 column: on_tok.column,
7658 ..Default::default()
7659 });
7660 }
7661
7662 let target = self.current().clone();
7663 self.advance();
7664
7665 let mut output_type = String::new();
7666 if self.check(TokenType::Arrow) {
7667 self.advance();
7668 output_type = self.consume(TokenType::Identifier)?.value.clone();
7669 }
7670
7671 Ok(LambdaDataApplyNode {
7672 lambda_data_name: lambda_name.value.clone(),
7673 target: target.value.clone(),
7674 output_type,
7675 loc: Loc {
7676 line: tok.line,
7677 column: tok.column,
7678 },
7679 })
7680 }
7681
7682 fn parse_generic_declaration(&mut self) -> Result<Declaration, ParseError> {
7685 let kw_tok = self.current().clone();
7686 self.advance(); let name = if self.current().ttype == TokenType::Identifier {
7690 let n = self.current().value.clone();
7691 self.advance();
7692 n
7693 } else if !self.check(TokenType::LBrace)
7694 && !self.check(TokenType::LParen)
7695 && !self.check(TokenType::Eof)
7696 && self
7697 .current()
7698 .value
7699 .chars()
7700 .all(|c| c.is_alphanumeric() || c == '_')
7701 {
7702 let n = self.current().value.clone();
7703 self.advance();
7704 n
7705 } else {
7706 String::new()
7707 };
7708
7709 if self.check(TokenType::LParen) {
7711 self.advance();
7712 let mut depth = 1u32;
7713 while depth > 0 && !self.check(TokenType::Eof) {
7714 if self.check(TokenType::LParen) {
7715 depth += 1;
7716 } else if self.check(TokenType::RParen) {
7717 depth -= 1;
7718 }
7719 self.advance();
7720 }
7721 }
7722
7723 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
7725 if self.check(TokenType::Eof) {
7726 break;
7727 }
7728 self.advance();
7729 }
7730
7731 if self.check(TokenType::LBrace) {
7733 self.skip_braced_block()?;
7734 }
7735
7736 Ok(Declaration::Generic(GenericDeclaration {
7737 keyword: kw_tok.value,
7738 name,
7739 loc: Loc {
7740 line: kw_tok.line,
7741 column: kw_tok.column,
7742 },
7743 leading_trivia: Vec::new(),
7744 trailing_trivia: Vec::new(),
7745 }))
7746 }
7747
7748 fn parse_channel(&mut self) -> Result<ChannelDefinition, ParseError> {
7756 let tok = self.consume(TokenType::Channel)?;
7757 let name = self.consume(TokenType::Identifier)?.value;
7758 let mut node = ChannelDefinition {
7759 name: name.clone(),
7760 message: String::new(),
7761 qos: "at_least_once".to_string(),
7762 lifetime: "affine".to_string(),
7763 persistence: "ephemeral".to_string(),
7764 shield_ref: String::new(),
7765 loc: Loc {
7766 line: tok.line,
7767 column: tok.column,
7768 },
7769 leading_trivia: Vec::new(),
7770 trailing_trivia: Vec::new(),
7771 };
7772 self.consume(TokenType::LBrace)?;
7773 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7774 let field_tok = self.current().clone();
7775 let field_name = field_tok.value.clone();
7776 self.advance();
7777 if !self.check(TokenType::Colon) {
7778 if self.check(TokenType::LBrace) {
7779 self.skip_braced_block()?;
7780 }
7781 continue;
7782 }
7783 self.advance();
7784 match field_name.as_str() {
7785 "message" => node.message = self.parse_channel_message_type()?,
7786 "qos" => {
7787 let q_tok = self.consume_any_ident_or_kw()?;
7788 if !matches!(
7789 q_tok.value.as_str(),
7790 "at_most_once" | "at_least_once" | "exactly_once" | "broadcast" | "queue"
7791 ) {
7792 return Err(ParseError {
7793 message: format!(
7794 "Invalid qos '{}' in channel '{}' — \
7795 expected at_most_once | at_least_once | \
7796 exactly_once | broadcast | queue",
7797 q_tok.value, name
7798 ),
7799 line: q_tok.line,
7800 column: q_tok.column,
7801 ..Default::default()
7802 });
7803 }
7804 node.qos = q_tok.value;
7805 }
7806 "lifetime" => {
7807 let lt_tok = self.consume_any_ident_or_kw()?;
7808 if !matches!(lt_tok.value.as_str(), "linear" | "affine" | "persistent") {
7809 return Err(ParseError {
7810 message: format!(
7811 "Invalid lifetime '{}' in channel '{}' — \
7812 expected linear | affine | persistent",
7813 lt_tok.value, name
7814 ),
7815 line: lt_tok.line,
7816 column: lt_tok.column,
7817 ..Default::default()
7818 });
7819 }
7820 node.lifetime = lt_tok.value;
7821 }
7822 "persistence" => {
7823 let p_tok = self.consume_any_ident_or_kw()?;
7824 if !matches!(p_tok.value.as_str(), "ephemeral" | "persistent_axonstore") {
7825 return Err(ParseError {
7826 message: format!(
7827 "Invalid persistence '{}' in channel '{}' — \
7828 expected ephemeral | persistent_axonstore",
7829 p_tok.value, name
7830 ),
7831 line: p_tok.line,
7832 column: p_tok.column,
7833 ..Default::default()
7834 });
7835 }
7836 node.persistence = p_tok.value;
7837 }
7838 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7839 _ => self.skip_value(),
7840 }
7841 }
7842 self.consume(TokenType::RBrace)?;
7843 Ok(node)
7844 }
7845
7846 fn parse_channel_message_type(&mut self) -> Result<String, ParseError> {
7849 let head = self.consume(TokenType::Identifier)?;
7850 let mut spelling = head.value;
7851 if self.check(TokenType::Lt) {
7852 self.advance();
7853 let inner = self.parse_channel_message_type()?;
7854 self.consume(TokenType::Gt)?;
7855 spelling = format!("{}<{}>", spelling, inner);
7856 }
7857 Ok(spelling)
7858 }
7859
7860 fn parse_emit_step(&mut self) -> Result<FlowStep, ParseError> {
7866 let tok = self.consume(TokenType::Emit)?;
7867 let channel = self.consume(TokenType::Identifier)?.value;
7868 self.consume(TokenType::LParen)?;
7869 let value = self.parse_emit_value_ref()?;
7870 self.consume(TokenType::RParen)?;
7871 Ok(FlowStep::Emit(EmitStatement {
7872 channel_ref: channel,
7873 value_ref: value,
7874 loc: Loc {
7875 line: tok.line,
7876 column: tok.column,
7877 },
7878 }))
7879 }
7880
7881 fn parse_emit_value_ref(&mut self) -> Result<String, ParseError> {
7897 let head = self.consume(TokenType::Identifier)?.value;
7898 let mut parts = vec![head];
7899 while self.check(TokenType::Dot) {
7900 self.advance(); let next_tok = self.current().clone();
7902 let valid = !next_tok.value.is_empty()
7903 && next_tok.value.as_bytes()[0].is_ascii_alphabetic()
7904 || next_tok.value.starts_with('_');
7905 if !valid {
7906 return Err(ParseError {
7907 message: format!(
7908 "Expected identifier or keyword after '.' in dotted \
7909 access, found {:?}",
7910 next_tok.value
7911 ),
7912 line: next_tok.line,
7913 column: next_tok.column,
7914 ..Default::default()
7915 });
7916 }
7917 self.advance();
7918 parts.push(next_tok.value);
7919 }
7920 Ok(parts.join("."))
7921 }
7922
7923 fn parse_publish_step(&mut self) -> Result<FlowStep, ParseError> {
7925 let tok = self.consume(TokenType::Publish)?;
7926 let channel = self.consume(TokenType::Identifier)?.value;
7927 self.consume(TokenType::Within)?;
7928 let shield = self.consume(TokenType::Identifier)?.value;
7929 Ok(FlowStep::Publish(PublishStatement {
7930 channel_ref: channel,
7931 shield_ref: shield,
7932 loc: Loc {
7933 line: tok.line,
7934 column: tok.column,
7935 },
7936 }))
7937 }
7938
7939 fn parse_discover_step(&mut self) -> Result<FlowStep, ParseError> {
7941 let tok = self.consume(TokenType::Discover)?;
7942 let cap = self.consume(TokenType::Identifier)?.value;
7943 self.consume(TokenType::As)?;
7944 let alias = self.consume(TokenType::Identifier)?.value;
7945 Ok(FlowStep::Discover(DiscoverStatement {
7946 capability_ref: cap,
7947 alias,
7948 loc: Loc {
7949 line: tok.line,
7950 column: tok.column,
7951 },
7952 }))
7953 }
7954}
7955
7956#[cfg(test)]
7959mod fase13_parser_tests {
7960 use super::*;
7961 use crate::lexer::Lexer;
7962
7963 fn parse(src: &str) -> Result<Program, ParseError> {
7964 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
7965 Parser::new(tokens).parse()
7966 }
7967
7968 #[test]
7969 fn channel_full_parses() {
7970 let src = r#"channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }"#;
7971 let prog = parse(src).expect("parse");
7972 match &prog.declarations[0] {
7973 Declaration::Channel(c) => {
7974 assert_eq!(c.name, "C");
7975 assert_eq!(c.message, "Order");
7976 assert_eq!(c.qos, "at_least_once");
7977 assert_eq!(c.lifetime, "affine");
7978 assert_eq!(c.persistence, "ephemeral");
7979 assert_eq!(c.shield_ref, "Gate");
7980 }
7981 _ => panic!("expected ChannelDefinition"),
7982 }
7983 }
7984
7985 #[test]
7986 fn channel_defaults_match_paper_d1() {
7987 let prog = parse("channel C { message: Order }").expect("parse");
7988 if let Declaration::Channel(c) = &prog.declarations[0] {
7989 assert_eq!(c.qos, "at_least_once"); assert_eq!(c.lifetime, "affine"); assert_eq!(c.persistence, "ephemeral");
7992 assert_eq!(c.shield_ref, "");
7993 } else {
7994 panic!("expected ChannelDefinition");
7995 }
7996 }
7997
7998 #[test]
7999 fn channel_second_order_message_type_parses() {
8000 let prog = parse("channel C { message: Channel<Order> }").expect("parse");
8001 if let Declaration::Channel(c) = &prog.declarations[0] {
8002 assert_eq!(c.message, "Channel<Order>");
8003 } else {
8004 panic!("expected ChannelDefinition");
8005 }
8006 }
8007
8008 #[test]
8009 fn channel_nested_channel_message_type_parses() {
8010 let prog = parse("channel C { message: Channel<Channel<Order>> }").expect("parse");
8011 if let Declaration::Channel(c) = &prog.declarations[0] {
8012 assert_eq!(c.message, "Channel<Channel<Order>>");
8013 } else {
8014 panic!("expected ChannelDefinition");
8015 }
8016 }
8017
8018 #[test]
8019 fn channel_invalid_qos_rejected() {
8020 let err = parse("channel C { message: T qos: bogus }").unwrap_err();
8021 assert!(err.message.contains("Invalid qos"), "got {}", err.message);
8022 }
8023
8024 #[test]
8025 fn channel_invalid_lifetime_rejected() {
8026 let err = parse("channel C { message: T lifetime: eternal }").unwrap_err();
8027 assert!(
8028 err.message.contains("Invalid lifetime"),
8029 "got {}",
8030 err.message
8031 );
8032 }
8033
8034 #[test]
8035 fn channel_invalid_persistence_rejected() {
8036 let err = parse("channel C { message: T persistence: forever }").unwrap_err();
8037 assert!(
8038 err.message.contains("Invalid persistence"),
8039 "got {}",
8040 err.message
8041 );
8042 }
8043
8044 #[test]
8045 fn emit_value_parses() {
8046 let src = "flow f() -> Out { emit C(payload) }";
8047 let prog = parse(src).expect("parse");
8048 if let Declaration::Flow(f) = &prog.declarations[0] {
8049 match &f.body[0] {
8050 FlowStep::Emit(e) => {
8051 assert_eq!(e.channel_ref, "C");
8052 assert_eq!(e.value_ref, "payload");
8053 }
8054 other => panic!("expected Emit, got {:?}", other),
8055 }
8056 } else {
8057 panic!("expected Flow");
8058 }
8059 }
8060
8061 #[test]
8062 fn publish_within_shield_parses() {
8063 let src = "flow f() -> Cap { publish C within Gate }";
8064 let prog = parse(src).expect("parse");
8065 if let Declaration::Flow(f) = &prog.declarations[0] {
8066 match &f.body[0] {
8067 FlowStep::Publish(p) => {
8068 assert_eq!(p.channel_ref, "C");
8069 assert_eq!(p.shield_ref, "Gate");
8070 }
8071 other => panic!("expected Publish, got {:?}", other),
8072 }
8073 } else {
8074 panic!("expected Flow");
8075 }
8076 }
8077
8078 #[test]
8079 fn discover_with_alias_parses() {
8080 let src = "flow f() -> Out { discover C as ch }";
8081 let prog = parse(src).expect("parse");
8082 if let Declaration::Flow(f) = &prog.declarations[0] {
8083 match &f.body[0] {
8084 FlowStep::Discover(d) => {
8085 assert_eq!(d.capability_ref, "C");
8086 assert_eq!(d.alias, "ch");
8087 }
8088 other => panic!("expected Discover, got {:?}", other),
8089 }
8090 } else {
8091 panic!("expected Flow");
8092 }
8093 }
8094
8095 #[test]
8096 fn listen_typed_ref_sets_flag_true() {
8097 let src = "daemon D() { goal: \"x\" listen C as ev { } }";
8098 let prog = parse(src).expect("parse");
8099 if let Declaration::Daemon(d) = &prog.declarations[0] {
8100 assert_eq!(d.listeners.len(), 1);
8101 assert_eq!(d.listeners[0].channel, "C");
8102 assert!(d.listeners[0].channel_is_ref, "typed ref ⇒ true");
8103 } else {
8104 panic!("expected Daemon");
8105 }
8106 }
8107
8108 #[test]
8109 fn listen_string_topic_legacy_flag_false() {
8110 let src = "daemon D() { goal: \"x\" listen \"orders\" as ev { } }";
8111 let prog = parse(src).expect("parse");
8112 if let Declaration::Daemon(d) = &prog.declarations[0] {
8113 assert_eq!(d.listeners.len(), 1);
8114 assert_eq!(d.listeners[0].channel, "orders");
8115 assert!(!d.listeners[0].channel_is_ref, "string topic ⇒ false");
8116 } else {
8117 panic!("expected Daemon");
8118 }
8119 }
8120
8121 fn extract_first_emit(prog: &Program) -> &EmitStatement {
8124 if let Declaration::Flow(f) = &prog.declarations[0] {
8125 if let FlowStep::Emit(e) = &f.body[0] {
8126 return e;
8127 }
8128 }
8129 panic!("expected emit statement at flow body[0]");
8130 }
8131
8132 #[test]
8133 fn emit_accepts_bare_identifier_value_ref() {
8134 let prog = parse("flow f() -> Out { emit Hello(payload) }").expect("parse");
8136 let emit = extract_first_emit(&prog);
8137 assert_eq!(emit.channel_ref, "Hello");
8138 assert_eq!(emit.value_ref, "payload");
8139 }
8140
8141 #[test]
8142 fn emit_accepts_two_segment_dotted_value_ref() {
8143 let prog = parse("flow f() -> Out { emit Hello(Build.output) }").expect("parse");
8145 let emit = extract_first_emit(&prog);
8146 assert_eq!(emit.value_ref, "Build.output");
8147 }
8148
8149 #[test]
8150 fn emit_accepts_three_segment_nested_dotted_value_ref() {
8151 let prog = parse("flow f() -> Out { emit Score(Analyze.result.score) }").expect("parse");
8152 let emit = extract_first_emit(&prog);
8153 assert_eq!(emit.value_ref, "Analyze.result.score");
8154 }
8155
8156 #[test]
8157 fn emit_dotted_with_trailing_dot_fails() {
8158 let result = parse("flow f() -> Out { emit Hello(Build.) }");
8160 assert!(result.is_err(), "expected parse error for trailing dot");
8161 }
8162}
8163
8164#[cfg(test)]
8167mod fase14a_declaration_trivia_tests {
8168 use super::*;
8169 use crate::lexer::Lexer;
8170 use crate::tokens::TriviaKind;
8171
8172 fn parse(src: &str) -> Program {
8173 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
8174 Parser::new(toks).parse().expect("parse")
8175 }
8176
8177 #[test]
8178 fn no_comments_means_empty_trivia_per_decl() {
8179 let prog = parse("flow F() -> Out { }");
8180 assert_eq!(prog.declarations.len(), 1);
8181 assert_eq!(prog.declaration_trivia.len(), 1);
8182 assert!(prog.declaration_trivia[0].leading.is_empty());
8183 assert!(prog.declaration_trivia[0].trailing.is_empty());
8184 }
8185
8186 #[test]
8187 fn doc_line_comment_attaches_as_leading() {
8188 let prog = parse("/// Documents F\nflow F() -> Out { }");
8189 let triv = &prog.declaration_trivia[0];
8190 assert_eq!(triv.leading.len(), 1);
8191 assert_eq!(triv.leading[0].kind, TriviaKind::DocLine);
8192 assert!(triv.leading[0].is_doc());
8193 assert_eq!(triv.leading[0].text, "/// Documents F");
8194 }
8195
8196 #[test]
8197 fn regular_line_comment_attaches_as_leading() {
8198 let prog = parse("// header\nflow F() -> Out { }");
8199 let triv = &prog.declaration_trivia[0];
8200 assert_eq!(triv.leading.len(), 1);
8201 assert_eq!(triv.leading[0].kind, TriviaKind::Line);
8202 assert!(!triv.leading[0].is_doc());
8203 }
8204
8205 #[test]
8206 fn block_doc_comment_attaches_as_leading() {
8207 let prog = parse("/** Doc block */\nflow F() -> Out { }");
8208 let triv = &prog.declaration_trivia[0];
8209 assert_eq!(triv.leading[0].kind, TriviaKind::DocBlock);
8210 assert!(triv.leading[0].is_doc());
8211 }
8212
8213 #[test]
8214 fn multiple_comments_collected_in_source_order() {
8215 let src = "/// First\n/// Second\nflow F() -> Out { }";
8216 let prog = parse(src);
8217 let triv = &prog.declaration_trivia[0];
8218 assert_eq!(triv.leading.len(), 2);
8219 assert_eq!(triv.leading[0].text, "/// First");
8220 assert_eq!(triv.leading[1].text, "/// Second");
8221 }
8222
8223 #[test]
8224 fn three_decls_each_get_own_leading() {
8225 let src = "/// for A\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }\n/// for C\nflow C() -> Out { }";
8226 let prog = parse(src);
8227 assert_eq!(prog.declarations.len(), 3);
8228 assert_eq!(prog.declaration_trivia.len(), 3);
8229 for (idx, name) in ["A", "B", "C"].iter().enumerate() {
8230 let triv = &prog.declaration_trivia[idx];
8231 assert_eq!(triv.leading.len(), 1);
8232 assert_eq!(triv.leading[0].text, format!("/// for {name}"));
8233 }
8234 }
8235
8236 #[test]
8237 fn trailing_comment_attaches_to_last_token_of_decl() {
8238 let prog = parse("flow F() -> Out { } // tail");
8240 let triv = &prog.declaration_trivia[0];
8241 assert_eq!(triv.trailing.len(), 1);
8242 assert_eq!(triv.trailing[0].text, "// tail");
8243 }
8244
8245 #[test]
8246 fn mixed_doc_and_regular_preserve_order_between_decls() {
8247 let src = "/// doc for A\nflow A() -> Out { }\n\n// header line\n/// doc for B\nflow B() -> Out { }";
8248 let prog = parse(src);
8249 assert_eq!(prog.declarations.len(), 2);
8250 assert_eq!(prog.declaration_trivia[0].leading.len(), 1);
8252 assert_eq!(prog.declaration_trivia[1].leading.len(), 2);
8254 assert_eq!(prog.declaration_trivia[1].leading[0].text, "// header line");
8255 assert_eq!(prog.declaration_trivia[1].leading[1].text, "/// doc for B");
8256 }
8257
8258 #[test]
8259 fn parser_unaffected_by_comments_in_grammar_path() {
8260 let src =
8265 "// before flow\nflow /* between flow and name */ F() -> Out {\n // body comment\n}";
8266 let prog = parse(src);
8267 assert_eq!(prog.declarations.len(), 1);
8268 if let Declaration::Flow(f) = &prog.declarations[0] {
8269 assert_eq!(f.name, "F");
8270 } else {
8271 panic!("expected Flow declaration");
8272 }
8273 }
8274}
8275
8276#[cfg(test)]
8285mod fase14b_per_struct_trivia_tests {
8286 use super::*;
8287 use crate::lexer::Lexer;
8288 use crate::tokens::TriviaKind;
8289
8290 fn parse(src: &str) -> Program {
8291 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
8292 Parser::new(toks).parse().expect("parse")
8293 }
8294
8295 #[test]
8296 fn flow_definition_carries_leading_trivia_directly() {
8297 let prog = parse("/// documents F\nflow F() -> Out { }");
8298 if let Declaration::Flow(f) = &prog.declarations[0] {
8299 assert_eq!(f.leading_trivia.len(), 1);
8300 assert_eq!(f.leading_trivia[0].kind, TriviaKind::DocLine);
8301 assert_eq!(f.leading_trivia[0].text, "/// documents F");
8302 assert!(f.trailing_trivia.is_empty());
8303 } else {
8304 panic!("expected Flow declaration");
8305 }
8306 }
8307
8308 #[test]
8309 fn flow_definition_carries_trailing_trivia_directly() {
8310 let prog = parse("flow F() -> Out { } // tail comment");
8311 if let Declaration::Flow(f) = &prog.declarations[0] {
8312 assert_eq!(f.trailing_trivia.len(), 1);
8313 assert_eq!(f.trailing_trivia[0].text, "// tail comment");
8314 } else {
8315 panic!("expected Flow declaration");
8316 }
8317 }
8318
8319 #[test]
8320 fn channel_definition_carries_trivia_directly() {
8321 let src = concat!(
8324 "/// inbound order events\n",
8325 "channel Orders {\n",
8326 " message: Order\n",
8327 " qos: at_least_once\n",
8328 " lifetime: affine\n",
8329 " persistence: ephemeral\n",
8330 " shield: Broker\n",
8331 "}",
8332 );
8333 let prog = parse(src);
8334 if let Declaration::Channel(ch) = &prog.declarations[0] {
8335 assert_eq!(ch.leading_trivia.len(), 1);
8336 assert!(ch.leading_trivia[0].is_doc());
8337 assert_eq!(ch.leading_trivia[0].text, "/// inbound order events");
8338 } else {
8339 panic!("expected Channel declaration");
8340 }
8341 }
8342
8343 #[test]
8344 fn per_struct_fields_match_side_channel() {
8345 let src = "/// for A\n// header for B\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }";
8348 let prog = parse(src);
8349 for (idx, decl) in prog.declarations.iter().enumerate() {
8350 let side = &prog.declaration_trivia[idx];
8351 let (per_lead, per_trail) = match decl {
8352 Declaration::Flow(f) => (&f.leading_trivia, &f.trailing_trivia),
8353 _ => panic!("unexpected variant"),
8354 };
8355 assert_eq!(per_lead.len(), side.leading.len());
8356 assert_eq!(per_trail.len(), side.trailing.len());
8357 for (a, b) in per_lead.iter().zip(side.leading.iter()) {
8358 assert_eq!(a.text, b.text);
8359 assert_eq!(a.kind, b.kind);
8360 }
8361 }
8362 }
8363
8364 #[test]
8365 fn comment_free_program_yields_empty_per_struct_fields() {
8366 let prog = parse("flow F() -> Out { }");
8367 if let Declaration::Flow(f) = &prog.declarations[0] {
8368 assert!(f.leading_trivia.is_empty());
8369 assert!(f.trailing_trivia.is_empty());
8370 } else {
8371 panic!("expected Flow declaration");
8372 }
8373 }
8374}
8375
8376#[cfg(test)]
8385mod fase14c_inner_doc_tests {
8386 use super::*;
8387 use crate::lexer::Lexer;
8388 use crate::tokens::TriviaKind;
8389
8390 fn parse(src: &str) -> Program {
8391 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
8392 Parser::new(toks).parse().expect("parse")
8393 }
8394
8395 #[test]
8396 fn inner_doc_line_reaches_leading_trivia() {
8397 let src = "//! file-level docs\nflow F() -> Out { }";
8398 let prog = parse(src);
8399 let triv = &prog.declaration_trivia[0];
8400 assert_eq!(triv.leading.len(), 1);
8401 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocLine);
8402 assert!(triv.leading[0].is_doc());
8403 assert!(triv.leading[0].is_inner_doc());
8404 assert_eq!(triv.leading[0].text, "//! file-level docs");
8405 assert_eq!(triv.leading[0].stripped_text(), " file-level docs");
8406 }
8407
8408 #[test]
8409 fn inner_doc_block_reaches_leading_trivia() {
8410 let src = "/*! module-level docs */\nflow F() -> Out { }";
8411 let prog = parse(src);
8412 let triv = &prog.declaration_trivia[0];
8413 assert_eq!(triv.leading.len(), 1);
8414 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocBlock);
8415 assert!(triv.leading[0].is_inner_doc());
8416 assert_eq!(triv.leading[0].stripped_text(), " module-level docs ");
8417 }
8418
8419 #[test]
8420 fn outer_and_inner_doc_can_coexist() {
8421 let src = "//! file docs\n/// docs F\nflow F() -> Out { }";
8425 let prog = parse(src);
8426 let triv = &prog.declaration_trivia[0];
8427 assert_eq!(triv.leading.len(), 2);
8428 assert!(triv.leading[0].is_inner_doc());
8429 assert!(triv.leading[1].is_doc());
8430 assert!(!triv.leading[1].is_inner_doc());
8431 }
8432
8433 #[test]
8434 fn inner_doc_reaches_per_struct_fields() {
8435 let src = "//! intro\nflow F() -> Out { }";
8437 let prog = parse(src);
8438 if let Declaration::Flow(f) = &prog.declarations[0] {
8439 assert_eq!(f.leading_trivia.len(), 1);
8440 assert!(f.leading_trivia[0].is_inner_doc());
8441 } else {
8442 panic!("expected Flow declaration");
8443 }
8444 }
8445}
8446
8447#[cfg(test)]
8465mod fase28_recovery_tests {
8466 use super::*;
8467 use crate::lexer::Lexer;
8468
8469 fn lex(src: &str) -> Vec<Token> {
8472 Lexer::new(src, "<test>").tokenize().expect("lex")
8473 }
8474
8475 fn recover(src: &str) -> ParseResult {
8478 Parser::new(lex(src)).parse_with_recovery()
8479 }
8480
8481 fn strict(src: &str) -> Result<Program, ParseError> {
8483 Parser::new(lex(src)).parse()
8484 }
8485
8486 #[test]
8489 fn strict_parse_unchanged_for_clean_source() {
8490 let src = "intent I {}";
8493 let prog = strict(src).expect("clean parse");
8494 assert_eq!(prog.declarations.len(), 1);
8495 }
8496
8497 #[test]
8498 fn strict_parse_still_raises_on_first_error() {
8499 let src = "flow F() { } not_a_keyword flow G() { }";
8504 let _ = strict(src).expect_err("must error fast in strict mode");
8505 }
8506
8507 #[test]
8508 fn recovery_clean_source_yields_no_errors() {
8509 let src = "flow F() { } flow G() { }";
8510 let pr = recover(src);
8511 assert!(pr.is_clean(), "errors: {:?}", pr.errors);
8512 assert_eq!(pr.program.declarations.len(), 2);
8513 }
8514
8515 #[test]
8518 fn single_unknown_top_level_token_recovers() {
8519 let src = "garbage_token flow F() { } flow G() { }";
8521 let pr = recover(src);
8522 assert_eq!(pr.errors.len(), 1, "errors: {:?}", pr.errors);
8523 assert_eq!(pr.program.declarations.len(), 2);
8524 }
8525
8526 #[test]
8527 fn error_in_first_decl_does_not_block_second() {
8528 let src = "flow F() { not_a_step nope } flow G() { }";
8531 let pr = recover(src);
8532 assert!(pr.has_errors(), "expected at least one error");
8533 let names: Vec<&str> = pr
8535 .program
8536 .declarations
8537 .iter()
8538 .filter_map(|d| match d {
8539 Declaration::Flow(f) => Some(f.name.as_str()),
8540 _ => None,
8541 })
8542 .collect();
8543 assert!(names.contains(&"G"), "G not found among {names:?}");
8544 }
8545
8546 #[test]
8547 fn malformed_declaration_then_clean_intent_recovers() {
8548 let src = "flow @ () { } intent I {}";
8549 let pr = recover(src);
8550 assert!(pr.has_errors());
8551 let kinds: Vec<&str> = pr
8552 .program
8553 .declarations
8554 .iter()
8555 .map(|d| match d {
8556 Declaration::Intent(_) => "intent",
8557 Declaration::Flow(_) => "flow",
8558 _ => "other",
8559 })
8560 .collect();
8561 assert!(kinds.contains(&"intent"), "kinds: {kinds:?}");
8562 }
8563
8564 #[test]
8565 fn recovery_does_not_double_count_a_single_error() {
8566 let src = "flow F() { not_a_step }";
8574 let pr = recover(src);
8575 let outer_ghosts = pr
8576 .errors
8577 .iter()
8578 .filter(|e| e.message.contains("at top level"))
8579 .count();
8580 assert_eq!(outer_ghosts, 0, "ghost errors: {:?}", pr.errors);
8581 }
8582
8583 #[test]
8586 fn three_independent_errors_yield_three_entries() {
8587 let src =
8588 "garbage1 flow F() { } garbage2 flow G() { } garbage3 flow H() { }";
8589 let pr = recover(src);
8590 assert_eq!(pr.errors.len(), 3, "errors: {:?}", pr.errors);
8591 assert_eq!(pr.program.declarations.len(), 3);
8592 }
8593
8594 #[test]
8595 fn all_errors_no_valid_declarations() {
8596 let src = "foo bar baz qux";
8597 let pr = recover(src);
8598 assert!(pr.has_errors());
8599 assert!(pr.program.declarations.is_empty());
8600 }
8601
8602 #[test]
8603 fn errors_recorded_in_source_order() {
8604 let src = "x flow A() { } y flow B() { } z flow C() { }";
8605 let pr = recover(src);
8606 assert_eq!(pr.errors.len(), 3);
8607 let lines: Vec<u32> = pr.errors.iter().map(|e| e.line).collect();
8608 assert!(
8611 lines.windows(2).all(|w| w[0] <= w[1]),
8612 "errors out of order: {lines:?}"
8613 );
8614 }
8615
8616 #[test]
8619 fn sync_to_flow_keyword() {
8620 let src = "garbage flow F() { }";
8621 let pr = recover(src);
8622 assert_eq!(pr.program.declarations.len(), 1);
8623 }
8624
8625 #[test]
8626 fn sync_to_intent_keyword() {
8627 let src = "garbage intent I {}";
8628 let pr = recover(src);
8629 assert_eq!(pr.program.declarations.len(), 1);
8630 }
8631
8632 #[test]
8633 fn sync_to_persona_keyword() {
8634 let src = "garbage persona P { name: \"P\" role: \"R\" }";
8635 let pr = recover(src);
8636 assert!(
8637 pr.program
8638 .declarations
8639 .iter()
8640 .any(|d| matches!(d, Declaration::Persona(_))),
8641 "persona not recovered: decls = {:?}",
8642 pr.program.declarations.len()
8643 );
8644 }
8645
8646 #[test]
8647 fn sync_to_run_keyword() {
8648 let src = "garbage run R { input: { user_message: \"hi\" } }";
8649 let pr = recover(src);
8650 assert!(pr.has_errors());
8652 }
8653
8654 #[test]
8657 fn parse_result_has_errors_and_is_clean_invert() {
8658 let pr_clean = recover("flow F() { }");
8659 assert!(pr_clean.is_clean());
8660 assert!(!pr_clean.has_errors());
8661
8662 let pr_err = recover("garbage");
8663 assert!(!pr_err.is_clean());
8664 assert!(pr_err.has_errors());
8665 }
8666
8667 #[test]
8668 fn parse_result_program_field_holds_partial_program() {
8669 let pr = recover("garbage flow F() { }");
8670 assert!(!pr.program.declarations.is_empty());
8671 }
8672
8673 #[test]
8674 fn parse_result_errors_carry_line_and_column() {
8675 let pr = recover("garbage");
8676 assert!(!pr.errors.is_empty());
8677 let e = &pr.errors[0];
8678 assert!(e.line >= 1);
8679 let _ = e.column;
8682 assert!(!e.message.is_empty());
8683 }
8684
8685 #[test]
8686 fn parse_result_debug_renders() {
8687 let pr = recover("flow F() { }");
8688 let s = format!("{pr:?}");
8689 assert!(s.contains("ParseResult"));
8690 }
8691
8692 #[test]
8695 fn empty_source_is_clean() {
8696 let pr = recover("");
8697 assert!(pr.is_clean());
8698 assert!(pr.program.declarations.is_empty());
8699 }
8700
8701 #[test]
8702 fn whitespace_only_source_is_clean() {
8703 let pr = recover(" \n\n\t \n");
8704 assert!(pr.is_clean());
8705 assert!(pr.program.declarations.is_empty());
8706 }
8707
8708 #[test]
8709 fn only_garbage_does_not_crash() {
8710 let pr = recover("foo bar baz { qux quux } corge { grault }");
8712 assert!(pr.has_errors());
8713 }
8714
8715 #[test]
8716 fn unbalanced_close_brace_does_not_crash() {
8717 let pr = recover("} flow F() { }");
8718 let names: Vec<&str> = pr
8720 .program
8721 .declarations
8722 .iter()
8723 .filter_map(|d| match d {
8724 Declaration::Flow(f) => Some(f.name.as_str()),
8725 _ => None,
8726 })
8727 .collect();
8728 assert!(names.contains(&"F"), "F not recovered: {names:?}");
8729 }
8730
8731 #[test]
8732 fn error_at_eof_does_not_loop() {
8733 let pr = recover("flow F() { ");
8735 let _ = pr.errors.len();
8737 }
8738
8739 #[test]
8740 fn nested_braces_inside_error_still_balance() {
8741 let src = "flow F() { not_a_step { inner } } flow G() { }";
8744 let pr = recover(src);
8745 let names: Vec<&str> = pr
8746 .program
8747 .declarations
8748 .iter()
8749 .filter_map(|d| match d {
8750 Declaration::Flow(f) => Some(f.name.as_str()),
8751 _ => None,
8752 })
8753 .collect();
8754 assert!(names.contains(&"G"), "G not recovered: {names:?}");
8755 }
8756
8757 #[derive(Clone, Copy)]
8766 struct Xorshift(u64);
8767 impl Xorshift {
8768 fn next(&mut self) -> u64 {
8769 let mut x = self.0;
8770 x ^= x << 13;
8771 x ^= x >> 7;
8772 x ^= x << 17;
8773 self.0 = x;
8774 x
8775 }
8776 fn pick<T: Copy>(&mut self, slice: &[T]) -> T {
8777 slice[(self.next() as usize) % slice.len()]
8778 }
8779 }
8780
8781 fn mutate(src: &str, rng: &mut Xorshift) -> String {
8782 let mut bytes: Vec<u8> = src.bytes().collect();
8783 if bytes.is_empty() {
8784 return src.to_string();
8785 }
8786 let op = rng.next() % 4;
8787 let pos = (rng.next() as usize) % bytes.len();
8788 let safe: &[u8] = b"abcdefghijklmnopqrstuvwxyz {}();:,_0123456789";
8792 match op {
8793 0 => {
8794 bytes.remove(pos);
8795 }
8796 1 => {
8797 let b = rng.pick(safe);
8798 bytes.insert(pos, b);
8799 }
8800 2 if pos + 1 < bytes.len() => {
8801 bytes.swap(pos, pos + 1);
8802 }
8803 _ => {
8804 let b = rng.pick(safe);
8805 bytes[pos] = b;
8806 }
8807 }
8808 bytes.retain(|b| b.is_ascii());
8811 String::from_utf8_lossy(&bytes).into_owned()
8812 }
8813
8814 #[test]
8815 fn fuzz_recovery_never_crashes() {
8816 let seed_bases = [
8817 "flow F() { }",
8818 "intent I { }",
8819 "persona P { name: \"P\" role: \"R\" }",
8820 "intent J { ask: \"a\" }",
8821 "type T = String",
8822 ];
8823 for (bucket, base) in (0..100u64).zip(seed_bases.iter().cycle()) {
8825 let mut rng = Xorshift(0x1234_5678_9abc_def0_u64.wrapping_add(bucket));
8826 let mut current = (*base).to_string();
8827 for _ in 0..10 {
8828 current = mutate(¤t, &mut rng);
8829 let toks = match Lexer::new(¤t, "<fuzz>").tokenize() {
8832 Ok(t) => t,
8833 Err(_) => continue,
8834 };
8835 let _pr = Parser::new(toks).parse_with_recovery();
8837 }
8838 }
8839 }
8840
8841 #[test]
8844 fn missing_colon_hint_preserved_under_recovery() {
8845 let src = "flow F() { run R { input { user_message: \"hi\" } } }";
8849 let pr = recover(src);
8850 if !pr.errors.is_empty() {
8854 let any_msg = pr.errors.iter().any(|e| !e.message.is_empty());
8855 assert!(any_msg);
8856 }
8857 }
8858
8859 #[test]
8862 fn recovered_declarations_appear_in_source_order() {
8863 let src = "flow A() { } garbage flow B() { } garbage flow C() { }";
8864 let pr = recover(src);
8865 let names: Vec<&str> = pr
8866 .program
8867 .declarations
8868 .iter()
8869 .filter_map(|d| match d {
8870 Declaration::Flow(f) => Some(f.name.as_str()),
8871 _ => None,
8872 })
8873 .collect();
8874 assert_eq!(names, vec!["A", "B", "C"]);
8875 }
8876}
8877
8878#[cfg(test)]
8886mod fase28_source_context_tests {
8887 use super::*;
8888 use crate::lexer::Lexer;
8889
8890 fn snippet(source: &str, line: u32, column: u32, filename: &str) -> String {
8891 SourceSnippet::new(
8892 source.to_string(),
8893 line,
8894 column,
8895 filename.to_string(),
8896 )
8897 .render()
8898 }
8899
8900 #[test]
8903 fn rustc_style_block_for_middle_line() {
8904 let src = "line one\nline two\nline three\nline four\nline five";
8905 let out = snippet(src, 3, 6, "x.axon");
8906 assert!(out.contains("--> x.axon:3:6"));
8907 assert!(out.contains("1 | line one"));
8908 assert!(out.contains("2 | line two"));
8909 assert!(out.contains("3 | line three"));
8910 assert!(out.contains("4 | line four"));
8911 assert!(out.contains("5 | line five"));
8912 assert!(out.contains("\n | ^"), "out:\n{out}");
8914 }
8915
8916 #[test]
8917 fn caret_column_one_renders_correctly() {
8918 let out = snippet("abc\n", 1, 1, "<source>");
8919 assert!(out.contains("\n | ^"));
8920 }
8921
8922 #[test]
8923 fn first_line_clamps_context_before_to_zero() {
8924 let src = "first\nsecond\nthird\nfourth\nfifth";
8925 let out = snippet(src, 1, 1, "<source>");
8926 assert!(out.contains("1 | first"));
8927 assert!(out.contains("2 | second"));
8928 assert!(out.contains("3 | third"));
8929 assert!(!out.contains("4 | fourth"));
8930 }
8931
8932 #[test]
8933 fn last_line_clamps_context_after_to_eof() {
8934 let src = "first\nsecond\nthird\nfourth\nfifth";
8935 let out = snippet(src, 5, 2, "<source>");
8936 assert!(out.contains("5 | fifth"));
8937 assert!(out.contains("3 | third"));
8938 assert!(out.contains("4 | fourth"));
8939 assert!(!out.contains("2 | second"));
8940 }
8941
8942 #[test]
8943 fn gutter_width_grows_with_line_count() {
8944 let src: String = (1..=12).map(|i| format!("line{i}")).collect::<Vec<_>>().join("\n");
8945 let out = snippet(&src, 12, 1, "<source>");
8946 assert!(out.contains("12 | line12"));
8947 assert!(out.contains("10 | line10"));
8948 }
8949
8950 #[test]
8953 fn empty_source_returns_empty() {
8954 assert_eq!(snippet("", 1, 1, "<source>"), "");
8955 }
8956
8957 #[test]
8958 fn zero_line_returns_empty() {
8959 assert_eq!(snippet("hi", 0, 1, "<source>"), "");
8960 }
8961
8962 #[test]
8963 fn out_of_range_line_returns_empty() {
8964 assert_eq!(snippet("hi", 99, 1, "<source>"), "");
8965 }
8966
8967 #[test]
8968 fn caret_clamps_past_eol() {
8969 let out = snippet("hello", 1, 50, "<source>");
8970 assert!(out.contains("\n | ^"), "out:\n{out}");
8971 }
8972
8973 #[test]
8974 fn unicode_codepoint_count_for_caret_clamp() {
8975 let out = snippet("héllo", 1, 99, "<source>");
8977 assert!(out.contains("\n | ^"), "out:\n{out}");
8978 }
8979
8980 #[test]
8981 fn trailing_newline_does_not_create_phantom_last_line() {
8982 let out = snippet("first\nsecond\n", 2, 1, "<source>");
8983 assert!(!out.contains("3 |"));
8984 assert!(out.contains("2 | second"));
8985 }
8986
8987 fn lex(src: &str) -> Vec<Token> {
8990 Lexer::new(src, "<test>").tokenize().expect("lex")
8991 }
8992
8993 #[test]
8994 fn strict_parse_attaches_snippet_when_source_given() {
8995 let src = "garbage_token\nflow F() { }";
8996 let err = Parser::new(lex(src))
8997 .with_source(src, "x.axon")
8998 .parse()
8999 .expect_err("must error");
9000 assert!(err.source_snippet.is_some());
9001 let display = format!("{err}");
9002 assert!(display.contains("--> x.axon:"), "display: {display}");
9003 }
9004
9005 #[test]
9006 fn strict_parse_no_snippet_when_no_source() {
9007 let src = "garbage_token";
9008 let err = Parser::new(lex(src)).parse().expect_err("must error");
9009 assert!(err.source_snippet.is_none());
9010 let display = format!("{err}");
9011 assert!(!display.contains("\n -->"));
9012 }
9013
9014 #[test]
9015 fn every_recovered_error_has_snippet() {
9016 let src = "garbage1\nflow F() { }\ngarbage2\nflow G() { }";
9017 let result = Parser::new(lex(src))
9018 .with_source(src, "multi.axon")
9019 .parse_with_recovery();
9020 assert!(!result.errors.is_empty());
9021 for err in &result.errors {
9022 assert!(err.source_snippet.is_some());
9023 let display = format!("{err}");
9024 assert!(
9025 display.contains("--> multi.axon:"),
9026 "display: {display}"
9027 );
9028 }
9029 }
9030
9031 #[test]
9032 fn recovery_no_snippet_when_no_source() {
9033 let src = "garbage1 garbage2";
9034 let result = Parser::new(lex(src)).parse_with_recovery();
9035 for err in &result.errors {
9036 assert!(err.source_snippet.is_none());
9037 }
9038 }
9039
9040 #[test]
9041 fn snippet_points_at_correct_line_for_each_error() {
9042 let src = "garbage_a\nflow F() { }\ngarbage_b\nflow G() { }";
9043 let result = Parser::new(lex(src))
9044 .with_source(src, "x")
9045 .parse_with_recovery();
9046 for err in &result.errors {
9047 let sn = err.source_snippet.as_ref().expect("snippet");
9048 assert_eq!(sn.line, err.line);
9049 }
9050 }
9051
9052 #[test]
9055 fn legacy_constructor_still_works() {
9056 let src = "flow F() { }";
9057 let prog = Parser::new(lex(src)).parse().expect("clean");
9058 assert_eq!(prog.declarations.len(), 1);
9059 }
9060
9061 #[test]
9062 fn attach_source_idempotent() {
9063 let err = ParseError {
9064 message: "bad".to_string(),
9065 line: 2,
9066 column: 3,
9067 ..Default::default()
9068 };
9069 let err2 = err.clone().attach_source("a\nb\nc\n", "f.axon");
9070 let first = format!("{err2}");
9071 let err3 = err.attach_source("a\nb\nc\n", "f.axon");
9072 let second = format!("{err3}");
9073 assert_eq!(first, second);
9074 }
9075
9076 #[test]
9077 fn attach_source_noop_when_line_zero() {
9078 let err = ParseError {
9079 message: "bad".to_string(),
9080 line: 0,
9081 column: 0,
9082 ..Default::default()
9083 };
9084 let err = err.attach_source("a\nb\nc\n", "f.axon");
9085 assert!(err.source_snippet.is_none());
9086 }
9087
9088 #[test]
9094 fn golden_simple_three_line_block() {
9095 let src = "alpha\nbeta\ngamma";
9096 let out = snippet(src, 2, 3, "g.axon");
9097 let expected = concat!(
9101 " --> g.axon:2:3\n",
9102 " |\n",
9103 "1 | alpha\n",
9104 "2 | beta\n",
9105 " | ^\n",
9106 "3 | gamma",
9107 );
9108 assert_eq!(out, expected);
9109 }
9110
9111 #[test]
9112 fn golden_first_line_caret() {
9113 let src = "abc\ndef\n";
9114 let out = snippet(src, 1, 1, "x");
9115 let expected = concat!(
9116 " --> x:1:1\n",
9117 " |\n",
9118 "1 | abc\n",
9119 " | ^\n",
9120 "2 | def",
9121 );
9122 assert_eq!(out, expected);
9123 }
9124
9125 #[test]
9126 fn golden_two_digit_gutter() {
9127 let src: String = (1..=11)
9128 .map(|i| format!("L{i}"))
9129 .collect::<Vec<_>>()
9130 .join("\n");
9131 let out = snippet(&src, 10, 2, "big");
9132 let expected = concat!(
9133 " --> big:10:2\n",
9134 " |\n",
9135 " 8 | L8\n",
9136 " 9 | L9\n",
9137 "10 | L10\n",
9138 " | ^\n",
9139 "11 | L11",
9140 );
9141 assert_eq!(out, expected);
9142 }
9143}
9144
9145#[cfg(test)]
9152mod fase28_smart_suggest_parser_tests {
9153 use super::*;
9154 use crate::lexer::Lexer;
9155
9156 fn lex(src: &str) -> Vec<Token> {
9157 Lexer::new(src, "<test>").tokenize().expect("lex")
9158 }
9159
9160 #[test]
9161 fn top_level_typo_suggests_flow() {
9162 let src = "flwo F() { }";
9163 let err = Parser::new(lex(src)).parse().expect_err("must error");
9164 assert!(
9165 err.message.contains("Did you mean `flow`?"),
9166 "msg: {}",
9167 err.message
9168 );
9169 }
9170
9171 #[test]
9172 fn top_level_unknown_far_no_suggestion() {
9173 let src = "qwerty F() { }";
9174 let err = Parser::new(lex(src)).parse().expect_err("must error");
9175 assert!(
9176 !err.message.contains("Did you mean"),
9177 "msg: {}",
9178 err.message
9179 );
9180 }
9181
9182 #[test]
9183 fn flow_body_typo_suggests_step() {
9184 let src = "flow F() { stepp S {} }";
9185 let err = Parser::new(lex(src)).parse().expect_err("must error");
9186 assert!(
9187 err.message.contains("Did you mean `step`"),
9188 "msg: {}",
9189 err.message
9190 );
9191 }
9192
9193 #[test]
9194 fn flow_body_typo_suggests_reason() {
9195 let src = "flow F() { reasn R {} }";
9196 let err = Parser::new(lex(src)).parse().expect_err("must error");
9197 assert!(
9198 err.message.contains("Did you mean `reason`?"),
9199 "msg: {}",
9200 err.message
9201 );
9202 }
9203
9204 #[test]
9205 fn recovery_mode_carries_hint() {
9206 let src = "flwo F() { }";
9207 let result = Parser::new(lex(src)).parse_with_recovery();
9208 assert!(
9209 result
9210 .errors
9211 .iter()
9212 .any(|e| e.message.contains("Did you mean `flow`?")),
9213 "errors: {:?}",
9214 result.errors
9215 );
9216 }
9217}
9218
9219#[cfg(test)]
9222mod fase35m_mutate_purge_where_tests {
9223 use super::*;
9224
9225 fn parse(src: &str) -> Program {
9226 let tokens = crate::lexer::Lexer::new(src, "<test>")
9227 .tokenize()
9228 .expect("lex");
9229 Parser::new(tokens).parse().expect("parse")
9230 }
9231
9232 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
9233 for d in &prog.declarations {
9234 if let Declaration::Flow(f) = d {
9235 if f.name == flow {
9236 return f.body.first().expect("flow has at least one step");
9237 }
9238 }
9239 }
9240 panic!("flow `{flow}` not found");
9241 }
9242
9243 #[test]
9244 fn mutate_captures_its_where_clause() {
9245 let prog =
9248 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
9249 match first_step(&prog, "F") {
9250 FlowStep::Mutate(m) => {
9251 assert_eq!(m.store_name, "accounts");
9252 assert_eq!(m.where_expr, "id = 1");
9253 }
9254 other => panic!("expected Mutate, got {other:?}"),
9255 }
9256 }
9257
9258 #[test]
9259 fn purge_captures_its_where_clause() {
9260 let prog =
9261 parse("flow F() -> Unit { purge logs { where: \"ts < 100\" } }");
9262 match first_step(&prog, "F") {
9263 FlowStep::Purge(p) => {
9264 assert_eq!(p.store_name, "logs");
9265 assert_eq!(p.where_expr, "ts < 100");
9266 }
9267 other => panic!("expected Purge, got {other:?}"),
9268 }
9269 }
9270
9271 #[test]
9272 fn mutate_without_a_where_block_is_a_whole_store_op() {
9273 let prog = parse("flow F() -> Unit { mutate accounts }");
9276 match first_step(&prog, "F") {
9277 FlowStep::Mutate(m) => {
9278 assert_eq!(m.store_name, "accounts");
9279 assert_eq!(m.where_expr, "");
9280 }
9281 other => panic!("expected Mutate, got {other:?}"),
9282 }
9283 }
9284}
9285
9286#[cfg(test)]
9289mod fase35o_persist_fields_tests {
9290 use super::*;
9291
9292 fn parse(src: &str) -> Program {
9293 let tokens = crate::lexer::Lexer::new(src, "<test>")
9294 .tokenize()
9295 .expect("lex");
9296 Parser::new(tokens).parse().expect("parse")
9297 }
9298
9299 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
9300 for d in &prog.declarations {
9301 if let Declaration::Flow(f) = d {
9302 if f.name == flow {
9303 return f.body.first().expect("flow has at least one step");
9304 }
9305 }
9306 }
9307 panic!("flow `{flow}` not found");
9308 }
9309
9310 #[test]
9311 fn persist_captures_its_field_block() {
9312 let prog = parse(
9316 "flow F() -> Unit { persist into chat_history { \
9317 session_id: \"${session_id}\" sender: \"user\" \
9318 content: \"${message}\" } }",
9319 );
9320 match first_step(&prog, "F") {
9321 FlowStep::Persist(p) => {
9322 assert_eq!(p.store_name, "chat_history");
9323 assert_eq!(
9324 p.fields,
9325 vec![
9326 ("session_id".to_string(), "${session_id}".to_string()),
9327 ("sender".to_string(), "user".to_string()),
9328 ("content".to_string(), "${message}".to_string()),
9329 ]
9330 );
9331 }
9332 other => panic!("expected Persist, got {other:?}"),
9333 }
9334 }
9335
9336 #[test]
9337 fn persist_without_a_block_keeps_the_user_bindings_fallback() {
9338 let prog = parse("flow F() -> Unit { persist events }");
9341 match first_step(&prog, "F") {
9342 FlowStep::Persist(p) => {
9343 assert_eq!(p.store_name, "events");
9344 assert!(p.fields.is_empty());
9345 }
9346 other => panic!("expected Persist, got {other:?}"),
9347 }
9348 }
9349
9350 #[test]
9351 fn persist_accepts_the_optional_into_connector() {
9352 let with =
9355 parse("flow F() -> Unit { persist into accounts { id: \"1\" } }");
9356 let without =
9357 parse("flow F() -> Unit { persist accounts { id: \"1\" } }");
9358 for prog in [&with, &without] {
9359 match first_step(prog, "F") {
9360 FlowStep::Persist(p) => assert_eq!(p.store_name, "accounts"),
9361 other => panic!("expected Persist, got {other:?}"),
9362 }
9363 }
9364 }
9365
9366 #[test]
9367 fn persist_into_without_a_block_resolves_the_store_name() {
9368 let prog = parse("flow F() -> Unit { persist into events }");
9371 match first_step(&prog, "F") {
9372 FlowStep::Persist(p) => {
9373 assert_eq!(p.store_name, "events");
9374 assert!(p.fields.is_empty());
9375 }
9376 other => panic!("expected Persist, got {other:?}"),
9377 }
9378 }
9379
9380 #[test]
9381 fn persist_fields_lower_into_the_ir() {
9382 let prog = parse(
9385 "flow F() -> Unit { persist into chat { content: \"${msg}\" } }",
9386 );
9387 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
9388 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
9389 match flow.steps.first().expect("one step") {
9390 crate::ir_nodes::IRFlowNode::Persist(p) => {
9391 assert_eq!(p.store_name, "chat");
9392 assert_eq!(
9393 p.fields,
9394 vec![("content".to_string(), "${msg}".to_string())]
9395 );
9396 }
9397 other => panic!("expected IRFlowNode::Persist, got {other:?}"),
9398 }
9399 }
9400}
9401
9402#[cfg(test)]
9405mod fase35p_mutate_fields_tests {
9406 use super::*;
9407
9408 fn parse(src: &str) -> Program {
9409 let tokens = crate::lexer::Lexer::new(src, "<test>")
9410 .tokenize()
9411 .expect("lex");
9412 Parser::new(tokens).parse().expect("parse")
9413 }
9414
9415 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
9416 for d in &prog.declarations {
9417 if let Declaration::Flow(f) = d {
9418 if f.name == flow {
9419 return f.body.first().expect("flow has at least one step");
9420 }
9421 }
9422 }
9423 panic!("flow `{flow}` not found");
9424 }
9425
9426 #[test]
9427 fn mutate_captures_its_set_field_block() {
9428 let prog = parse(
9432 "flow F() -> Unit { mutate accounts { where: \"id = ${id}\" \
9433 balance: \"${new_balance}\" status: \"active\" } }",
9434 );
9435 match first_step(&prog, "F") {
9436 FlowStep::Mutate(m) => {
9437 assert_eq!(m.store_name, "accounts");
9438 assert_eq!(m.where_expr, "id = ${id}");
9439 assert_eq!(
9440 m.fields,
9441 vec![
9442 ("balance".to_string(), "${new_balance}".to_string()),
9443 ("status".to_string(), "active".to_string()),
9444 ]
9445 );
9446 }
9447 other => panic!("expected Mutate, got {other:?}"),
9448 }
9449 }
9450
9451 #[test]
9452 fn mutate_where_only_block_has_no_set_fields() {
9453 let prog =
9456 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
9457 match first_step(&prog, "F") {
9458 FlowStep::Mutate(m) => {
9459 assert_eq!(m.where_expr, "id = 1");
9460 assert!(m.fields.is_empty());
9461 }
9462 other => panic!("expected Mutate, got {other:?}"),
9463 }
9464 }
9465
9466 #[test]
9467 fn mutate_with_no_block_is_a_whole_store_op() {
9468 let prog = parse("flow F() -> Unit { mutate accounts }");
9471 match first_step(&prog, "F") {
9472 FlowStep::Mutate(m) => {
9473 assert_eq!(m.store_name, "accounts");
9474 assert_eq!(m.where_expr, "");
9475 assert!(m.fields.is_empty());
9476 }
9477 other => panic!("expected Mutate, got {other:?}"),
9478 }
9479 }
9480
9481 #[test]
9482 fn mutate_fields_lower_into_the_ir() {
9483 let prog = parse(
9484 "flow F() -> Unit { mutate t { where: \"id = 1\" v: \"${x}\" } }",
9485 );
9486 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
9487 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
9488 match flow.steps.first().expect("one step") {
9489 crate::ir_nodes::IRFlowNode::Mutate(m) => {
9490 assert_eq!(m.where_expr, "id = 1");
9491 assert_eq!(
9492 m.fields,
9493 vec![("v".to_string(), "${x}".to_string())]
9494 );
9495 }
9496 other => panic!("expected IRFlowNode::Mutate, got {other:?}"),
9497 }
9498 }
9499}
9500