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::Psyche(n) => {
118 n.leading_trivia = leading;
119 n.trailing_trivia = trailing;
120 }
121 Declaration::Corpus(n) => {
122 n.leading_trivia = leading;
123 n.trailing_trivia = trailing;
124 }
125 Declaration::Dataspace(n) => {
126 n.leading_trivia = leading;
127 n.trailing_trivia = trailing;
128 }
129 Declaration::Ots(n) => {
130 n.leading_trivia = leading;
131 n.trailing_trivia = trailing;
132 }
133 Declaration::Mandate(n) => {
134 n.leading_trivia = leading;
135 n.trailing_trivia = trailing;
136 }
137 Declaration::Compute(n) => {
138 n.leading_trivia = leading;
139 n.trailing_trivia = trailing;
140 }
141 Declaration::Daemon(n) => {
142 n.leading_trivia = leading;
143 n.trailing_trivia = trailing;
144 }
145 Declaration::Extension(n) => {
146 n.leading_trivia = leading;
147 n.trailing_trivia = trailing;
148 }
149 Declaration::AxonStore(n) => {
150 n.leading_trivia = leading;
151 n.trailing_trivia = trailing;
152 }
153 Declaration::AxonEndpoint(n) => {
154 n.leading_trivia = leading;
155 n.trailing_trivia = trailing;
156 }
157 Declaration::Resource(n) => {
158 n.leading_trivia = leading;
159 n.trailing_trivia = trailing;
160 }
161 Declaration::Fabric(n) => {
162 n.leading_trivia = leading;
163 n.trailing_trivia = trailing;
164 }
165 Declaration::Manifest(n) => {
166 n.leading_trivia = leading;
167 n.trailing_trivia = trailing;
168 }
169 Declaration::Observe(n) => {
170 n.leading_trivia = leading;
171 n.trailing_trivia = trailing;
172 }
173 Declaration::Reconcile(n) => {
174 n.leading_trivia = leading;
175 n.trailing_trivia = trailing;
176 }
177 Declaration::Lease(n) => {
178 n.leading_trivia = leading;
179 n.trailing_trivia = trailing;
180 }
181 Declaration::Ensemble(n) => {
182 n.leading_trivia = leading;
183 n.trailing_trivia = trailing;
184 }
185 Declaration::Session(n) => {
186 n.leading_trivia = leading;
187 n.trailing_trivia = trailing;
188 }
189 Declaration::Topology(n) => {
190 n.leading_trivia = leading;
191 n.trailing_trivia = trailing;
192 }
193 Declaration::Immune(n) => {
194 n.leading_trivia = leading;
195 n.trailing_trivia = trailing;
196 }
197 Declaration::Reflex(n) => {
198 n.leading_trivia = leading;
199 n.trailing_trivia = trailing;
200 }
201 Declaration::Heal(n) => {
202 n.leading_trivia = leading;
203 n.trailing_trivia = trailing;
204 }
205 Declaration::Component(n) => {
206 n.leading_trivia = leading;
207 n.trailing_trivia = trailing;
208 }
209 Declaration::View(n) => {
210 n.leading_trivia = leading;
211 n.trailing_trivia = trailing;
212 }
213 Declaration::Channel(n) => {
214 n.leading_trivia = leading;
215 n.trailing_trivia = trailing;
216 }
217 Declaration::Socket(n) => {
218 n.leading_trivia = leading;
219 n.trailing_trivia = trailing;
220 }
221 Declaration::Generic(n) => {
222 n.leading_trivia = leading;
223 n.trailing_trivia = trailing;
224 }
225 }
226}
227
228pub const SOURCE_CONTEXT_LINES_BEFORE: usize = 2;
235pub const SOURCE_CONTEXT_LINES_AFTER: usize = 2;
236
237#[derive(Debug, Clone)]
248pub struct SourceSnippet {
249 pub source: String,
250 pub line: u32,
251 pub column: u32,
252 pub filename: String,
253 pub context_before: usize,
254 pub context_after: usize,
255}
256
257impl SourceSnippet {
258 pub fn new(source: String, line: u32, column: u32, filename: String) -> Self {
260 Self {
261 source,
262 line,
263 column,
264 filename,
265 context_before: SOURCE_CONTEXT_LINES_BEFORE,
266 context_after: SOURCE_CONTEXT_LINES_AFTER,
267 }
268 }
269
270 #[must_use]
277 pub fn render(&self) -> String {
278 if self.source.is_empty() || self.line < 1 {
279 return String::new();
280 }
281 let raw: Vec<&str> = self.source.split('\n').collect();
282 let lines: Vec<&str> = if raw.last() == Some(&"") {
285 raw[..raw.len() - 1].to_vec()
286 } else {
287 raw
288 };
289 if lines.is_empty() || self.line as usize > lines.len() {
290 return String::new();
291 }
292
293 let line_idx = self.line as usize;
294 let start = line_idx.saturating_sub(self.context_before).max(1);
295 let end = (line_idx + self.context_after).min(lines.len());
296
297 let gutter = end.to_string().len();
298 let empty_gutter = " ".repeat(gutter);
299
300 let mut out: Vec<String> = Vec::with_capacity(end - start + 4);
301 out.push(format!(
302 "{empty_gutter} --> {}:{}:{}",
303 self.filename, self.line, self.column
304 ));
305 out.push(format!("{empty_gutter} |"));
306 for n in start..=end {
307 let line_text = lines[n - 1];
308 out.push(format!("{n:>gutter$} | {line_text}", gutter = gutter));
309 if n == line_idx {
310 let line_len = line_text.chars().count();
311 let col = (self.column as usize).clamp(1, line_len + 1);
312 out.push(format!(
313 "{empty_gutter} | {pad}^",
314 pad = " ".repeat(col - 1)
315 ));
316 }
317 }
318 out.join("\n")
319 }
320}
321
322#[derive(Debug, Clone, Default)]
323pub struct ParseError {
324 pub message: String,
325 pub line: u32,
326 pub column: u32,
327 pub source_snippet: Option<SourceSnippet>,
334}
335
336impl ParseError {
337 #[must_use]
341 pub fn attach_source(mut self, source: &str, filename: &str) -> Self {
342 if self.line >= 1 {
343 self.source_snippet = Some(SourceSnippet::new(
344 source.to_string(),
345 self.line,
346 self.column,
347 filename.to_string(),
348 ));
349 }
350 self
351 }
352}
353
354impl std::fmt::Display for ParseError {
355 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356 write!(f, "[line {}:{}] {}", self.line, self.column, self.message)?;
357 if let Some(snippet) = &self.source_snippet {
358 let block = snippet.render();
359 if !block.is_empty() {
360 write!(f, "\n{block}")?;
361 }
362 }
363 Ok(())
364 }
365}
366
367impl std::error::Error for ParseError {}
368
369#[derive(Debug)]
387pub struct ParseResult {
388 pub program: Program,
389 pub errors: Vec<ParseError>,
390}
391
392impl ParseResult {
393 #[inline]
398 #[must_use]
399 pub fn has_errors(&self) -> bool {
400 !self.errors.is_empty()
401 }
402
403 #[inline]
406 #[must_use]
407 pub fn is_clean(&self) -> bool {
408 self.errors.is_empty()
409 }
410}
411
412#[inline]
426const fn is_top_level_decl_kw_for_recovery(tt: &TokenType) -> bool {
427 matches!(
428 tt,
429 TokenType::Import
430 | TokenType::Persona
431 | TokenType::Context
432 | TokenType::Anchor
433 | TokenType::Memory
434 | TokenType::Tool
435 | TokenType::Type
436 | TokenType::Flow
437 | TokenType::Intent
438 | TokenType::Run
439 | TokenType::Let
440 | TokenType::Know
441 | TokenType::Believe
442 | TokenType::Speculate
443 | TokenType::Doubt
444 | TokenType::Lambda
445 | TokenType::Agent
446 | TokenType::Shield
447 | TokenType::Pix
448 | TokenType::Psyche
449 | TokenType::Corpus
450 | TokenType::Dataspace
451 | TokenType::Ots
452 | TokenType::Mandate
453 | TokenType::Compute
454 | TokenType::Daemon
455 | TokenType::AxonStore
456 | TokenType::AxonEndpoint
457 | TokenType::Resource
458 | TokenType::Fabric
459 | TokenType::Manifest
460 | TokenType::Observe
461 | TokenType::Reconcile
462 | TokenType::Lease
463 | TokenType::Ensemble
464 | TokenType::Session
465 | TokenType::Topology
466 | TokenType::Immune
467 | TokenType::Reflex
468 | TokenType::Heal
469 | TokenType::Component
470 | TokenType::View
471 | TokenType::Channel
472 | TokenType::Ingest
473 | TokenType::Persist
474 | TokenType::Retrieve
475 | TokenType::Mutate
476 | TokenType::Purge
477 | TokenType::Transact
478 | TokenType::Mcp
479 )
480}
481
482pub const AXONENDPOINT_TRANSPORT_VALUES: &[&str] = &["json", "sse", "ndjson"];
495
496pub const AXONENDPOINT_TRANSPORT_DIALECTS: &[&str] =
540 &["axon", "openai", "kimi", "glm", "anthropic"];
541
542pub const AXONENDPOINT_KEEPALIVE_VALUES: &[&str] = &["5s", "15s", "30s", "60s"];
544
545pub const AXONENDPOINT_METHOD_VALUES: &[&str] = &["GET", "POST", "PUT", "DELETE", "PATCH"];
551
552pub const AXONENDPOINT_BACKEND_VALUES: &[&str] = &[
573 "anthropic",
574 "auto",
575 "gemini",
576 "glm",
577 "kimi",
578 "ollama",
579 "openai",
580 "openrouter",
581 "stub",
582];
583
584#[inline]
585fn axonendpoint_is_valid_transport(s: &str) -> bool {
586 AXONENDPOINT_TRANSPORT_VALUES.iter().any(|&v| v == s)
587}
588
589#[inline]
590fn axonendpoint_is_valid_method(s: &str) -> bool {
591 AXONENDPOINT_METHOD_VALUES.iter().any(|&v| v == s)
592}
593
594#[inline]
595fn axonendpoint_is_valid_backend(s: &str) -> bool {
596 AXONENDPOINT_BACKEND_VALUES.iter().any(|&v| v == s)
597}
598
599#[inline]
600fn axonendpoint_is_valid_keepalive(s: &str) -> bool {
601 AXONENDPOINT_KEEPALIVE_VALUES.iter().any(|&v| v == s)
602}
603
604pub const AXONENDPOINT_QUERY_PARAM_TYPES: &[&str] =
619 &["Text", "Int", "Float", "Bool", "Uuid"];
620
621#[inline]
624pub(crate) fn axonendpoint_is_valid_query_param_type(s: &str) -> bool {
625 AXONENDPOINT_QUERY_PARAM_TYPES.iter().any(|&v| v == s)
626}
627
628pub(crate) fn extract_path_param_names(path: &str) -> Result<Vec<String>, String> {
655 let mut out: Vec<String> = Vec::new();
656 let bytes = path.as_bytes();
657 let mut i = 0;
658 while i < bytes.len() {
659 if bytes[i] != b'{' {
660 i += 1;
661 continue;
662 }
663 let start = i + 1;
666 let mut end = start;
667 while end < bytes.len() && bytes[end] != b'}' {
668 end += 1;
669 }
670 if end == bytes.len() {
671 break;
674 }
675 let raw = &path[start..end];
676 let valid = !raw.is_empty()
678 && raw.bytes().enumerate().all(|(idx, b)| {
679 if idx == 0 {
680 b.is_ascii_alphabetic() || b == b'_'
681 } else {
682 b.is_ascii_alphanumeric() || b == b'_'
683 }
684 });
685 if valid {
686 let name = raw.to_string();
687 if out.iter().any(|existing| existing == &name) {
688 return Err(name);
689 }
690 out.push(name);
691 }
692 i = end + 1;
693 }
694 Ok(out)
695}
696
697pub fn is_valid_capability_slug(slug: &str) -> bool {
711 if slug.is_empty() {
712 return false;
713 }
714 for segment in slug.split('.') {
715 if !is_valid_slug_segment(segment) {
716 return false;
717 }
718 }
719 true
720}
721
722fn is_valid_slug_segment(seg: &str) -> bool {
723 let mut chars = seg.chars();
724 let first = match chars.next() {
725 Some(c) => c,
726 None => return false,
727 };
728 if !first.is_ascii_lowercase() {
729 return false;
730 }
731 chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
732}
733
734#[cfg(test)]
744mod query_param_catalog_tests {
745 use super::{axonendpoint_is_valid_query_param_type, AXONENDPOINT_QUERY_PARAM_TYPES};
746
747 #[test]
748 fn accepts_every_catalog_entry() {
749 for ty in AXONENDPOINT_QUERY_PARAM_TYPES {
750 assert!(
751 axonendpoint_is_valid_query_param_type(ty),
752 "catalog entry `{ty}` must validate"
753 );
754 }
755 }
756
757 #[test]
758 fn rejects_off_catalog_types() {
759 for off in &[
760 "Timestamp", "Date",
762 "DateTime",
763 "List<Text>", "Jsonb", "Bytea",
766 "text", "TEXT",
768 "Number", "", " ", ] {
772 assert!(
773 !axonendpoint_is_valid_query_param_type(off),
774 "off-catalog `{off}` must reject"
775 );
776 }
777 }
778
779 #[test]
780 fn catalog_size_matches_design() {
781 assert_eq!(AXONENDPOINT_QUERY_PARAM_TYPES.len(), 5);
785 }
786}
787
788#[cfg(test)]
789mod query_param_parser_tests {
790 use crate::lexer::Lexer;
791 use crate::parser::Parser;
792
793 fn parse_endpoint_source(src: &str) -> Result<crate::ast::AxonEndpointDefinition, String> {
794 let tokens = Lexer::new(src, "test.axon")
795 .tokenize()
796 .map_err(|e| format!("lex: {}", e.message))?;
797 let mut parser = Parser::new(tokens);
798 let program = parser.parse().map_err(|e| format!("parse: {}", e.message))?;
799 program
800 .declarations
801 .into_iter()
802 .find_map(|d| match d {
803 crate::ast::Declaration::AxonEndpoint(e) => Some(e),
804 _ => None,
805 })
806 .ok_or_else(|| "no axonendpoint in program".to_string())
807 }
808
809 #[test]
810 fn endpoint_with_no_query_block_keeps_empty_vec() {
811 let src = r#"
812 axonendpoint write_secret {
813 method: POST
814 path: "/api/users"
815 body: SecretWriteRequest
816 execute: WriteSecret
817 }
818 "#;
819 let ep = parse_endpoint_source(src).expect("parses");
820 assert!(
821 ep.query_params.is_empty(),
822 "D5 — no `query:` block ⇒ empty query_params"
823 );
824 }
825
826 #[test]
827 fn single_query_param_required() {
828 let src = r#"
829 axonendpoint list_users {
830 method: GET
831 path: "/api/users"
832 query: { status: Text }
833 execute: ListUsers
834 }
835 "#;
836 let ep = parse_endpoint_source(src).expect("parses");
837 assert_eq!(ep.query_params.len(), 1);
838 assert_eq!(ep.query_params[0].name, "status");
839 assert_eq!(ep.query_params[0].type_expr.name, "Text");
840 assert!(!ep.query_params[0].type_expr.optional);
841 }
842
843 #[test]
844 fn optional_query_param_via_question_suffix() {
845 let src = r#"
846 axonendpoint list_users {
847 method: GET
848 path: "/api/users"
849 query: { limit: Int? }
850 execute: ListUsers
851 }
852 "#;
853 let ep = parse_endpoint_source(src).expect("parses");
854 assert_eq!(ep.query_params.len(), 1);
855 assert_eq!(ep.query_params[0].name, "limit");
856 assert_eq!(ep.query_params[0].type_expr.name, "Int");
857 assert!(
858 ep.query_params[0].type_expr.optional,
859 "`?` suffix sets optional"
860 );
861 }
862
863 #[test]
864 fn multiple_query_params_preserve_declaration_order() {
865 let src = r#"
866 axonendpoint search {
867 method: GET
868 path: "/api/search"
869 query: { q: Text, page: Int?, limit: Int?, exact: Bool? }
870 execute: Search
871 }
872 "#;
873 let ep = parse_endpoint_source(src).expect("parses");
874 let names: Vec<&str> = ep.query_params.iter().map(|f| f.name.as_str()).collect();
875 assert_eq!(names, vec!["q", "page", "limit", "exact"]);
876 let types: Vec<&str> = ep
877 .query_params
878 .iter()
879 .map(|f| f.type_expr.name.as_str())
880 .collect();
881 assert_eq!(types, vec!["Text", "Int", "Int", "Bool"]);
882 let optionals: Vec<bool> = ep
883 .query_params
884 .iter()
885 .map(|f| f.type_expr.optional)
886 .collect();
887 assert_eq!(optionals, vec![false, true, true, true]);
888 }
889
890 #[test]
891 fn duplicate_query_param_is_parse_error() {
892 let src = r#"
893 axonendpoint bad {
894 method: GET
895 path: "/api/x"
896 query: { name: Text, name: Int? }
897 execute: Bad
898 }
899 "#;
900 let err = parse_endpoint_source(src).expect_err("must fail");
901 assert!(
902 err.contains("duplicate query param 'name'"),
903 "error must name the duplicate. Got: {err}"
904 );
905 }
906
907 #[test]
908 fn off_catalog_type_with_smart_suggest_hint() {
909 let src = r#"
914 axonendpoint bad {
915 method: GET
916 path: "/api/x"
917 query: { value: Strng }
918 execute: Bad
919 }
920 "#;
921 let err = parse_endpoint_source(src).expect_err("must fail");
922 assert!(
923 err.contains("unsupported type 'Strng'"),
924 "error must name the bad type. Got: {err}"
925 );
926 assert!(
927 err.contains("Expected one of: Text | Int | Float | Bool | Uuid"),
928 "error must list the closed catalog. Got: {err}"
929 );
930 }
931
932 #[test]
933 fn close_typo_gets_did_you_mean_hint() {
934 let src = r#"
937 axonendpoint bad {
938 method: GET
939 path: "/api/x"
940 query: { value: Txt }
941 execute: Bad
942 }
943 "#;
944 let err = parse_endpoint_source(src).expect_err("must fail");
945 assert!(
946 err.contains("Did you mean") && err.contains("`Text`"),
947 "smart-suggest must hint `Text`. Got: {err}"
948 );
949 }
950
951 #[test]
952 fn every_catalog_type_parses_cleanly() {
953 for ty in &["Text", "Int", "Float", "Bool", "Uuid"] {
955 let src = format!(
956 r#"
957 axonendpoint x {{
958 method: GET
959 path: "/api/x"
960 query: {{ v: {ty} }}
961 execute: X
962 }}
963 "#
964 );
965 let ep = parse_endpoint_source(&src)
966 .unwrap_or_else(|e| panic!("`{ty}` should parse: {e}"));
967 assert_eq!(ep.query_params[0].type_expr.name, *ty);
968 }
969 }
970
971 #[test]
972 fn comma_optional_between_params() {
973 let src = r#"
977 axonendpoint x {
978 method: GET
979 path: "/api/x"
980 query: { a: Text b: Int? }
981 execute: X
982 }
983 "#;
984 let ep = parse_endpoint_source(src).expect("parses without commas");
985 assert_eq!(ep.query_params.len(), 2);
986 }
987
988 #[test]
991 fn double_query_block_is_parse_error() {
992 let src = r#"
996 axonendpoint x {
997 method: GET
998 path: "/api/x"
999 query: { a: Text }
1000 query: { b: Int? }
1001 execute: X
1002 }
1003 "#;
1004 let err = parse_endpoint_source(src).expect_err("must fail");
1005 assert!(
1006 err.contains("declares `query: { … }` more than once"),
1007 "error must call out the duplicate block. Got: {err}"
1008 );
1009 assert!(
1010 err.contains("combine all params into a single block"),
1011 "error must hint the canonical fix. Got: {err}"
1012 );
1013 }
1014
1015 #[test]
1016 fn optional_generic_type_is_parse_error_with_canonical_hint() {
1017 let src = r#"
1021 axonendpoint x {
1022 method: GET
1023 path: "/api/x"
1024 query: { value: Optional<Text> }
1025 execute: X
1026 }
1027 "#;
1028 let err = parse_endpoint_source(src).expect_err("must fail");
1029 assert!(
1030 err.contains("generic type `Optional<Text>`"),
1031 "error must name the generic type literally. Got: {err}"
1032 );
1033 assert!(
1034 err.contains("Use `Text?` (the `?` suffix)"),
1035 "error must hint the canonical `Text?` syntax. Got: {err}"
1036 );
1037 }
1038
1039 #[test]
1040 fn list_generic_type_is_parse_error_with_deferral_hint() {
1041 let src = r#"
1046 axonendpoint x {
1047 method: GET
1048 path: "/api/x"
1049 query: { tags: List<Text> }
1050 execute: X
1051 }
1052 "#;
1053 let err = parse_endpoint_source(src).expect_err("must fail");
1054 assert!(
1055 err.contains("generic type `List<Text>`"),
1056 "error must name the generic type. Got: {err}"
1057 );
1058 assert!(
1059 err.contains("Multi-value query params")
1060 && err.contains("honest-deferred"),
1061 "error must mention the multi-value deferral. Got: {err}"
1062 );
1063 }
1064
1065 #[test]
1066 fn other_generic_types_caught_generically() {
1067 let src = r#"
1071 axonendpoint x {
1072 method: GET
1073 path: "/api/x"
1074 query: { value: Stream<Int> }
1075 execute: X
1076 }
1077 "#;
1078 let err = parse_endpoint_source(src).expect_err("must fail");
1079 assert!(
1080 err.contains("generic type `Stream<Int>`"),
1081 "error must name the generic type. Got: {err}"
1082 );
1083 assert!(
1084 err.contains("Text | Int | Float | Bool | Uuid"),
1085 "error must list the closed catalog. Got: {err}"
1086 );
1087 }
1088
1089 #[test]
1090 fn uuid_optional_parses_cleanly() {
1091 let src = r#"
1094 axonendpoint find {
1095 method: GET
1096 path: "/api/x"
1097 query: { after: Uuid? }
1098 execute: Find
1099 }
1100 "#;
1101 let ep = parse_endpoint_source(src).expect("parses");
1102 assert_eq!(ep.query_params.len(), 1);
1103 assert_eq!(ep.query_params[0].name, "after");
1104 assert_eq!(ep.query_params[0].type_expr.name, "Uuid");
1105 assert!(ep.query_params[0].type_expr.optional);
1106 assert_eq!(ep.query_params[0].type_expr.generic_param, "");
1107 }
1108
1109 #[test]
1110 fn empty_query_block_yields_empty_vec() {
1111 let src = r#"
1115 axonendpoint x {
1116 method: GET
1117 path: "/api/x"
1118 query: { }
1119 execute: X
1120 }
1121 "#;
1122 let ep = parse_endpoint_source(src).expect("empty block parses");
1123 assert!(ep.query_params.is_empty());
1124 }
1125
1126 #[test]
1127 fn kivi_secret_write_path_plus_query() {
1128 let src = r#"
1132 axonendpoint write_secret {
1133 method: POST
1134 path: "/api/tenants/{tenant_id}/secrets/{secret_name}"
1135 query: { dry_run: Bool?, overwrite: Bool? }
1136 body: SecretWriteRequest
1137 execute: WriteSecret
1138 }
1139 "#;
1140 let ep = parse_endpoint_source(src).expect("parses");
1141 assert_eq!(ep.path_params, vec!["tenant_id", "secret_name"]);
1143 assert_eq!(ep.query_params.len(), 2);
1145 assert_eq!(ep.query_params[0].name, "dry_run");
1146 assert_eq!(ep.query_params[0].type_expr.name, "Bool");
1147 assert!(ep.query_params[0].type_expr.optional);
1148 assert_eq!(ep.query_params[1].name, "overwrite");
1149 assert_eq!(ep.body_type, "SecretWriteRequest");
1151 }
1152}
1153
1154#[cfg(test)]
1155mod path_param_extraction_tests {
1156 use super::extract_path_param_names;
1157
1158 #[test]
1159 fn empty_path_no_placeholders() {
1160 assert_eq!(extract_path_param_names("/api/users"), Ok(vec![]));
1161 assert_eq!(extract_path_param_names("/"), Ok(vec![]));
1162 assert_eq!(extract_path_param_names(""), Ok(vec![]));
1163 }
1164
1165 #[test]
1166 fn single_placeholder() {
1167 assert_eq!(
1168 extract_path_param_names("/api/users/{id}"),
1169 Ok(vec!["id".to_string()])
1170 );
1171 }
1172
1173 #[test]
1174 fn multiple_placeholders_in_declaration_order() {
1175 assert_eq!(
1176 extract_path_param_names(
1177 "/api/tenants/{tenant_id}/secrets/{secret_name}"
1178 ),
1179 Ok(vec![
1180 "tenant_id".to_string(),
1181 "secret_name".to_string(),
1182 ])
1183 );
1184 }
1185
1186 #[test]
1187 fn kivi_chat_history_path_pattern() {
1188 let names = extract_path_param_names(
1192 "/api/tenants/{tenant_id}/secrets/{secret_name}",
1193 );
1194 assert_eq!(
1195 names,
1196 Ok(vec![
1197 "tenant_id".to_string(),
1198 "secret_name".to_string(),
1199 ])
1200 );
1201 }
1202
1203 #[test]
1204 fn duplicate_placeholder_returns_err() {
1205 assert_eq!(
1206 extract_path_param_names("/api/users/{id}/posts/{id}"),
1207 Err("id".to_string())
1208 );
1209 }
1210
1211 #[test]
1212 fn underscore_and_numeric_in_name() {
1213 assert_eq!(
1214 extract_path_param_names("/api/{user_id}/items/{item_2}"),
1215 Ok(vec!["user_id".to_string(), "item_2".to_string()])
1216 );
1217 }
1218
1219 #[test]
1220 fn leading_underscore_accepted() {
1221 assert_eq!(
1227 extract_path_param_names("/api/{_internal}"),
1228 Ok(vec!["_internal".to_string()])
1229 );
1230 }
1231
1232 #[test]
1233 fn malformed_placeholder_silently_ignored() {
1234 assert_eq!(
1238 extract_path_param_names("/api/{not valid}"),
1239 Ok(vec![])
1240 );
1241 assert_eq!(extract_path_param_names("/api/{}"), Ok(vec![]));
1243 assert_eq!(
1245 extract_path_param_names("/api/{tenant id}/users/{id}"),
1246 Ok(vec!["id".to_string()])
1247 );
1248 }
1249
1250 #[test]
1251 fn unterminated_brace_returns_clean() {
1252 assert_eq!(extract_path_param_names("/api/{id"), Ok(vec![]));
1255 }
1256
1257 #[test]
1258 fn placeholders_at_path_boundaries() {
1259 assert_eq!(
1262 extract_path_param_names("{prefix}/api/users/{id}"),
1263 Ok(vec!["prefix".to_string(), "id".to_string()])
1264 );
1265 assert_eq!(
1266 extract_path_param_names("/api/{id}"),
1267 Ok(vec!["id".to_string()])
1268 );
1269 }
1270
1271 #[test]
1272 fn deduplication_detects_non_adjacent_duplicates() {
1273 assert_eq!(
1275 extract_path_param_names(
1276 "/api/orgs/{org_id}/teams/{team_id}/repos/{org_id}"
1277 ),
1278 Err("org_id".to_string())
1279 );
1280 }
1281
1282 #[test]
1283 fn never_panics_on_arbitrary_input() {
1284 for input in &[
1286 "{",
1287 "}",
1288 "{}",
1289 "{{}}",
1290 "{{{",
1291 "/api/{}/{id}",
1292 "////",
1293 "\u{1F4A1}", "\u{0000}", ] {
1296 let _ = extract_path_param_names(input); }
1298 }
1299}
1300
1301#[cfg(test)]
1302mod capability_slug_tests {
1303 use super::is_valid_capability_slug;
1304
1305 #[test]
1306 fn accepts_canonical_examples() {
1307 assert!(is_valid_capability_slug("admin"));
1308 assert!(is_valid_capability_slug("legal.read"));
1309 assert!(is_valid_capability_slug("hipaa.phi.read"));
1310 assert!(is_valid_capability_slug("bank.officer.senior"));
1311 assert!(is_valid_capability_slug("a"));
1312 assert!(is_valid_capability_slug("a_b"));
1313 assert!(is_valid_capability_slug("a1"));
1314 assert!(is_valid_capability_slug("a.b1_c"));
1315 }
1316
1317 #[test]
1318 fn rejects_empty_string() {
1319 assert!(!is_valid_capability_slug(""));
1320 }
1321
1322 #[test]
1323 fn rejects_uppercase() {
1324 assert!(!is_valid_capability_slug("Admin"));
1325 assert!(!is_valid_capability_slug("admin.READ"));
1326 }
1327
1328 #[test]
1329 fn rejects_digit_first() {
1330 assert!(!is_valid_capability_slug("1admin"));
1331 assert!(!is_valid_capability_slug("admin.1read"));
1332 }
1333
1334 #[test]
1335 fn rejects_hyphen() {
1336 assert!(!is_valid_capability_slug("bank-officer"));
1337 }
1338
1339 #[test]
1340 fn rejects_empty_segments() {
1341 assert!(!is_valid_capability_slug("bank..a"));
1342 assert!(!is_valid_capability_slug(".admin"));
1343 assert!(!is_valid_capability_slug("admin."));
1344 }
1345
1346 #[test]
1347 fn rejects_special_chars() {
1348 assert!(!is_valid_capability_slug("admin@read"));
1349 assert!(!is_valid_capability_slug("admin/read"));
1350 assert!(!is_valid_capability_slug("admin read"));
1351 }
1352}
1353
1354pub struct Parser {
1357 tokens: Vec<Token>,
1358 pos: usize,
1359 leading_trivia: Vec<Vec<Trivia>>,
1364 trailing_trivia: Vec<Vec<Trivia>>,
1368 last_let_value_kind: String,
1372 loop_depth: u32,
1377 source: Option<String>,
1383 filename: String,
1384}
1385
1386impl Parser {
1387 pub fn new(raw_tokens: Vec<Token>) -> Self {
1388 let mut effective: Vec<Token> = Vec::with_capacity(raw_tokens.len());
1398 let mut leading: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1399 let mut trailing: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1400
1401 let mut pending_leading: Vec<Trivia> = Vec::new();
1402 let mut last_effective_line: i64 = -1;
1403 for tok in raw_tokens {
1404 if is_comment_token(&tok.ttype) {
1405 let kind = token_to_trivia_kind(&tok.ttype)
1406 .expect("comment token must map to a trivia kind");
1407 let triv = Trivia {
1408 kind,
1409 text: tok.value,
1410 line: tok.line,
1411 column: tok.column,
1412 };
1413 if !effective.is_empty() && (tok.line as i64) == last_effective_line {
1414 trailing.last_mut().unwrap().push(triv);
1415 } else {
1416 pending_leading.push(triv);
1417 }
1418 } else {
1419 last_effective_line = tok.line as i64;
1420 effective.push(tok);
1421 leading.push(std::mem::take(&mut pending_leading));
1422 trailing.push(Vec::new());
1423 }
1424 }
1425
1426 Parser {
1427 tokens: effective,
1428 pos: 0,
1429 leading_trivia: leading,
1430 trailing_trivia: trailing,
1431 last_let_value_kind: "literal".to_string(),
1432 loop_depth: 0,
1433 source: None,
1434 filename: "<source>".to_string(),
1435 }
1436 }
1437
1438 #[must_use]
1450 pub fn with_source(mut self, source: &str, filename: &str) -> Self {
1451 self.source = Some(source.to_string());
1452 self.filename = filename.to_string();
1453 self
1454 }
1455
1456 pub fn parse(&mut self) -> Result<Program, ParseError> {
1459 let mut program = Program {
1460 declarations: Vec::new(),
1461 declaration_trivia: Vec::new(),
1462 loc: Loc { line: 1, column: 1 },
1463 };
1464 while !self.check(TokenType::Eof) {
1465 let start_pos = self.pos;
1471 let mut decl = match self.parse_declaration() {
1472 Ok(d) => d,
1473 Err(e) => return Err(self.attach_source_to_error(e)),
1474 };
1475 let end_pos = self.pos.saturating_sub(1);
1476 let leading = self
1477 .leading_trivia
1478 .get(start_pos)
1479 .cloned()
1480 .unwrap_or_default();
1481 let trailing = self
1482 .trailing_trivia
1483 .get(end_pos)
1484 .cloned()
1485 .unwrap_or_default();
1486 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1492 program.declarations.push(decl);
1493 program
1494 .declaration_trivia
1495 .push(DeclarationTrivia { leading, trailing });
1496 }
1497 Ok(program)
1498 }
1499
1500 pub fn parse_with_recovery(&mut self) -> ParseResult {
1533 let mut program = Program {
1534 declarations: Vec::new(),
1535 declaration_trivia: Vec::new(),
1536 loc: Loc { line: 1, column: 1 },
1537 };
1538 let mut errors: Vec<ParseError> = Vec::new();
1539
1540 while !self.check(TokenType::Eof) {
1541 let start_pos = self.pos;
1542 match self.parse_declaration() {
1543 Ok(mut decl) => {
1544 let end_pos = self.pos.saturating_sub(1);
1545 let leading = self
1546 .leading_trivia
1547 .get(start_pos)
1548 .cloned()
1549 .unwrap_or_default();
1550 let trailing = self
1551 .trailing_trivia
1552 .get(end_pos)
1553 .cloned()
1554 .unwrap_or_default();
1555 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1556 program.declarations.push(decl);
1557 program
1558 .declaration_trivia
1559 .push(DeclarationTrivia { leading, trailing });
1560 }
1561 Err(err) => {
1562 errors.push(self.attach_source_to_error(err));
1566 if self.pos == start_pos && !self.check(TokenType::Eof) {
1571 self.advance();
1572 }
1573 self.advance_to_sync_point();
1574 }
1575 }
1576 }
1577
1578 ParseResult { program, errors }
1579 }
1580
1581 fn attach_source_to_error(&self, err: ParseError) -> ParseError {
1586 match &self.source {
1587 Some(src) if err.line >= 1 => err.attach_source(src, &self.filename),
1588 _ => err,
1589 }
1590 }
1591
1592 fn advance_to_sync_point(&mut self) {
1597 let mut depth: i32 = 0;
1598 while !self.check(TokenType::Eof) {
1599 let tt = self.current().ttype.clone();
1600 if is_top_level_decl_kw_for_recovery(&tt) && depth <= 0 {
1604 return;
1605 }
1606 if matches!(tt, TokenType::LBrace) {
1607 depth += 1;
1608 } else if matches!(tt, TokenType::RBrace) {
1609 depth -= 1;
1610 }
1611 self.advance();
1612 }
1613 }
1614
1615 fn current(&self) -> &Token {
1618 if self.pos >= self.tokens.len() {
1619 self.tokens.last().unwrap() } else {
1621 &self.tokens[self.pos]
1622 }
1623 }
1624
1625 fn advance(&mut self) -> &Token {
1626 let idx = self.pos;
1627 if self.pos < self.tokens.len() {
1628 self.pos += 1;
1629 }
1630 &self.tokens[idx]
1631 }
1632
1633 fn check(&self, tt: TokenType) -> bool {
1634 self.current().ttype == tt
1635 }
1636
1637 fn consume(&mut self, expected: TokenType) -> Result<Token, ParseError> {
1638 let tok = self.current().clone();
1639 if tok.ttype != expected {
1640 return Err(ParseError {
1641 message: format!(
1642 "Expected {:?}, found {:?}('{}')",
1643 expected, tok.ttype, tok.value
1644 ),
1645 line: tok.line,
1646 column: tok.column,
1647 ..Default::default()
1648 });
1649 }
1650 self.pos += 1;
1651 Ok(tok)
1652 }
1653
1654 fn error(&self, message: &str) -> ParseError {
1656 let tok = self.current();
1657 ParseError { message: message.to_string(), line: tok.line, column: tok.column, ..Default::default() }
1658 }
1659
1660 fn consume_any_ident_or_kw(&mut self) -> Result<Token, ParseError> {
1662 let tok = self.current().clone();
1663 match tok.ttype {
1664 TokenType::Identifier
1665 | TokenType::Bool
1666 | TokenType::StringLit
1667 | TokenType::Integer
1668 | TokenType::Float => {
1669 self.pos += 1;
1670 Ok(tok)
1671 }
1672 _ => {
1673 if !tok.value.is_empty()
1675 && tok.value.chars().all(|c| c.is_alphanumeric() || c == '_')
1676 && tok.ttype != TokenType::Eof
1677 {
1678 self.pos += 1;
1679 Ok(tok)
1680 } else {
1681 Err(ParseError {
1682 message: format!(
1683 "Expected identifier or keyword value, found {:?}('{}')",
1684 tok.ttype, tok.value
1685 ),
1686 line: tok.line,
1687 column: tok.column,
1688 ..Default::default()
1689 })
1690 }
1691 }
1692 }
1693 }
1694
1695 fn consume_number(&mut self) -> Result<f64, ParseError> {
1696 let tok = self.current().clone();
1697 match tok.ttype {
1698 TokenType::Float | TokenType::Integer => {
1699 self.pos += 1;
1700 tok.value.parse::<f64>().map_err(|_| ParseError {
1701 message: format!("Invalid number '{}'", tok.value),
1702 line: tok.line,
1703 column: tok.column,
1704 ..Default::default()
1705 })
1706 }
1707 _ => Err(ParseError {
1708 message: format!("Expected number, found {:?}('{}')", tok.ttype, tok.value),
1709 line: tok.line,
1710 column: tok.column,
1711 ..Default::default()
1712 }),
1713 }
1714 }
1715
1716 fn parse_bool(&mut self) -> Result<bool, ParseError> {
1717 let tok = self.consume(TokenType::Bool)?;
1718 Ok(tok.value == "true")
1719 }
1720
1721 fn loc_of(&self, tok: &Token) -> Loc {
1722 Loc {
1723 line: tok.line,
1724 column: tok.column,
1725 }
1726 }
1727
1728 fn check_comparison(&self) -> bool {
1729 matches!(
1730 self.current().ttype,
1731 TokenType::Lt
1732 | TokenType::Gt
1733 | TokenType::Lte
1734 | TokenType::Gte
1735 | TokenType::Eq
1736 | TokenType::Neq
1737 )
1738 }
1739
1740 fn check_run_modifier(&self) -> bool {
1741 matches!(
1742 self.current().ttype,
1743 TokenType::As
1744 | TokenType::Within
1745 | TokenType::ConstrainedBy
1746 | TokenType::OnFailure
1747 | TokenType::OutputTo
1748 | TokenType::Effort
1749 )
1750 }
1751
1752 fn parse_string_list(&mut self) -> Result<Vec<String>, ParseError> {
1755 self.consume(TokenType::LBracket)?;
1756 let mut items = Vec::new();
1757 items.push(self.consume(TokenType::StringLit)?.value);
1758 while self.check(TokenType::Comma) {
1759 self.advance();
1760 items.push(self.consume(TokenType::StringLit)?.value);
1761 }
1762 self.consume(TokenType::RBracket)?;
1763 Ok(items)
1764 }
1765
1766 fn parse_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1767 let mut names = Vec::new();
1768 names.push(self.consume(TokenType::Identifier)?.value);
1769 while self.check(TokenType::Comma) {
1770 self.advance();
1771 names.push(self.consume(TokenType::Identifier)?.value);
1772 }
1773 Ok(names)
1774 }
1775
1776 fn parse_bracketed_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1777 self.consume(TokenType::LBracket)?;
1778 let items = self.parse_extended_identifier_list()?;
1779 self.consume(TokenType::RBracket)?;
1780 Ok(items)
1781 }
1782
1783 fn parse_extended_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1784 let mut items = Vec::new();
1785 items.push(self.consume_any_ident_or_kw()?.value);
1786 while self.check(TokenType::Comma) {
1787 self.advance();
1788 items.push(self.consume_any_ident_or_kw()?.value);
1789 }
1790 Ok(items)
1791 }
1792
1793 fn parse_dotted_identifier(&mut self) -> Result<String, ParseError> {
1794 let mut parts = vec![self.consume_any_ident_or_kw()?.value];
1795 while self.check(TokenType::Dot) {
1796 self.advance();
1797 parts.push(self.consume_any_ident_or_kw()?.value);
1798 }
1799 Ok(parts.join("."))
1800 }
1801
1802 fn parse_expression_string(&mut self) -> Result<String, ParseError> {
1803 if self.check(TokenType::LBracket) {
1804 let items = self.parse_bracketed_dot_identifiers()?;
1805 return Ok(format!("[{}]", items.join(", ")));
1806 }
1807 self.parse_dotted_identifier()
1808 }
1809
1810 fn parse_bracketed_dot_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1811 self.consume(TokenType::LBracket)?;
1812 let mut items = vec![self.parse_dotted_identifier()?];
1813 while self.check(TokenType::Comma) {
1814 self.advance();
1815 items.push(self.parse_dotted_identifier()?);
1816 }
1817 self.consume(TokenType::RBracket)?;
1818 Ok(items)
1819 }
1820
1821 fn parse_argument_list(&mut self) -> Result<Vec<String>, ParseError> {
1822 let mut args = Vec::new();
1823 while !self.check(TokenType::RParen) {
1824 let tok = self.current().clone();
1825 match tok.ttype {
1826 TokenType::StringLit | TokenType::Integer | TokenType::Float => {
1827 self.advance();
1828 args.push(tok.value);
1829 }
1830 TokenType::Identifier => {
1831 self.advance();
1832 let mut val = tok.value;
1833 if self.check(TokenType::Dot) {
1834 self.advance();
1835 val.push('.');
1836 val.push_str(&self.consume_any_ident_or_kw()?.value);
1837 }
1838 args.push(val);
1839 }
1840 _ => {
1841 self.advance();
1842 let key = tok.value;
1843 if self.check(TokenType::Colon) {
1844 self.advance();
1845 let v = self.advance().value.clone();
1846 args.push(format!("{key}:{v}"));
1847 } else {
1848 args.push(key);
1849 }
1850 }
1851 }
1852 if self.check(TokenType::Comma) {
1853 self.advance();
1854 }
1855 }
1856 Ok(args)
1857 }
1858
1859 fn skip_value(&mut self) {
1861 match self.current().ttype {
1862 TokenType::LBracket => {
1863 self.advance();
1864 let mut depth = 1u32;
1865 while depth > 0 && !self.check(TokenType::Eof) {
1866 if self.check(TokenType::LBracket) {
1867 depth += 1;
1868 } else if self.check(TokenType::RBracket) {
1869 depth -= 1;
1870 }
1871 self.advance();
1872 }
1873 }
1874 TokenType::LBrace => {
1875 self.advance();
1876 let mut depth = 1u32;
1877 while depth > 0 && !self.check(TokenType::Eof) {
1878 if self.check(TokenType::LBrace) {
1879 depth += 1;
1880 } else if self.check(TokenType::RBrace) {
1881 depth -= 1;
1882 }
1883 self.advance();
1884 }
1885 }
1886 TokenType::Lt => {
1887 self.advance();
1889 let mut depth = 1u32;
1890 while depth > 0 && !self.check(TokenType::Eof) {
1891 if self.check(TokenType::Lt) {
1892 depth += 1;
1893 } else if self.check(TokenType::Gt) {
1894 depth -= 1;
1895 }
1896 self.advance();
1897 }
1898 }
1899 _ => {
1900 self.advance();
1901 while self.check(TokenType::Dot) {
1902 self.advance();
1903 self.advance();
1904 }
1905 }
1906 }
1907 }
1908
1909 fn skip_braced_block(&mut self) -> Result<(), ParseError> {
1911 self.consume(TokenType::LBrace)?;
1912 let mut depth = 1u32;
1913 while depth > 0 {
1914 if self.check(TokenType::Eof) {
1915 let tok = self.current();
1916 return Err(ParseError {
1917 message: "Unterminated block — expected '}'".to_string(),
1918 line: tok.line,
1919 column: tok.column,
1920 ..Default::default()
1921 });
1922 }
1923 if self.check(TokenType::LBrace) {
1924 depth += 1;
1925 } else if self.check(TokenType::RBrace) {
1926 depth -= 1;
1927 }
1928 self.advance();
1929 }
1930 Ok(())
1931 }
1932
1933 fn at_declaration_start(&self) -> bool {
1934 is_declaration_keyword(&self.current().ttype) || self.check(TokenType::Eof)
1935 }
1936
1937 fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
1940 let tok = self.current().clone();
1941
1942 match tok.ttype {
1943 TokenType::Import => self.parse_import().map(Declaration::Import),
1944 TokenType::Persona => self.parse_persona().map(Declaration::Persona),
1945 TokenType::Context => self.parse_context().map(Declaration::Context),
1946 TokenType::Anchor => self.parse_anchor().map(Declaration::Anchor),
1947 TokenType::Memory => self.parse_memory().map(Declaration::Memory),
1948 TokenType::Tool => self.parse_tool().map(Declaration::Tool),
1949 TokenType::Type => self.parse_type_def().map(Declaration::Type),
1950 TokenType::Flow => self.parse_flow().map(Declaration::Flow),
1951 TokenType::Intent => self.parse_intent().map(Declaration::Intent),
1952 TokenType::Run => self.parse_run().map(Declaration::Run),
1953 TokenType::Let => self.parse_let().map(Declaration::Let),
1954 TokenType::Know | TokenType::Believe | TokenType::Speculate | TokenType::Doubt => {
1955 self.parse_epistemic_block().map(Declaration::Epistemic)
1956 }
1957 TokenType::Lambda => self.parse_lambda_data().map(Declaration::LambdaData),
1958
1959 TokenType::Agent => self.parse_agent().map(Declaration::Agent),
1961 TokenType::Shield => self.parse_shield().map(Declaration::Shield),
1962 TokenType::Pix => self.parse_pix().map(Declaration::Pix),
1963 TokenType::Psyche => self.parse_psyche().map(Declaration::Psyche),
1964 TokenType::Corpus => self.parse_corpus().map(Declaration::Corpus),
1965 TokenType::Dataspace => self.parse_dataspace().map(Declaration::Dataspace),
1966 TokenType::Ots => self.parse_ots().map(Declaration::Ots),
1967 TokenType::Mandate => self.parse_mandate().map(Declaration::Mandate),
1968 TokenType::Compute => self.parse_compute().map(Declaration::Compute),
1969 TokenType::Daemon => self.parse_daemon().map(Declaration::Daemon),
1970 TokenType::Extension => self.parse_extension().map(Declaration::Extension),
1971 TokenType::AxonStore => self.parse_axonstore().map(Declaration::AxonStore),
1972 TokenType::AxonEndpoint => self.parse_axonendpoint().map(Declaration::AxonEndpoint),
1973
1974 TokenType::Resource => self.parse_resource().map(Declaration::Resource),
1976 TokenType::Fabric => self.parse_fabric().map(Declaration::Fabric),
1977 TokenType::Manifest => self.parse_manifest().map(Declaration::Manifest),
1978 TokenType::Observe => self.parse_observe().map(Declaration::Observe),
1979
1980 TokenType::Reconcile => self.parse_reconcile().map(Declaration::Reconcile),
1982 TokenType::Lease => self.parse_lease().map(Declaration::Lease),
1983 TokenType::Ensemble => self.parse_ensemble().map(Declaration::Ensemble),
1984
1985 TokenType::Session => self.parse_session_definition().map(Declaration::Session),
1987 TokenType::Topology => self.parse_topology().map(Declaration::Topology),
1988
1989 TokenType::Socket => self.parse_socket().map(Declaration::Socket),
1991
1992 TokenType::Immune => self.parse_immune().map(Declaration::Immune),
1994 TokenType::Reflex => self.parse_reflex().map(Declaration::Reflex),
1995 TokenType::Heal => self.parse_heal().map(Declaration::Heal),
1996
1997 TokenType::Component => self.parse_component().map(Declaration::Component),
1999 TokenType::View => self.parse_view().map(Declaration::View),
2000
2001 TokenType::Channel => self.parse_channel().map(Declaration::Channel),
2003
2004 TokenType::Ingest
2007 | TokenType::Persist
2008 | TokenType::Retrieve
2009 | TokenType::Mutate
2010 | TokenType::Purge
2011 | TokenType::Transact => self.parse_generic_declaration(),
2012
2013 TokenType::Mcp => self.parse_generic_declaration(),
2015
2016 _ => {
2017 let hint = crate::smart_suggest::suggest_for(
2021 &tok.value,
2022 crate::smart_suggest::TOP_LEVEL_KEYWORD_NAMES,
2023 );
2024 let base = format!(
2025 "Unexpected token at top level: '{}' — expected declaration \
2026 (persona, context, anchor, flow, run, ...)",
2027 tok.value
2028 );
2029 let message = if hint.is_empty() {
2030 base
2031 } else {
2032 format!("{base}. {hint}")
2033 };
2034 Err(ParseError {
2035 message,
2036 line: tok.line,
2037 column: tok.column,
2038 ..Default::default()
2039 })
2040 }
2041 }
2042 }
2043
2044 fn parse_import(&mut self) -> Result<ImportNode, ParseError> {
2047 let tok = self.consume(TokenType::Import)?;
2048 let loc = self.loc_of(&tok);
2049
2050 let mut path_parts = Vec::new();
2051
2052 if self.check(TokenType::At) {
2054 self.advance();
2055 let first = self.consume(TokenType::Identifier)?;
2056 path_parts.push(format!("@{}", first.value));
2057 } else {
2058 let first = self.consume(TokenType::Identifier)?;
2059 path_parts.push(first.value);
2060 }
2061
2062 while self.check(TokenType::Dot) {
2063 self.advance();
2064 if self.check(TokenType::LBrace) {
2065 break;
2066 }
2067 let part = self.consume(TokenType::Identifier)?;
2068 path_parts.push(part.value);
2069 }
2070
2071 let mut names = Vec::new();
2072 if self.check(TokenType::LBrace) {
2073 self.advance();
2074 names = self.parse_identifier_list()?;
2075 self.consume(TokenType::RBrace)?;
2076 }
2077
2078 if self.current().value == "with" {
2080 self.advance();
2081 self.advance(); if self.check(TokenType::LBrace) {
2083 self.skip_braced_block()?;
2084 }
2085 }
2086
2087 Ok(ImportNode {
2088 module_path: path_parts,
2089 names,
2090 loc,
2091 leading_trivia: Vec::new(),
2092 trailing_trivia: Vec::new(),
2093 })
2094 }
2095
2096 fn parse_persona(&mut self) -> Result<PersonaDefinition, ParseError> {
2099 let tok = self.consume(TokenType::Persona)?;
2100 let loc = self.loc_of(&tok);
2101 let name = self.consume(TokenType::Identifier)?.value;
2102 self.consume(TokenType::LBrace)?;
2103
2104 let mut node = PersonaDefinition {
2105 name,
2106 domain: Vec::new(),
2107 tone: String::new(),
2108 confidence_threshold: None,
2109 cite_sources: None,
2110 refuse_if: Vec::new(),
2111 language: String::new(),
2112 description: String::new(),
2113 loc,
2114 leading_trivia: Vec::new(),
2115 trailing_trivia: Vec::new(),
2116 };
2117
2118 while !self.check(TokenType::RBrace) {
2119 let field_name = self.current().value.clone();
2120 self.advance();
2121 self.consume(TokenType::Colon)?;
2122
2123 match field_name.as_str() {
2124 "domain" => node.domain = self.parse_string_list()?,
2125 "tone" => node.tone = self.consume_any_ident_or_kw()?.value,
2126 "confidence_threshold" => node.confidence_threshold = Some(self.consume_number()?),
2127 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2128 "refuse_if" => node.refuse_if = self.parse_bracketed_identifiers()?,
2129 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2130 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2131 _ => self.skip_value(),
2132 }
2133 }
2134 self.consume(TokenType::RBrace)?;
2135 Ok(node)
2136 }
2137
2138 fn parse_context(&mut self) -> Result<ContextDefinition, ParseError> {
2141 let tok = self.consume(TokenType::Context)?;
2142 let loc = self.loc_of(&tok);
2143 let name = self.consume(TokenType::Identifier)?.value;
2144 self.consume(TokenType::LBrace)?;
2145
2146 let mut node = ContextDefinition {
2147 name,
2148 memory_scope: String::new(),
2149 language: String::new(),
2150 depth: String::new(),
2151 max_tokens: None,
2152 temperature: None,
2153 cite_sources: None,
2154 loc,
2155 leading_trivia: Vec::new(),
2156 trailing_trivia: Vec::new(),
2157 };
2158
2159 while !self.check(TokenType::RBrace) {
2160 let field_name = self.current().value.clone();
2161 self.advance();
2162 self.consume(TokenType::Colon)?;
2163
2164 match field_name.as_str() {
2165 "memory" => node.memory_scope = self.consume_any_ident_or_kw()?.value,
2166 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2167 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
2168 "max_tokens" => {
2169 node.max_tokens = Some(
2170 self.consume(TokenType::Integer)?
2171 .value
2172 .parse::<i64>()
2173 .unwrap_or(0),
2174 )
2175 }
2176 "temperature" => node.temperature = Some(self.consume_number()?),
2177 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2178 _ => self.skip_value(),
2179 }
2180 }
2181 self.consume(TokenType::RBrace)?;
2182 Ok(node)
2183 }
2184
2185 fn parse_anchor(&mut self) -> Result<AnchorConstraint, ParseError> {
2188 let tok = self.consume(TokenType::Anchor)?;
2189 let loc = self.loc_of(&tok);
2190 let name = self.consume(TokenType::Identifier)?.value;
2191 self.consume(TokenType::LBrace)?;
2192
2193 let mut node = AnchorConstraint {
2194 name,
2195 require: String::new(),
2196 reject: Vec::new(),
2197 enforce: String::new(),
2198 description: String::new(),
2199 confidence_floor: None,
2200 unknown_response: String::new(),
2201 on_violation: String::new(),
2202 on_violation_target: String::new(),
2203 loc,
2204 leading_trivia: Vec::new(),
2205 trailing_trivia: Vec::new(),
2206 };
2207
2208 while !self.check(TokenType::RBrace) {
2209 let field_name = self.current().value.clone();
2210 self.advance();
2211 self.consume(TokenType::Colon)?;
2212
2213 match field_name.as_str() {
2214 "require" => node.require = self.consume_any_ident_or_kw()?.value,
2215 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2216 "reject" => node.reject = self.parse_bracketed_identifiers()?,
2217 "enforce" => node.enforce = self.consume_any_ident_or_kw()?.value,
2218 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
2219 "unknown_response" => {
2220 node.unknown_response = self.consume(TokenType::StringLit)?.value
2221 }
2222 "on_violation" => {
2223 let action = self.consume_any_ident_or_kw()?.value;
2225 node.on_violation = action.clone();
2226 if action == "raise" || action == "fallback" {
2227 node.on_violation_target = self.consume_any_ident_or_kw()?.value;
2228 }
2229 }
2230 _ => self.skip_value(),
2231 }
2232 }
2233 self.consume(TokenType::RBrace)?;
2234 Ok(node)
2235 }
2236
2237 fn parse_memory(&mut self) -> Result<MemoryDefinition, ParseError> {
2240 let tok = self.consume(TokenType::Memory)?;
2241 let loc = self.loc_of(&tok);
2242 let name = self.consume(TokenType::Identifier)?.value;
2243 self.consume(TokenType::LBrace)?;
2244
2245 let mut node = MemoryDefinition {
2246 name,
2247 store: String::new(),
2248 backend: String::new(),
2249 retrieval: String::new(),
2250 decay: String::new(),
2251 loc,
2252 leading_trivia: Vec::new(),
2253 trailing_trivia: Vec::new(),
2254 };
2255
2256 while !self.check(TokenType::RBrace) {
2257 let field_name = self.current().value.clone();
2258 self.advance();
2259 self.consume(TokenType::Colon)?;
2260
2261 match field_name.as_str() {
2262 "store" => node.store = self.consume_any_ident_or_kw()?.value,
2263 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
2264 "retrieval" => node.retrieval = self.consume_any_ident_or_kw()?.value,
2265 "decay" => {
2266 if self.check(TokenType::Duration) {
2267 node.decay = self.advance().value.clone();
2268 } else {
2269 node.decay = self.consume_any_ident_or_kw()?.value;
2270 }
2271 }
2272 _ => self.skip_value(),
2273 }
2274 }
2275 self.consume(TokenType::RBrace)?;
2276 Ok(node)
2277 }
2278
2279 fn parse_tool(&mut self) -> Result<ToolDefinition, ParseError> {
2282 let tok = self.consume(TokenType::Tool)?;
2283 let loc = self.loc_of(&tok);
2284 let name = self.consume(TokenType::Identifier)?.value;
2285 self.consume(TokenType::LBrace)?;
2286
2287 let mut node = ToolDefinition {
2288 name,
2289 provider: String::new(),
2290 max_results: None,
2291 filter_expr: String::new(),
2292 timeout: String::new(),
2293 runtime: String::new(),
2294 sandbox: None,
2295 effects: None,
2296 parameters: Vec::new(),
2297 output_type: None,
2298 loc,
2299 leading_trivia: Vec::new(),
2300 trailing_trivia: Vec::new(),
2301 };
2302
2303 while !self.check(TokenType::RBrace) {
2304 let field_name = self.current().value.clone();
2305 self.advance();
2306 self.consume(TokenType::Colon)?;
2307
2308 match field_name.as_str() {
2309 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
2310 "max_results" => {
2311 node.max_results = Some(
2312 self.consume(TokenType::Integer)?
2313 .value
2314 .parse::<i64>()
2315 .unwrap_or(0),
2316 )
2317 }
2318 "filter" => node.filter_expr = self.parse_filter_expression()?,
2319 "timeout" => node.timeout = self.consume(TokenType::Duration)?.value,
2320 "runtime" => node.runtime = self.consume_any_ident_or_kw()?.value,
2321 "sandbox" => node.sandbox = Some(self.parse_bool()?),
2322 "effects" => node.effects = Some(self.parse_effect_row()?),
2323 "parameters" => node.parameters = self.parse_tool_param_schema()?,
2325 "output_type" => node.output_type = Some(self.parse_output_type_string()?),
2326 _ => self.skip_value(),
2327 }
2328 }
2329 self.consume(TokenType::RBrace)?;
2330 Ok(node)
2331 }
2332
2333 fn parse_tool_param_schema(&mut self) -> Result<Vec<Parameter>, ParseError> {
2339 self.consume(TokenType::LBrace)?;
2340 let mut params = Vec::new();
2341 while !self.check(TokenType::RBrace) {
2342 let name = self.consume_any_ident_or_kw()?;
2346 let ploc = self.loc_of(&name);
2347 self.consume(TokenType::Colon)?;
2348 let type_expr = self.parse_type_expr()?;
2349 params.push(Parameter {
2350 name: name.value,
2351 type_expr,
2352 loc: ploc,
2353 });
2354 if self.check(TokenType::Comma) {
2355 self.advance();
2356 } else {
2357 break;
2358 }
2359 }
2360 self.consume(TokenType::RBrace)?;
2361 Ok(params)
2362 }
2363
2364 fn parse_filter_expression(&mut self) -> Result<String, ParseError> {
2365 let name = self.consume_any_ident_or_kw()?.value;
2366 if self.check(TokenType::LParen) {
2367 self.advance();
2368 let mut parts = vec![name, "(".to_string()];
2369 while !self.check(TokenType::RParen) {
2370 parts.push(self.advance().value.clone());
2371 }
2372 self.consume(TokenType::RParen)?;
2373 parts.push(")".to_string());
2374 Ok(parts.join(""))
2375 } else {
2376 Ok(name)
2377 }
2378 }
2379
2380 fn parse_effect_row(&mut self) -> Result<EffectRow, ParseError> {
2381 let tok = self.consume(TokenType::Lt)?;
2382 let loc = self.loc_of(&tok);
2383 let mut effects = Vec::new();
2384 let mut epistemic_level = String::new();
2385
2386 while !self.check(TokenType::Gt) {
2387 let name = self.consume_any_ident_or_kw()?.value;
2388 if self.check(TokenType::Colon) {
2389 self.advance();
2390 let level = self.parse_qualifier_value()?;
2407 if name == "epistemic" {
2408 epistemic_level = level;
2409 } else {
2410 effects.push(format!("{name}:{level}"));
2411 }
2412 } else {
2413 effects.push(name);
2414 }
2415 if self.check(TokenType::Comma) {
2416 self.advance();
2417 }
2418 }
2419 self.consume(TokenType::Gt)?;
2420
2421 Ok(EffectRow {
2422 effects,
2423 epistemic_level,
2424 loc,
2425 })
2426 }
2427
2428 fn parse_qualifier_value(&mut self) -> Result<String, ParseError> {
2437 let mut buf = self.consume_dotted_slug_segment()?;
2438 loop {
2439 let sep = if self.check(TokenType::Dot) {
2440 '.'
2441 } else if self.check(TokenType::Colon) {
2442 ':'
2443 } else {
2444 break;
2445 };
2446 self.advance();
2447 let part = self.consume_dotted_slug_segment()?;
2448 buf.push(sep);
2449 buf.push_str(&part);
2450 }
2451 Ok(buf)
2452 }
2453
2454 fn consume_dotted_slug_segment(&mut self) -> Result<String, ParseError> {
2466 let first = self.consume_any_ident_or_kw()?;
2467 let mut buf = first.value.clone();
2468 let mut next_line = first.line;
2469 let mut next_col = first.column + first.value.chars().count() as u32;
2470 loop {
2471 let cur = self.current();
2472 let is_segment_token = matches!(cur.ttype, TokenType::Identifier | TokenType::Integer,);
2473 if !is_segment_token {
2474 break;
2475 }
2476 if cur.line != next_line || cur.column != next_col {
2477 break;
2478 }
2479 buf.push_str(&cur.value);
2480 next_col = cur.column + cur.value.chars().count() as u32;
2481 next_line = cur.line;
2482 self.pos += 1;
2483 }
2484 Ok(buf)
2485 }
2486
2487 fn parse_type_def(&mut self) -> Result<TypeDefinition, ParseError> {
2490 let tok = self.consume(TokenType::Type)?;
2491 let loc = self.loc_of(&tok);
2492 let name = self.consume(TokenType::Identifier)?.value;
2493
2494 let mut node = TypeDefinition {
2495 name,
2496 fields: Vec::new(),
2497 range_constraint: None,
2498 where_clause: None,
2499 compliance: Vec::new(),
2500 loc: loc.clone(),
2501 leading_trivia: Vec::new(),
2502 trailing_trivia: Vec::new(),
2503 };
2504
2505 if self.check(TokenType::LParen) {
2507 self.advance();
2508 let min_val = self.consume_number()?;
2509 self.consume(TokenType::DotDot)?;
2510 let max_val = self.consume_number()?;
2511 self.consume(TokenType::RParen)?;
2512 node.range_constraint = Some(RangeConstraint {
2513 min_value: min_val,
2514 max_value: max_val,
2515 loc: loc.clone(),
2516 });
2517 }
2518
2519 if self.check(TokenType::Where) {
2521 self.advance();
2522 let mut expr_parts = Vec::new();
2523 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
2524 if self.check(TokenType::Eof) {
2525 break;
2526 }
2527 expr_parts.push(self.advance().value.clone());
2528 }
2529 node.where_clause = Some(WhereClause {
2530 expression: expr_parts.join(" "),
2531 loc: loc.clone(),
2532 });
2533 }
2534
2535 if self.check(TokenType::Identifier) && self.current().value == "compliance" {
2538 self.advance();
2539 node.compliance = self.parse_bracketed_identifiers()?;
2540 }
2541
2542 if self.check(TokenType::LBrace) {
2544 self.advance();
2545 while !self.check(TokenType::RBrace) {
2546 let field_name = self.consume(TokenType::Identifier)?;
2547 let field_loc = self.loc_of(&field_name);
2548 self.consume(TokenType::Colon)?;
2549 let type_expr = self.parse_type_expr()?;
2550 node.fields.push(TypeField {
2551 name: field_name.value,
2552 type_expr,
2553 loc: field_loc,
2554 });
2555 if self.check(TokenType::Comma) {
2556 self.advance();
2557 }
2558 }
2559 self.consume(TokenType::RBrace)?;
2560 }
2561
2562 Ok(node)
2563 }
2564
2565 fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
2566 let name_tok = self.consume(TokenType::Identifier)?;
2567 let loc = self.loc_of(&name_tok);
2568 let mut generic_param = String::new();
2569 let mut optional = false;
2570
2571 if self.check(TokenType::Lt) {
2572 self.advance();
2573 let inner = self.parse_type_expr()?;
2583 generic_param = if inner.generic_param.is_empty() {
2584 inner.name
2585 } else {
2586 format!("{}<{}>", inner.name, inner.generic_param)
2587 };
2588 self.consume(TokenType::Gt)?;
2589 }
2590 if self.check(TokenType::Question) {
2591 self.advance();
2592 optional = true;
2593 }
2594
2595 Ok(TypeExpr {
2596 name: name_tok.value,
2597 generic_param,
2598 optional,
2599 loc,
2600 })
2601 }
2602
2603 fn parse_output_type_string(&mut self) -> Result<String, ParseError> {
2628 let expr = self.parse_type_expr()?;
2629 let mut s = expr.name;
2630 if !expr.generic_param.is_empty() {
2631 s.push('<');
2632 s.push_str(&expr.generic_param);
2633 s.push('>');
2634 }
2635 if expr.optional {
2636 s.push('?');
2637 }
2638 Ok(s)
2639 }
2640
2641 fn parse_flow(&mut self) -> Result<FlowDefinition, ParseError> {
2644 let tok = self.consume(TokenType::Flow)?;
2645 let loc = self.loc_of(&tok);
2646 let name = self.consume(TokenType::Identifier)?.value;
2647
2648 self.consume(TokenType::LParen)?;
2649 let mut parameters = Vec::new();
2650 if !self.check(TokenType::RParen) {
2651 parameters = self.parse_param_list()?;
2652 }
2653 self.consume(TokenType::RParen)?;
2654
2655 let mut return_type = None;
2656 if self.check(TokenType::Arrow) {
2657 self.advance();
2658 return_type = Some(self.parse_type_expr()?);
2659 }
2660
2661 self.consume(TokenType::LBrace)?;
2662 let mut body = Vec::new();
2663 while !self.check(TokenType::RBrace) {
2664 body.push(self.parse_flow_step()?);
2665 }
2666 self.consume(TokenType::RBrace)?;
2667
2668 Ok(FlowDefinition {
2669 name,
2670 parameters,
2671 return_type,
2672 body,
2673 loc,
2674 leading_trivia: Vec::new(),
2675 trailing_trivia: Vec::new(),
2676 })
2677 }
2678
2679 fn parse_param_list(&mut self) -> Result<Vec<Parameter>, ParseError> {
2680 let mut params = Vec::new();
2681
2682 let name = self.consume(TokenType::Identifier)?;
2683 let ploc = self.loc_of(&name);
2684 self.consume(TokenType::Colon)?;
2685 let type_expr = self.parse_type_expr()?;
2686 params.push(Parameter {
2687 name: name.value,
2688 type_expr,
2689 loc: ploc,
2690 });
2691
2692 while self.check(TokenType::Comma) {
2693 self.advance();
2694 let name = self.consume(TokenType::Identifier)?;
2695 let ploc = self.loc_of(&name);
2696 self.consume(TokenType::Colon)?;
2697 let type_expr = self.parse_type_expr()?;
2698 params.push(Parameter {
2699 name: name.value,
2700 type_expr,
2701 loc: ploc,
2702 });
2703 }
2704 Ok(params)
2705 }
2706
2707 fn parse_flow_step(&mut self) -> Result<FlowStep, ParseError> {
2710 let tok = self.current().clone();
2711
2712 match tok.ttype {
2713 TokenType::Step => self.parse_step().map(FlowStep::Step),
2714 TokenType::If => self.parse_if().map(FlowStep::If),
2715 TokenType::For => self.parse_for_in().map(FlowStep::ForIn),
2716 TokenType::Let => self.parse_let().map(FlowStep::Let),
2717 TokenType::Return => self.parse_return().map(FlowStep::Return),
2718 TokenType::Break => self.parse_break().map(FlowStep::Break),
2719 TokenType::Continue => self.parse_continue().map(FlowStep::Continue),
2720 TokenType::Lambda => self.parse_lambda_data_apply().map(FlowStep::LambdaDataApply),
2721
2722 TokenType::Probe => self.parse_flow_step_simple("probe").map(|l| FlowStep::Probe(ProbeStep { target: l.1, loc: l.0 })),
2724 TokenType::Reason => self.parse_flow_step_simple("reason").map(|l| FlowStep::Reason(ReasonStep { strategy: String::new(), target: l.1, loc: l.0 })),
2725 TokenType::Validate => self.parse_flow_step_simple("validate").map(|l| FlowStep::Validate(ValidateStep { target: l.1, rule: String::new(), loc: l.0 })),
2726 TokenType::Refine => self.parse_flow_step_simple("refine").map(|l| FlowStep::Refine(RefineStep { target: l.1, strategy: String::new(), loc: l.0 })),
2727 TokenType::Weave => self.parse_weave_step(),
2728 TokenType::Use => self.parse_use_step(),
2729 TokenType::Remember => self.parse_remember_step(),
2730 TokenType::Recall => self.parse_recall_step(),
2731 TokenType::Par => self.parse_block_step("par").map(|l| FlowStep::Par(ParBlock { loc: l })),
2732 TokenType::Hibernate => self.parse_hibernate_step(),
2733 TokenType::Deliberate => self.parse_block_step("deliberate").map(|l| FlowStep::Deliberate(DeliberateBlock { loc: l })),
2734 TokenType::Consensus => self.parse_block_step("consensus").map(|l| FlowStep::Consensus(ConsensusBlock { loc: l })),
2735 TokenType::Forge => self.parse_block_step("forge").map(|l| FlowStep::Forge(ForgeBlock { loc: l })),
2736 TokenType::Focus => self.parse_flow_step_simple("focus").map(|l| FlowStep::Focus(FocusStep { expression: l.1, loc: l.0 })),
2737 TokenType::Associate => self.parse_associate_step(),
2738 TokenType::Aggregate => self.parse_aggregate_step(),
2739 TokenType::Explore => self.parse_explore_step(),
2740 TokenType::Ingest => self.parse_ingest_step(),
2741 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 })),
2742 TokenType::Stream => self.parse_block_step("stream").map(|l| FlowStep::Stream(StreamBlock { loc: l })),
2743 TokenType::Navigate => self.parse_navigate_step(),
2744 TokenType::Drill => self.parse_drill_step(),
2745 TokenType::Trail => self.parse_flow_step_simple("trail").map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 })),
2746 TokenType::Corroborate => self.parse_corroborate_step(),
2747 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 })),
2748 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 })),
2749 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 })),
2750 TokenType::Listen => self.parse_listen_step(),
2751 TokenType::Daemon => self.parse_flow_step_simple("daemon").map(|l| FlowStep::DaemonStep(DaemonStepNode { daemon_ref: l.1, loc: l.0 })),
2752 TokenType::Emit => self.parse_emit_step(),
2754 TokenType::Publish => self.parse_publish_step(),
2755 TokenType::Discover => self.parse_discover_step(),
2756 TokenType::Persist => self.parse_persist_step(),
2757 TokenType::Retrieve => self.parse_retrieve_step(),
2758 TokenType::Mutate => self.parse_mutate_step(),
2759 TokenType::Purge => self.parse_store_where_step().map(|(loc, store_name, where_expr)| FlowStep::Purge(PurgeStep { store_name, where_expr, loc })),
2760 TokenType::Transact => self.parse_block_step("transact").map(|l| FlowStep::Transact(TransactBlock { loc: l })),
2761
2762 _ => {
2763 let hint = crate::smart_suggest::suggest_for(
2767 &tok.value,
2768 crate::smart_suggest::FLOW_BODY_KEYWORD_NAMES,
2769 );
2770 let base = format!(
2771 "Unexpected token in flow body: '{}' — expected step, if, for, let, return, ...",
2772 tok.value
2773 );
2774 let message = if hint.is_empty() {
2775 base
2776 } else {
2777 format!("{base}. {hint}")
2778 };
2779 Err(ParseError {
2780 message,
2781 line: tok.line,
2782 column: tok.column,
2783 ..Default::default()
2784 })
2785 }
2786 }
2787 }
2788
2789 fn parse_step(&mut self) -> Result<StepNode, ParseError> {
2792 let tok = self.consume(TokenType::Step)?;
2793 let loc = self.loc_of(&tok);
2794 let name = self.consume(TokenType::Identifier)?.value;
2795
2796 let mut persona_ref = String::new();
2797 if self.check(TokenType::Use) {
2798 self.advance();
2799 persona_ref = self.consume_any_ident_or_kw()?.value;
2800 }
2801
2802 self.consume(TokenType::LBrace)?;
2803
2804 let mut node = StepNode {
2805 name,
2806 persona_ref,
2807 given: String::new(),
2808 ask: String::new(),
2809 output_type: String::new(),
2810 confidence_floor: None,
2811 navigate_ref: String::new(),
2812 apply_ref: String::new(),
2813 loc,
2814 };
2815
2816 while !self.check(TokenType::RBrace) {
2817 let inner = self.current().clone();
2818
2819 match inner.ttype {
2820 TokenType::Given => {
2821 self.advance();
2822 self.consume(TokenType::Colon)?;
2823 node.given = self.parse_expression_string()?;
2824 }
2825 TokenType::Ask => {
2826 self.advance();
2827 self.consume(TokenType::Colon)?;
2828 node.ask = self.consume(TokenType::StringLit)?.value;
2829 }
2830 TokenType::Output => {
2831 self.advance();
2841 self.consume(TokenType::Colon)?;
2842 node.output_type = self.parse_output_type_string()?;
2843 }
2844 TokenType::Navigate => {
2845 self.advance();
2846 self.consume(TokenType::Colon)?;
2847 node.navigate_ref = self.parse_dotted_identifier()?;
2848 }
2849 TokenType::Identifier if inner.value == "confidence_floor" => {
2850 self.advance();
2851 self.consume(TokenType::Colon)?;
2852 node.confidence_floor = Some(self.consume_number()?);
2853 }
2854 TokenType::Identifier if inner.value == "apply" => {
2855 self.advance();
2856 self.consume(TokenType::Colon)?;
2857 node.apply_ref = self.consume_any_ident_or_kw()?.value;
2858 }
2859 TokenType::Use => {
2869 let tool = self
2870 .tokens
2871 .get(self.pos + 1)
2872 .map(|t| t.value.as_str())
2873 .filter(|v| !v.is_empty())
2874 .unwrap_or("<Tool>");
2875 return Err(ParseError {
2876 message: format!(
2877 "`use` is not valid inside a `step {{ }}` body — the tool dispatch \
2878 would be silently dropped. To invoke a tool, either write the \
2879 flow-level step `use {tool} on <arg>` (outside this block), or bind \
2880 it inside this step with `apply: {tool}`. To attach a persona, put \
2881 it in the step header: `step <name> use <Persona> {{ … }}`."
2882 ),
2883 line: inner.line,
2884 column: inner.column,
2885 ..Default::default()
2886 });
2887 }
2888 TokenType::Probe
2890 | TokenType::Reason
2891 | TokenType::Weave
2892 | TokenType::Stream => {
2893 self.skip_flow_step_structural()?;
2894 }
2895 _ => {
2896 return Err(ParseError {
2897 message: format!(
2898 "Unexpected token in step body: '{}' — expected given, ask, \
2899 probe, reason, weave, stream, output, confidence_floor, navigate, apply",
2900 inner.value
2901 ),
2902 line: inner.line,
2903 column: inner.column,
2904 ..Default::default()
2905 });
2906 }
2907 }
2908 }
2909 self.consume(TokenType::RBrace)?;
2910 Ok(node)
2911 }
2912
2913 fn skip_flow_step_structural(&mut self) -> Result<(), ParseError> {
2915 self.advance();
2917 while !self.check(TokenType::LBrace)
2919 && !self.check(TokenType::RBrace)
2920 && !self.check(TokenType::Eof)
2921 {
2922 let tt = &self.current().ttype;
2924 if matches!(
2925 tt,
2926 TokenType::Step
2927 | TokenType::Given
2928 | TokenType::Ask
2929 | TokenType::Output
2930 | TokenType::Navigate
2931 | TokenType::Use
2932 | TokenType::Probe
2933 | TokenType::Reason
2934 | TokenType::Weave
2935 | TokenType::Stream
2936 | TokenType::If
2937 | TokenType::For
2938 | TokenType::Let
2939 | TokenType::Return
2940 ) {
2941 return Ok(());
2942 }
2943 self.advance();
2944 }
2945 if self.check(TokenType::LBrace) {
2947 self.skip_braced_block()?;
2948 }
2949 Ok(())
2950 }
2951
2952 fn parse_intent(&mut self) -> Result<IntentNode, ParseError> {
2955 let tok = self.consume(TokenType::Intent)?;
2956 let loc = self.loc_of(&tok);
2957 let name = self.consume(TokenType::Identifier)?.value;
2958 self.consume(TokenType::LBrace)?;
2959
2960 let mut node = IntentNode {
2961 name,
2962 given: String::new(),
2963 ask: String::new(),
2964 output_type: None,
2965 confidence_floor: None,
2966 loc,
2967 leading_trivia: Vec::new(),
2968 trailing_trivia: Vec::new(),
2969 };
2970
2971 while !self.check(TokenType::RBrace) {
2972 let field_name = self.current().value.clone();
2973 self.advance();
2974 self.consume(TokenType::Colon)?;
2975
2976 match field_name.as_str() {
2977 "given" => node.given = self.consume(TokenType::Identifier)?.value,
2978 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
2979 "output" => node.output_type = Some(self.parse_type_expr()?),
2980 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
2981 _ => self.skip_value(),
2982 }
2983 }
2984 self.consume(TokenType::RBrace)?;
2985 Ok(node)
2986 }
2987
2988 fn parse_run(&mut self) -> Result<RunStatement, ParseError> {
2991 let tok = self.consume(TokenType::Run)?;
2992 let loc = self.loc_of(&tok);
2993 let flow_name = self.consume(TokenType::Identifier)?.value;
2994
2995 self.consume(TokenType::LParen)?;
2996 let mut arguments = Vec::new();
2997 if !self.check(TokenType::RParen) {
2998 arguments = self.parse_argument_list()?;
2999 }
3000 self.consume(TokenType::RParen)?;
3001
3002 let mut node = RunStatement {
3003 flow_name,
3004 arguments,
3005 persona: String::new(),
3006 context: String::new(),
3007 anchors: Vec::new(),
3008 on_failure: String::new(),
3009 on_failure_params: Vec::new(),
3010 output_to: String::new(),
3011 effort: String::new(),
3012 loc,
3013 leading_trivia: Vec::new(),
3014 trailing_trivia: Vec::new(),
3015 };
3016
3017 while self.check_run_modifier() {
3018 let mod_tok = self.current().clone();
3019 match mod_tok.ttype {
3020 TokenType::As => {
3021 self.advance();
3022 node.persona = self.consume(TokenType::Identifier)?.value;
3023 }
3024 TokenType::Within => {
3025 self.advance();
3026 node.context = self.consume(TokenType::Identifier)?.value;
3027 }
3028 TokenType::ConstrainedBy => {
3029 self.advance();
3030 node.anchors = self.parse_bracketed_identifiers()?;
3031 }
3032 TokenType::OnFailure => {
3033 self.advance();
3034 self.consume(TokenType::Colon)?;
3035 node.on_failure = self.consume_any_ident_or_kw()?.value;
3036 if self.check(TokenType::LParen) {
3038 self.advance();
3039 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3040 let key = self.consume_any_ident_or_kw()?.value;
3041 self.consume(TokenType::Colon)?;
3042 let val = self.consume_any_ident_or_kw()?.value;
3043 node.on_failure_params.push((key, val));
3044 if self.check(TokenType::Comma) {
3045 self.advance();
3046 }
3047 }
3048 if self.check(TokenType::RParen) {
3049 self.advance();
3050 }
3051 }
3052 }
3053 TokenType::OutputTo => {
3054 self.advance();
3055 self.consume(TokenType::Colon)?;
3056 node.output_to = self.consume(TokenType::StringLit)?.value;
3057 }
3058 TokenType::Effort => {
3059 self.advance();
3060 self.consume(TokenType::Colon)?;
3061 node.effort = self.consume_any_ident_or_kw()?.value;
3062 }
3063 _ => break,
3064 }
3065 }
3066
3067 Ok(node)
3068 }
3069
3070 fn parse_epistemic_block(&mut self) -> Result<EpistemicBlock, ParseError> {
3073 let tok = self.current().clone();
3074 let mode = match tok.ttype {
3075 TokenType::Know => "know",
3076 TokenType::Believe => "believe",
3077 TokenType::Speculate => "speculate",
3078 TokenType::Doubt => "doubt",
3079 _ => unreachable!(),
3080 };
3081 self.advance();
3082 let loc = self.loc_of(&tok);
3083
3084 self.consume(TokenType::LBrace)?;
3085 let mut body = Vec::new();
3086 while !self.check(TokenType::RBrace) {
3087 body.push(self.parse_declaration()?);
3088 }
3089 self.consume(TokenType::RBrace)?;
3090
3091 Ok(EpistemicBlock {
3092 mode: mode.to_string(),
3093 body,
3094 loc,
3095 leading_trivia: Vec::new(),
3096 trailing_trivia: Vec::new(),
3097 })
3098 }
3099
3100 fn parse_if(&mut self) -> Result<ConditionalNode, ParseError> {
3103 let tok = self.consume(TokenType::If)?;
3104 let loc = self.loc_of(&tok);
3105
3106 let mut parts = vec![self.consume_any_ident_or_kw()?.value];
3108 while self.check(TokenType::Dot) {
3109 self.advance();
3110 parts.push(self.consume_any_ident_or_kw()?.value);
3111 }
3112 let condition = parts.join(".");
3113
3114 let mut comparison_op = String::new();
3115 let mut comparison_value = String::new();
3116 if self.check_comparison() {
3117 comparison_op = self.advance().value.clone();
3118 let val_tok = self.current().clone();
3119 if val_tok.ttype == TokenType::StringLit {
3120 comparison_value = val_tok.value;
3121 self.advance();
3122 } else {
3123 comparison_value = self.advance().value.clone();
3124 }
3125 }
3126
3127 let mut conditions = Vec::new();
3129 let mut conjunctor = String::new();
3130 while self.check(TokenType::Or) {
3131 conjunctor = "or".to_string();
3132 self.advance();
3133 let mut cond_parts = vec![self.consume_any_ident_or_kw()?.value];
3134 while self.check(TokenType::Dot) {
3135 self.advance();
3136 cond_parts.push(self.consume_any_ident_or_kw()?.value);
3137 }
3138 let cond_str = cond_parts.join(".");
3139 let mut cond_op = String::new();
3140 let mut cond_val = String::new();
3141 if self.check_comparison() {
3142 cond_op = self.advance().value.clone();
3143 let val_tok = self.current().clone();
3144 if val_tok.ttype == TokenType::StringLit {
3145 cond_val = val_tok.value;
3146 self.advance();
3147 } else {
3148 cond_val = self.advance().value.clone();
3149 }
3150 }
3151 conditions.push((cond_str, cond_op, cond_val));
3152 }
3153
3154 let mut then_body = Vec::new();
3155 let mut else_body = Vec::new();
3156
3157 if self.check(TokenType::Arrow) {
3159 self.advance();
3160 then_body.push(self.parse_flow_step()?);
3161 } else if self.check(TokenType::LBrace) {
3162 self.advance();
3163 while !self.check(TokenType::RBrace) {
3164 then_body.push(self.parse_flow_step()?);
3165 }
3166 self.consume(TokenType::RBrace)?;
3167 }
3168
3169 if self.check(TokenType::Else) {
3171 self.advance();
3172 if self.check(TokenType::Arrow) {
3173 self.advance();
3174 else_body.push(self.parse_flow_step()?);
3175 } else if self.check(TokenType::LBrace) {
3176 self.advance();
3177 while !self.check(TokenType::RBrace) {
3178 else_body.push(self.parse_flow_step()?);
3179 }
3180 self.consume(TokenType::RBrace)?;
3181 }
3182 }
3183
3184 Ok(ConditionalNode {
3185 condition,
3186 comparison_op,
3187 comparison_value,
3188 then_body,
3189 else_body,
3190 conditions,
3191 conjunctor,
3192 loc,
3193 })
3194 }
3195
3196 fn parse_for_in(&mut self) -> Result<ForInStatement, ParseError> {
3199 let tok = self.consume(TokenType::For)?;
3200 let loc = self.loc_of(&tok);
3201 let variable = self.consume(TokenType::Identifier)?.value;
3202 self.consume(TokenType::In)?;
3203 let iterable = self.parse_dotted_identifier()?;
3204
3205 self.consume(TokenType::LBrace)?;
3206 self.loop_depth += 1;
3213 let body_result = (|| -> Result<Vec<FlowStep>, ParseError> {
3214 let mut body = Vec::new();
3215 while !self.check(TokenType::RBrace) {
3216 body.push(self.parse_flow_step()?);
3217 }
3218 Ok(body)
3219 })();
3220 self.loop_depth -= 1;
3221 let body = body_result?;
3222 self.consume(TokenType::RBrace)?;
3223
3224 Ok(ForInStatement {
3225 variable,
3226 iterable,
3227 body,
3228 loc,
3229 })
3230 }
3231
3232 fn parse_break(&mut self) -> Result<BreakStatement, ParseError> {
3235 let tok = self.consume(TokenType::Break)?;
3236 let loc = self.loc_of(&tok);
3237 if self.loop_depth == 0 {
3238 return Err(ParseError {
3239 message: "'break' outside of a for-in loop body".to_string(),
3240 line: tok.line,
3241 column: tok.column,
3242 ..Default::default()
3243 });
3244 }
3245 Ok(BreakStatement { loc })
3246 }
3247
3248 fn parse_continue(&mut self) -> Result<ContinueStatement, ParseError> {
3251 let tok = self.consume(TokenType::Continue)?;
3252 let loc = self.loc_of(&tok);
3253 if self.loop_depth == 0 {
3254 return Err(ParseError {
3255 message: "'continue' outside of a for-in loop body".to_string(),
3256 line: tok.line,
3257 column: tok.column,
3258 ..Default::default()
3259 });
3260 }
3261 Ok(ContinueStatement { loc })
3262 }
3263
3264 fn parse_let(&mut self) -> Result<LetStatement, ParseError> {
3267 let tok = self.consume(TokenType::Let)?;
3268 let loc = self.loc_of(&tok);
3269
3270 let name = self.consume_any_ident_or_kw()?.value;
3272 self.consume(TokenType::Assign)?;
3273 self.last_let_value_kind = "literal".to_string();
3276 let value = self.parse_let_value_expr()?;
3277
3278 Ok(LetStatement {
3279 identifier: name,
3280 value_expr: value,
3281 value_kind: self.last_let_value_kind.clone(),
3282 loc,
3283 leading_trivia: Vec::new(),
3284 trailing_trivia: Vec::new(),
3285 })
3286 }
3287
3288 fn parse_let_value_expr(&mut self) -> Result<String, ParseError> {
3289 let atom = self.parse_let_atom()?;
3290
3291 if matches!(
3293 self.current().ttype,
3294 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3295 ) {
3296 let mut parts = vec![atom];
3297 while matches!(
3298 self.current().ttype,
3299 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3300 ) {
3301 parts.push(self.advance().value.clone());
3302 parts.push(self.parse_let_atom()?);
3303 }
3304 self.last_let_value_kind = "expression".to_string();
3305 return Ok(parts.join(" "));
3306 }
3307 Ok(atom)
3308 }
3309
3310 fn parse_let_atom(&mut self) -> Result<String, ParseError> {
3311 let tok = self.current().clone();
3312
3313 match tok.ttype {
3314 TokenType::StringLit => {
3315 self.last_let_value_kind = "literal".to_string();
3316 self.advance();
3317 Ok(tok.value)
3318 }
3319 TokenType::Integer | TokenType::Float => {
3320 self.last_let_value_kind = "literal".to_string();
3321 self.advance();
3322 Ok(tok.value)
3323 }
3324 TokenType::Bool => {
3325 self.last_let_value_kind = "literal".to_string();
3326 self.advance();
3327 Ok(tok.value)
3328 }
3329 TokenType::Identifier => {
3330 self.last_let_value_kind = "reference".to_string();
3331 self.parse_dotted_identifier()
3332 }
3333 TokenType::LBracket => {
3334 self.last_let_value_kind = "literal".to_string();
3335 self.parse_let_list_literal()
3336 }
3337 _ => {
3338 if self.pos + 1 < self.tokens.len()
3340 && self.tokens[self.pos + 1].ttype == TokenType::Dot
3341 {
3342 self.last_let_value_kind = "reference".to_string();
3343 return self.parse_dotted_identifier();
3344 }
3345 Err(ParseError {
3346 message: format!(
3347 "Expected value expression, found {:?}('{}')",
3348 tok.ttype, tok.value
3349 ),
3350 line: tok.line,
3351 column: tok.column,
3352 ..Default::default()
3353 })
3354 }
3355 }
3356 }
3357
3358 fn parse_let_list_literal(&mut self) -> Result<String, ParseError> {
3359 self.consume(TokenType::LBracket)?;
3360 let mut items = Vec::new();
3361 if !self.check(TokenType::RBracket) {
3362 items.push(self.parse_let_value_expr()?);
3363 while self.check(TokenType::Comma) {
3364 self.advance();
3365 if self.check(TokenType::RBracket) {
3366 break; }
3368 items.push(self.parse_let_value_expr()?);
3369 }
3370 }
3371 self.consume(TokenType::RBracket)?;
3372 Ok(format!("[{}]", items.join(", ")))
3373 }
3374
3375 fn parse_return(&mut self) -> Result<ReturnStatement, ParseError> {
3378 let tok = self.consume(TokenType::Return)?;
3379 let loc = self.loc_of(&tok);
3380 let value = self.parse_let_value_expr()?;
3381 Ok(ReturnStatement {
3382 value_expr: value,
3383 loc,
3384 })
3385 }
3386
3387 fn parse_flow_step_simple(&mut self, _kw: &str) -> Result<(Loc, String), ParseError> {
3391 let tok = self.current().clone();
3392 self.advance(); let target = if self.at_declaration_start()
3394 || self.check(TokenType::RBrace)
3395 || self.check(TokenType::Eof)
3396 {
3397 String::new()
3398 } else {
3399 self.consume_any_ident_or_kw()?.value.clone()
3400 };
3401 if self.check(TokenType::LBrace) {
3403 self.skip_braced_block()?;
3404 }
3405 Ok((
3406 Loc {
3407 line: tok.line,
3408 column: tok.column,
3409 },
3410 target,
3411 ))
3412 }
3413
3414 fn parse_block_step(&mut self, _kw: &str) -> Result<Loc, ParseError> {
3416 let tok = self.current().clone();
3417 self.advance();
3418 while !self.check(TokenType::LBrace)
3420 && !self.check(TokenType::RBrace)
3421 && !self.check(TokenType::Eof)
3422 && !self.at_declaration_start()
3423 {
3424 self.advance();
3425 }
3426 if self.check(TokenType::LBrace) {
3427 self.skip_braced_block()?;
3428 }
3429 Ok(Loc {
3430 line: tok.line,
3431 column: tok.column,
3432 })
3433 }
3434
3435 fn parse_apply_step(&mut self, _kw: &str) -> Result<(Loc, String, String, String), ParseError> {
3437 let tok = self.current().clone();
3438 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
3440 let mut target = String::new();
3441 let mut output_type = String::new();
3442 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3444 let next = self.current().clone();
3445 if next.value == "on" {
3446 self.advance();
3447 target = self.consume_any_ident_or_kw()?.value.clone();
3448 }
3449 }
3450 if self.check(TokenType::Arrow) {
3452 self.advance();
3453 output_type = self.consume_any_ident_or_kw()?.value.clone();
3454 }
3455 if self.check(TokenType::LBrace) {
3457 self.skip_braced_block()?;
3458 }
3459 Ok((
3460 Loc {
3461 line: tok.line,
3462 column: tok.column,
3463 },
3464 name,
3465 target,
3466 output_type,
3467 ))
3468 }
3469
3470 fn parse_weave_step(&mut self) -> Result<FlowStep, ParseError> {
3471 let tok = self.current().clone();
3472 self.advance();
3473 let mut node = WeaveStep {
3474 sources: Vec::new(),
3475 target: String::new(),
3476 format_type: String::new(),
3477 priority: Vec::new(),
3478 style: String::new(),
3479 loc: Loc {
3480 line: tok.line,
3481 column: tok.column,
3482 },
3483 };
3484 if self.check(TokenType::LBrace) {
3485 self.advance();
3486 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3487 let f = self.current().value.clone();
3488 self.advance();
3489 if self.check(TokenType::Colon) {
3490 self.advance();
3491 match f.as_str() {
3492 "sources" => node.sources = self.parse_bracketed_identifiers()?,
3493 "target" => node.target = self.consume_any_ident_or_kw()?.value.clone(),
3494 "format" => {
3495 node.format_type = self.consume_any_ident_or_kw()?.value.clone()
3496 }
3497 "priority" => node.priority = self.parse_bracketed_identifiers()?,
3498 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
3499 _ => self.skip_value(),
3500 }
3501 }
3502 }
3503 if self.check(TokenType::RBrace) {
3504 self.advance();
3505 }
3506 }
3507 Ok(FlowStep::Weave(node))
3508 }
3509
3510 fn parse_use_step(&mut self) -> Result<FlowStep, ParseError> {
3511 let tok = self.current().clone();
3512 self.advance();
3513 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
3514 let args = if self.check(TokenType::LParen) {
3525 UseArgs::Named(self.parse_named_arg_list()?)
3526 } else {
3527 let mut argument = String::new();
3528 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3529 let next = self.current().clone();
3530 if next.value == "on" {
3531 self.advance();
3532 argument = self.consume_any_ident_or_kw()?.value.clone();
3533 }
3534 }
3535 UseArgs::LegacyPositional(argument)
3536 };
3537 if self.check(TokenType::LBrace) {
3538 self.skip_braced_block()?;
3539 }
3540 Ok(FlowStep::UseTool(UseToolStep {
3541 tool_name,
3542 args,
3543 loc: Loc {
3544 line: tok.line,
3545 column: tok.column,
3546 },
3547 }))
3548 }
3549
3550 fn parse_named_arg_list(&mut self) -> Result<Vec<(String, String, String)>, ParseError> {
3556 self.consume(TokenType::LParen)?;
3557 let mut args = Vec::new();
3558 while !self.check(TokenType::RParen) {
3559 let name = self.consume_any_ident_or_kw()?.value;
3562 self.consume(TokenType::Assign)?;
3563 let value = self.parse_let_atom()?;
3564 let value_kind = self.last_let_value_kind.clone();
3568 args.push((name, value, value_kind));
3569 if self.check(TokenType::Comma) {
3570 self.advance();
3571 } else {
3572 break;
3573 }
3574 }
3575 self.consume(TokenType::RParen)?;
3576 Ok(args)
3577 }
3578
3579 fn parse_remember_step(&mut self) -> Result<FlowStep, ParseError> {
3580 let tok = self.current().clone();
3581 self.advance();
3582 let expr = self.consume_any_ident_or_kw()?.value.clone();
3583 let mut mem = String::new();
3584 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3585 let next = self.current().clone();
3586 if next.value == "in" || next.ttype == TokenType::In {
3587 self.advance();
3588 mem = self.consume_any_ident_or_kw()?.value.clone();
3589 }
3590 }
3591 Ok(FlowStep::Remember(RememberStep {
3592 expression: expr,
3593 memory_target: mem,
3594 loc: Loc {
3595 line: tok.line,
3596 column: tok.column,
3597 },
3598 }))
3599 }
3600
3601 fn parse_recall_step(&mut self) -> Result<FlowStep, ParseError> {
3602 let tok = self.current().clone();
3603 self.advance();
3604 let query = if self.check(TokenType::StringLit) {
3605 self.consume(TokenType::StringLit)?.value.clone()
3606 } else {
3607 self.consume_any_ident_or_kw()?.value.clone()
3608 };
3609 let mut mem = String::new();
3610 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3611 let next = self.current().clone();
3612 if next.value == "from" || next.ttype == TokenType::From {
3613 self.advance();
3614 mem = self.consume_any_ident_or_kw()?.value.clone();
3615 }
3616 }
3617 Ok(FlowStep::Recall(RecallStep {
3618 query,
3619 memory_source: mem,
3620 loc: Loc {
3621 line: tok.line,
3622 column: tok.column,
3623 },
3624 }))
3625 }
3626
3627 fn parse_hibernate_step(&mut self) -> Result<FlowStep, ParseError> {
3628 let tok = self.current().clone();
3629 self.advance();
3630 let mut event = String::new();
3631 let mut timeout = String::new();
3632 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3633 event = self.consume_any_ident_or_kw()?.value.clone();
3634 }
3635 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3636 let next = self.current().clone();
3637 if next.ttype == TokenType::Duration {
3638 self.advance();
3639 timeout = next.value.clone();
3640 }
3641 }
3642 Ok(FlowStep::Hibernate(HibernateStep {
3643 event_name: event,
3644 timeout,
3645 loc: Loc {
3646 line: tok.line,
3647 column: tok.column,
3648 },
3649 }))
3650 }
3651
3652 fn parse_associate_step(&mut self) -> Result<FlowStep, ParseError> {
3653 let tok = self.current().clone();
3654 self.advance();
3655 let left = self.consume_any_ident_or_kw()?.value.clone();
3656 let mut right = String::new();
3657 let mut using = String::new();
3658 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3659 right = self.consume_any_ident_or_kw()?.value.clone();
3660 }
3661 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3662 let next = self.current().clone();
3663 if next.value == "using" {
3664 self.advance();
3665 using = self.consume_any_ident_or_kw()?.value.clone();
3666 }
3667 }
3668 Ok(FlowStep::Associate(AssociateStep {
3669 left,
3670 right,
3671 using_field: using,
3672 loc: Loc {
3673 line: tok.line,
3674 column: tok.column,
3675 },
3676 }))
3677 }
3678
3679 fn parse_aggregate_step(&mut self) -> Result<FlowStep, ParseError> {
3680 let tok = self.current().clone();
3681 self.advance();
3682 let target = self.consume_any_ident_or_kw()?.value.clone();
3683 let mut group_by = Vec::new();
3684 let mut alias = String::new();
3685 if self.check(TokenType::LBrace) {
3686 self.advance();
3687 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3688 let f = self.current().value.clone();
3689 self.advance();
3690 if self.check(TokenType::Colon) {
3691 self.advance();
3692 match f.as_str() {
3693 "group_by" => group_by = self.parse_bracketed_identifiers()?,
3694 "alias" | "as" => alias = self.consume_any_ident_or_kw()?.value.clone(),
3695 _ => self.skip_value(),
3696 }
3697 }
3698 }
3699 if self.check(TokenType::RBrace) {
3700 self.advance();
3701 }
3702 }
3703 Ok(FlowStep::Aggregate(AggregateStep {
3704 target,
3705 group_by,
3706 alias,
3707 loc: Loc {
3708 line: tok.line,
3709 column: tok.column,
3710 },
3711 }))
3712 }
3713
3714 fn parse_explore_step(&mut self) -> Result<FlowStep, ParseError> {
3715 let tok = self.current().clone();
3716 self.advance();
3717 let target = self.consume_any_ident_or_kw()?.value.clone();
3718 let mut limit = None;
3719 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3720 if self.current().ttype == TokenType::Integer {
3721 limit = self.current().value.parse::<i64>().ok();
3722 self.advance();
3723 }
3724 }
3725 Ok(FlowStep::ExploreStep(ExploreStepNode {
3726 target,
3727 limit,
3728 loc: Loc {
3729 line: tok.line,
3730 column: tok.column,
3731 },
3732 }))
3733 }
3734
3735 fn parse_ingest_step(&mut self) -> Result<FlowStep, ParseError> {
3736 let tok = self.current().clone();
3737 self.advance();
3738 let source = self.consume_any_ident_or_kw()?.value.clone();
3739 let mut target = String::new();
3740 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
3741 let next = self.current().clone();
3742 if next.value == "into" || next.ttype == TokenType::Into {
3743 self.advance();
3744 target = self.consume_any_ident_or_kw()?.value.clone();
3745 }
3746 }
3747 if self.check(TokenType::LBrace) {
3748 self.skip_braced_block()?;
3749 }
3750 Ok(FlowStep::Ingest(IngestStep {
3751 source,
3752 target,
3753 loc: Loc {
3754 line: tok.line,
3755 column: tok.column,
3756 },
3757 }))
3758 }
3759
3760 fn parse_navigate_step(&mut self) -> Result<FlowStep, ParseError> {
3761 let tok = self.current().clone();
3762 self.advance();
3763 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
3764 let mut node = NavigateStep {
3765 pix_name,
3766 corpus_name: String::new(),
3767 query_expr: String::new(),
3768 trail_enabled: false,
3769 output_name: String::new(),
3770 loc: Loc {
3771 line: tok.line,
3772 column: tok.column,
3773 },
3774 };
3775 if self.check(TokenType::LBrace) {
3776 self.advance();
3777 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3778 let f = self.current().value.clone();
3779 self.advance();
3780 if self.check(TokenType::Colon) {
3781 self.advance();
3782 match f.as_str() {
3783 "corpus" => {
3784 node.corpus_name = self.consume_any_ident_or_kw()?.value.clone()
3785 }
3786 "query" => {
3787 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
3788 }
3789 "trail" => {
3790 node.trail_enabled = self.consume_any_ident_or_kw()?.value == "true"
3791 }
3792 "output" | "as" => {
3793 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
3794 }
3795 _ => self.skip_value(),
3796 }
3797 }
3798 }
3799 if self.check(TokenType::RBrace) {
3800 self.advance();
3801 }
3802 }
3803 Ok(FlowStep::Navigate(node))
3804 }
3805
3806 fn parse_drill_step(&mut self) -> Result<FlowStep, ParseError> {
3807 let tok = self.current().clone();
3808 self.advance();
3809 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
3810 let mut node = DrillStep {
3811 pix_name,
3812 subtree_path: String::new(),
3813 query_expr: String::new(),
3814 output_name: String::new(),
3815 loc: Loc {
3816 line: tok.line,
3817 column: tok.column,
3818 },
3819 };
3820 if self.check(TokenType::LBrace) {
3821 self.advance();
3822 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3823 let f = self.current().value.clone();
3824 self.advance();
3825 if self.check(TokenType::Colon) {
3826 self.advance();
3827 match f.as_str() {
3828 "subtree" | "path" => {
3829 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
3830 }
3831 "query" => {
3832 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
3833 }
3834 "output" | "as" => {
3835 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
3836 }
3837 _ => self.skip_value(),
3838 }
3839 }
3840 }
3841 if self.check(TokenType::RBrace) {
3842 self.advance();
3843 }
3844 }
3845 Ok(FlowStep::Drill(node))
3846 }
3847
3848 fn parse_corroborate_step(&mut self) -> Result<FlowStep, ParseError> {
3849 let tok = self.current().clone();
3850 self.advance();
3851 let nav_ref = self.consume_any_ident_or_kw()?.value.clone();
3852 let mut output = String::new();
3853 if self.check(TokenType::Arrow) {
3854 self.advance();
3855 output = self.consume_any_ident_or_kw()?.value.clone();
3856 }
3857 Ok(FlowStep::Corroborate(CorroborateStep {
3858 navigate_ref: nav_ref,
3859 output_name: output,
3860 loc: Loc {
3861 line: tok.line,
3862 column: tok.column,
3863 },
3864 }))
3865 }
3866
3867 fn parse_listen_step(&mut self) -> Result<FlowStep, ParseError> {
3868 let tok = self.current().clone();
3869 self.advance();
3870 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
3874 (self.consume(TokenType::StringLit)?.value.clone(), false)
3875 } else {
3876 (self.consume_any_ident_or_kw()?.value.clone(), true)
3877 };
3878 let mut alias = String::new();
3879 if !self.at_declaration_start()
3880 && !self.check(TokenType::RBrace)
3881 && !self.check(TokenType::LBrace)
3882 {
3883 let next = self.current().clone();
3884 if next.value == "as" || next.ttype == TokenType::As {
3885 self.advance();
3886 alias = self.consume_any_ident_or_kw()?.value.clone();
3887 }
3888 }
3889 if self.check(TokenType::LBrace) {
3890 self.skip_braced_block()?;
3891 }
3892 Ok(FlowStep::Listen(ListenStep {
3893 channel,
3894 channel_is_ref,
3895 event_alias: alias,
3896 loc: Loc {
3897 line: tok.line,
3898 column: tok.column,
3899 },
3900 }))
3901 }
3902
3903 fn parse_retrieve_step(&mut self) -> Result<FlowStep, ParseError> {
3904 let tok = self.current().clone();
3905 self.advance();
3906 let store = self.consume_any_ident_or_kw()?.value.clone();
3907 let mut where_expr = String::new();
3908 let mut alias = String::new();
3909 if self.check(TokenType::LBrace) {
3910 self.advance();
3911 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3912 let f = self.current().value.clone();
3913 self.advance();
3914 if self.check(TokenType::Colon) {
3915 self.advance();
3916 match f.as_str() {
3917 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
3918 "as" | "alias" => alias = self.consume_any_ident_or_kw()?.value.clone(),
3919 _ => self.skip_value(),
3920 }
3921 }
3922 }
3923 if self.check(TokenType::RBrace) {
3924 self.advance();
3925 }
3926 }
3927 Ok(FlowStep::Retrieve(RetrieveStep {
3928 store_name: store,
3929 where_expr,
3930 alias,
3931 loc: Loc {
3932 line: tok.line,
3933 column: tok.column,
3934 },
3935 }))
3936 }
3937
3938 fn parse_store_where_step(
3951 &mut self,
3952 ) -> Result<(Loc, String, String), ParseError> {
3953 let tok = self.current().clone();
3954 self.advance(); let store = if self.at_declaration_start()
3956 || self.check(TokenType::RBrace)
3957 || self.check(TokenType::Eof)
3958 {
3959 String::new()
3960 } else {
3961 self.consume_any_ident_or_kw()?.value.clone()
3962 };
3963 let mut where_expr = String::new();
3964 if self.check(TokenType::LBrace) {
3965 self.advance();
3966 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3967 let field = self.current().value.clone();
3968 self.advance();
3969 if self.check(TokenType::Colon) {
3970 self.advance();
3971 match field.as_str() {
3972 "where" => {
3973 where_expr =
3974 self.consume(TokenType::StringLit)?.value.clone()
3975 }
3976 _ => self.skip_value(),
3977 }
3978 }
3979 }
3980 if self.check(TokenType::RBrace) {
3981 self.advance();
3982 }
3983 }
3984 Ok((
3985 Loc {
3986 line: tok.line,
3987 column: tok.column,
3988 },
3989 store,
3990 where_expr,
3991 ))
3992 }
3993
3994 fn parse_persist_step(&mut self) -> Result<FlowStep, ParseError> {
4013 let tok = self.current().clone();
4014 self.advance(); if self.current().value == "into" && !self.check(TokenType::LBrace) {
4018 self.advance();
4019 }
4020 let store = if self.at_declaration_start()
4021 || self.check(TokenType::LBrace)
4022 || self.check(TokenType::RBrace)
4023 || self.check(TokenType::Eof)
4024 {
4025 String::new()
4026 } else {
4027 self.consume_any_ident_or_kw()?.value.clone()
4028 };
4029 let mut fields: Vec<(String, String)> = Vec::new();
4030 if self.check(TokenType::LBrace) {
4031 self.advance();
4032 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4033 let col = self.current().value.clone();
4034 self.advance();
4035 if self.check(TokenType::Colon) {
4036 self.advance();
4037 let value = if self.check(TokenType::StringLit) {
4038 self.consume(TokenType::StringLit)?.value.clone()
4039 } else if self.check(TokenType::RBrace)
4040 || self.check(TokenType::Eof)
4041 || self.check(TokenType::Colon)
4042 {
4043 String::new()
4044 } else {
4045 let v = self.current().clone();
4046 self.advance();
4047 v.value.clone()
4048 };
4049 fields.push((col, value));
4050 }
4051 }
4052 if self.check(TokenType::RBrace) {
4053 self.advance();
4054 }
4055 }
4056 Ok(FlowStep::Persist(PersistStep {
4057 store_name: store,
4058 fields,
4059 loc: Loc {
4060 line: tok.line,
4061 column: tok.column,
4062 },
4063 }))
4064 }
4065
4066 fn parse_mutate_step(&mut self) -> Result<FlowStep, ParseError> {
4080 let tok = self.current().clone();
4081 self.advance(); let store = if self.at_declaration_start()
4083 || self.check(TokenType::LBrace)
4084 || self.check(TokenType::RBrace)
4085 || self.check(TokenType::Eof)
4086 {
4087 String::new()
4088 } else {
4089 self.consume_any_ident_or_kw()?.value.clone()
4090 };
4091 let mut where_expr = String::new();
4092 let mut fields: Vec<(String, String)> = Vec::new();
4093 if self.check(TokenType::LBrace) {
4094 self.advance();
4095 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4096 let key = self.current().value.clone();
4097 self.advance();
4098 if self.check(TokenType::Colon) {
4099 self.advance();
4100 if key == "where" {
4101 where_expr =
4102 self.consume(TokenType::StringLit)?.value.clone();
4103 } else {
4104 let value = if self.check(TokenType::StringLit) {
4105 self.consume(TokenType::StringLit)?.value.clone()
4106 } else if self.check(TokenType::RBrace)
4107 || self.check(TokenType::Eof)
4108 || self.check(TokenType::Colon)
4109 {
4110 String::new()
4111 } else {
4112 let v = self.current().clone();
4113 self.advance();
4114 v.value.clone()
4115 };
4116 fields.push((key, value));
4117 }
4118 }
4119 }
4120 if self.check(TokenType::RBrace) {
4121 self.advance();
4122 }
4123 }
4124 Ok(FlowStep::Mutate(MutateStep {
4125 store_name: store,
4126 where_expr,
4127 fields,
4128 loc: Loc {
4129 line: tok.line,
4130 column: tok.column,
4131 },
4132 }))
4133 }
4134
4135 fn parse_agent(&mut self) -> Result<AgentDefinition, ParseError> {
4138 let tok = self.consume(TokenType::Agent)?;
4139 let name = self.consume(TokenType::Identifier)?.value;
4140 let mut node = AgentDefinition {
4141 name,
4142 goal: String::new(),
4143 tools: Vec::new(),
4144 memory_ref: String::new(),
4145 strategy: String::new(),
4146 on_stuck: String::new(),
4147 shield_ref: String::new(),
4148 max_iterations: None,
4149 max_tokens: None,
4150 max_time: String::new(),
4151 max_cost: None,
4152 loc: Loc {
4153 line: tok.line,
4154 column: tok.column,
4155 },
4156 leading_trivia: Vec::new(),
4157 trailing_trivia: Vec::new(),
4158 };
4159 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
4161 self.advance();
4162 }
4163 self.consume(TokenType::LBrace)?;
4164 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4165 let field = self.current().clone();
4166 let field_name = field.value.clone();
4167 self.advance();
4168 if self.check(TokenType::Colon) {
4169 self.advance();
4170 match field_name.as_str() {
4171 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
4172 "tools" => node.tools = self.parse_bracketed_identifiers()?,
4173 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
4174 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
4175 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
4176 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
4177 "max_iterations" => node.max_iterations = self.parse_optional_int(),
4178 "max_tokens" => node.max_tokens = self.parse_optional_int(),
4179 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
4180 "max_cost" => node.max_cost = self.parse_optional_float(),
4181 _ => self.skip_value(),
4182 }
4183 } else if self.check(TokenType::LBrace) {
4184 self.skip_braced_block()?;
4185 }
4186 }
4187 self.consume(TokenType::RBrace)?;
4188 Ok(node)
4189 }
4190
4191 fn parse_extension(&mut self) -> Result<ExtensionDefinition, ParseError> {
4196 let tok = self.consume(TokenType::Extension)?;
4197 let name = self.consume(TokenType::Identifier)?.value;
4198 let mut node = ExtensionDefinition {
4199 name,
4200 category: String::new(),
4201 members: Vec::new(),
4202 loc: Loc {
4203 line: tok.line,
4204 column: tok.column,
4205 },
4206 leading_trivia: Vec::new(),
4207 trailing_trivia: Vec::new(),
4208 };
4209 self.consume(TokenType::LBrace)?;
4210 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4211 let field_name = self.current().value.clone();
4212 self.advance();
4213 if self.check(TokenType::Colon) {
4214 self.advance();
4215 match field_name.as_str() {
4216 "category" => {
4217 node.category = self.consume_any_ident_or_kw()?.value.clone()
4218 }
4219 "members" => node.members = self.parse_extension_members()?,
4220 _ => self.skip_value(),
4221 }
4222 } else if self.check(TokenType::LBrace) {
4223 self.skip_braced_block()?;
4224 }
4225 }
4226 self.consume(TokenType::RBrace)?;
4227 Ok(node)
4228 }
4229
4230 fn parse_extension_members(&mut self) -> Result<Vec<ExtensionMember>, ParseError> {
4234 let mut members = Vec::new();
4235 self.consume(TokenType::LBracket)?;
4236 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4237 let name_tok = self.consume(TokenType::StringLit)?;
4238 let mut member = ExtensionMember {
4239 name: name_tok.value.clone(),
4240 semantics: None,
4241 default_confidence: None,
4242 loc: Loc {
4243 line: name_tok.line,
4244 column: name_tok.column,
4245 },
4246 };
4247 if self.check(TokenType::Colon) {
4249 self.advance();
4250 self.consume(TokenType::LBrace)?;
4251 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4252 let mkey = self.current().value.clone();
4253 self.advance();
4254 if self.check(TokenType::Colon) {
4255 self.advance();
4256 match mkey.as_str() {
4257 "semantics" => {
4258 member.semantics =
4259 Some(self.consume(TokenType::StringLit)?.value.clone())
4260 }
4261 "default_confidence" => {
4262 member.default_confidence = self.parse_optional_float()
4263 }
4264 _ => self.skip_value(),
4265 }
4266 }
4267 if self.check(TokenType::Comma) {
4268 self.advance();
4269 }
4270 }
4271 self.consume(TokenType::RBrace)?;
4272 }
4273 members.push(member);
4274 if self.check(TokenType::Comma) {
4275 self.advance();
4276 }
4277 }
4278 self.consume(TokenType::RBracket)?;
4279 Ok(members)
4280 }
4281
4282 fn parse_shield(&mut self) -> Result<ShieldDefinition, ParseError> {
4283 let tok = self.consume(TokenType::Shield)?;
4284 let name = self.consume(TokenType::Identifier)?.value;
4285 let mut node = ShieldDefinition {
4286 name,
4287 scan: Vec::new(),
4288 strategy: String::new(),
4289 on_breach: String::new(),
4290 severity: String::new(),
4291 quarantine: String::new(),
4292 max_retries: None,
4293 confidence_threshold: None,
4294 allow_tools: Vec::new(),
4295 deny_tools: Vec::new(),
4296 sandbox: None,
4297 redact: Vec::new(),
4298 log: String::new(),
4299 deflect_message: String::new(),
4300 taint: String::new(),
4301 compliance: Vec::new(),
4302 loc: Loc {
4303 line: tok.line,
4304 column: tok.column,
4305 },
4306 leading_trivia: Vec::new(),
4307 trailing_trivia: Vec::new(),
4308 };
4309 self.consume(TokenType::LBrace)?;
4310 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4311 let field_name = self.current().value.clone();
4312 self.advance();
4313 if self.check(TokenType::Colon) {
4314 self.advance();
4315 match field_name.as_str() {
4316 "scan" => node.scan = self.parse_bracketed_identifiers()?,
4317 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
4318 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
4319 "severity" => node.severity = self.consume_any_ident_or_kw()?.value.clone(),
4320 "quarantine" => {
4321 node.quarantine = self.consume(TokenType::StringLit)?.value.clone()
4322 }
4323 "max_retries" => node.max_retries = self.parse_optional_int(),
4324 "confidence_threshold" => {
4325 node.confidence_threshold = self.parse_optional_float()
4326 }
4327 "allow_tools" => node.allow_tools = self.parse_bracketed_identifiers()?,
4328 "deny_tools" => node.deny_tools = self.parse_bracketed_identifiers()?,
4329 "sandbox" => {
4330 node.sandbox = Some(self.consume_any_ident_or_kw()?.value == "true")
4331 }
4332 "redact" => node.redact = self.parse_bracketed_identifiers()?,
4333 "log" => node.log = self.consume_any_ident_or_kw()?.value.clone(),
4334 "deflect_message" => {
4335 node.deflect_message = self.consume(TokenType::StringLit)?.value.clone()
4336 }
4337 "taint" => node.taint = self.consume_any_ident_or_kw()?.value.clone(),
4338 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
4340 _ => self.skip_value(),
4341 }
4342 } else if self.check(TokenType::LBrace) {
4343 self.skip_braced_block()?;
4344 }
4345 }
4346 self.consume(TokenType::RBrace)?;
4347 Ok(node)
4348 }
4349
4350 fn parse_pix(&mut self) -> Result<PixDefinition, ParseError> {
4351 let tok = self.consume(TokenType::Pix)?;
4352 let name = self.consume(TokenType::Identifier)?.value;
4353 let mut node = PixDefinition {
4354 name,
4355 source: String::new(),
4356 depth: None,
4357 branching: None,
4358 model: String::new(),
4359 loc: Loc {
4360 line: tok.line,
4361 column: tok.column,
4362 },
4363 leading_trivia: Vec::new(),
4364 trailing_trivia: Vec::new(),
4365 };
4366 self.consume(TokenType::LBrace)?;
4367 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4368 let field_name = self.current().value.clone();
4369 self.advance();
4370 if self.check(TokenType::Colon) {
4371 self.advance();
4372 match field_name.as_str() {
4373 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
4374 "depth" => node.depth = self.parse_optional_int(),
4375 "branching" => node.branching = self.parse_optional_int(),
4376 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
4377 _ => self.skip_value(),
4378 }
4379 } else if self.check(TokenType::LBrace) {
4380 self.skip_braced_block()?;
4381 }
4382 }
4383 self.consume(TokenType::RBrace)?;
4384 Ok(node)
4385 }
4386
4387 fn parse_psyche(&mut self) -> Result<PsycheDefinition, ParseError> {
4388 let tok = self.consume(TokenType::Psyche)?;
4389 let name = self.consume(TokenType::Identifier)?.value;
4390 let mut node = PsycheDefinition {
4391 name,
4392 dimensions: Vec::new(),
4393 manifold_noise: None,
4394 manifold_momentum: None,
4395 safety_constraints: Vec::new(),
4396 quantum_enabled: None,
4397 inference_mode: String::new(),
4398 loc: Loc {
4399 line: tok.line,
4400 column: tok.column,
4401 },
4402 leading_trivia: Vec::new(),
4403 trailing_trivia: Vec::new(),
4404 };
4405 self.consume(TokenType::LBrace)?;
4406 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4407 let field_name = self.current().value.clone();
4408 self.advance();
4409 if self.check(TokenType::Colon) {
4410 self.advance();
4411 match field_name.as_str() {
4412 "dimensions" => node.dimensions = self.parse_bracketed_identifiers()?,
4413 "manifold_noise" => node.manifold_noise = self.parse_optional_float(),
4414 "manifold_momentum" => node.manifold_momentum = self.parse_optional_float(),
4415 "safety_constraints" => {
4416 node.safety_constraints = self.parse_bracketed_identifiers()?
4417 }
4418 "quantum_enabled" => {
4419 node.quantum_enabled = Some(self.consume_any_ident_or_kw()?.value == "true")
4420 }
4421 "inference_mode" => {
4422 node.inference_mode = self.consume_any_ident_or_kw()?.value.clone()
4423 }
4424 _ => self.skip_value(),
4425 }
4426 } else if self.check(TokenType::LBrace) {
4427 self.skip_braced_block()?;
4428 }
4429 }
4430 self.consume(TokenType::RBrace)?;
4431 Ok(node)
4432 }
4433
4434 fn parse_corpus(&mut self) -> Result<CorpusDefinition, ParseError> {
4435 let tok = self.consume(TokenType::Corpus)?;
4436 let name = self.consume(TokenType::Identifier)?.value;
4437 let mut node = CorpusDefinition {
4438 name,
4439 documents: Vec::new(),
4440 mcp_server: String::new(),
4441 mcp_resource_uri: String::new(),
4442 loc: Loc {
4443 line: tok.line,
4444 column: tok.column,
4445 },
4446 leading_trivia: Vec::new(),
4447 trailing_trivia: Vec::new(),
4448 };
4449 if self.check(TokenType::From) {
4451 self.advance();
4452 self.consume(TokenType::Mcp)?;
4453 self.consume(TokenType::LParen)?;
4454 node.mcp_server = self.consume(TokenType::StringLit)?.value.clone();
4455 self.consume(TokenType::Comma)?;
4456 node.mcp_resource_uri = self.consume(TokenType::StringLit)?.value.clone();
4457 self.consume(TokenType::RParen)?;
4458 return Ok(node);
4459 }
4460 self.consume(TokenType::LBrace)?;
4461 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4462 let field_name = self.current().value.clone();
4463 self.advance();
4464 if self.check(TokenType::Colon) {
4465 self.advance();
4466 match field_name.as_str() {
4467 "documents" => node.documents = self.parse_bracketed_identifiers()?,
4468 _ => self.skip_value(),
4469 }
4470 } else if self.check(TokenType::LBrace) {
4471 self.skip_braced_block()?;
4472 }
4473 }
4474 self.consume(TokenType::RBrace)?;
4475 Ok(node)
4476 }
4477
4478 fn parse_dataspace(&mut self) -> Result<DataspaceDefinition, ParseError> {
4479 let tok = self.consume(TokenType::Dataspace)?;
4480 let name = self.consume(TokenType::Identifier)?.value;
4481 let node = DataspaceDefinition {
4482 name,
4483 loc: Loc {
4484 line: tok.line,
4485 column: tok.column,
4486 },
4487 leading_trivia: Vec::new(),
4488 trailing_trivia: Vec::new(),
4489 };
4490 if self.check(TokenType::LBrace) {
4491 self.skip_braced_block()?;
4492 }
4493 Ok(node)
4494 }
4495
4496 fn parse_ots(&mut self) -> Result<OtsDefinition, ParseError> {
4497 let tok = self.consume(TokenType::Ots)?;
4498 let name = self.consume(TokenType::Identifier)?.value;
4499 let mut node = OtsDefinition {
4500 name,
4501 teleology: String::new(),
4502 homotopy_search: String::new(),
4503 loss_function: String::new(),
4504 loc: Loc {
4505 line: tok.line,
4506 column: tok.column,
4507 },
4508 leading_trivia: Vec::new(),
4509 trailing_trivia: Vec::new(),
4510 };
4511 if self.check(TokenType::Lt) {
4513 while !self.check(TokenType::Gt) && !self.check(TokenType::Eof) {
4514 self.advance();
4515 }
4516 if self.check(TokenType::Gt) {
4517 self.advance();
4518 }
4519 }
4520 self.consume(TokenType::LBrace)?;
4521 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4522 let field_name = self.current().value.clone();
4523 self.advance();
4524 if self.check(TokenType::Colon) {
4525 self.advance();
4526 match field_name.as_str() {
4527 "teleology" => {
4528 node.teleology = self.consume(TokenType::StringLit)?.value.clone()
4529 }
4530 "homotopy_search" => {
4531 node.homotopy_search = self.consume_any_ident_or_kw()?.value.clone()
4532 }
4533 "loss_function" => {
4534 node.loss_function = self.consume(TokenType::StringLit)?.value.clone()
4535 }
4536 _ => self.skip_value(),
4537 }
4538 } else if self.check(TokenType::LBrace) {
4539 self.skip_braced_block()?;
4540 }
4541 }
4542 self.consume(TokenType::RBrace)?;
4543 Ok(node)
4544 }
4545
4546 fn parse_mandate(&mut self) -> Result<MandateDefinition, ParseError> {
4547 let tok = self.consume(TokenType::Mandate)?;
4548 let name = self.consume(TokenType::Identifier)?.value;
4549 let mut node = MandateDefinition {
4550 name,
4551 constraint: String::new(),
4552 kp: None,
4553 ki: None,
4554 kd: None,
4555 tolerance: None,
4556 max_steps: None,
4557 on_violation: String::new(),
4558 loc: Loc {
4559 line: tok.line,
4560 column: tok.column,
4561 },
4562 leading_trivia: Vec::new(),
4563 trailing_trivia: Vec::new(),
4564 };
4565 self.consume(TokenType::LBrace)?;
4566 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4567 let field_name = self.current().value.clone();
4568 self.advance();
4569 if self.check(TokenType::Colon) {
4570 self.advance();
4571 match field_name.as_str() {
4572 "constraint" => {
4573 node.constraint = self.consume(TokenType::StringLit)?.value.clone()
4574 }
4575 "kp" | "Kp" => node.kp = self.parse_optional_float(),
4576 "ki" | "Ki" => node.ki = self.parse_optional_float(),
4577 "kd" | "Kd" => node.kd = self.parse_optional_float(),
4578 "tolerance" => node.tolerance = self.parse_optional_float(),
4579 "max_steps" => node.max_steps = self.parse_optional_int(),
4580 "on_violation" => {
4581 node.on_violation = self.consume_any_ident_or_kw()?.value.clone()
4582 }
4583 _ => self.skip_value(),
4584 }
4585 } else if self.check(TokenType::LBrace) {
4586 self.skip_braced_block()?;
4587 }
4588 }
4589 self.consume(TokenType::RBrace)?;
4590 Ok(node)
4591 }
4592
4593 fn parse_compute(&mut self) -> Result<ComputeDefinition, ParseError> {
4594 let tok = self.consume(TokenType::Compute)?;
4595 let name = self.consume(TokenType::Identifier)?.value;
4596 let mut node = ComputeDefinition {
4597 name,
4598 shield_ref: String::new(),
4599 loc: Loc {
4600 line: tok.line,
4601 column: tok.column,
4602 },
4603 leading_trivia: Vec::new(),
4604 trailing_trivia: Vec::new(),
4605 };
4606 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
4608 self.advance();
4609 }
4610 self.consume(TokenType::LBrace)?;
4611 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4612 let field_name = self.current().value.clone();
4613 self.advance();
4614 if self.check(TokenType::Colon) {
4615 self.advance();
4616 match field_name.as_str() {
4617 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
4618 _ => self.skip_value(),
4619 }
4620 } else if self.check(TokenType::LBrace) {
4621 self.skip_braced_block()?;
4622 }
4623 }
4624 self.consume(TokenType::RBrace)?;
4625 Ok(node)
4626 }
4627
4628 fn parse_daemon(&mut self) -> Result<DaemonDefinition, ParseError> {
4629 let tok = self.consume(TokenType::Daemon)?;
4630 let name = self.consume(TokenType::Identifier)?.value;
4631 let mut node = DaemonDefinition {
4632 name,
4633 goal: String::new(),
4634 tools: Vec::new(),
4635 memory_ref: String::new(),
4636 strategy: String::new(),
4637 on_stuck: String::new(),
4638 shield_ref: String::new(),
4639 max_tokens: None,
4640 max_time: String::new(),
4641 max_cost: None,
4642 listeners: Vec::new(),
4643 loc: Loc {
4644 line: tok.line,
4645 column: tok.column,
4646 },
4647 leading_trivia: Vec::new(),
4648 trailing_trivia: Vec::new(),
4649 };
4650 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
4652 self.advance();
4653 }
4654 self.consume(TokenType::LBrace)?;
4655 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4656 let field = self.current().clone();
4657 let field_name = field.value.clone();
4658 self.advance();
4659 if self.check(TokenType::Colon) {
4660 self.advance();
4661 match field_name.as_str() {
4662 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
4663 "tools" => node.tools = self.parse_bracketed_identifiers()?,
4664 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
4665 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
4666 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
4667 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
4668 "max_tokens" => node.max_tokens = self.parse_optional_int(),
4669 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
4670 "max_cost" => node.max_cost = self.parse_optional_float(),
4671 _ => self.skip_value(),
4672 }
4673 } else if field.ttype == TokenType::Listen {
4674 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
4680 (self.consume(TokenType::StringLit)?.value.clone(), false)
4681 } else {
4682 (self.consume_any_ident_or_kw()?.value.clone(), true)
4683 };
4684 let mut alias = String::new();
4685 if !self.at_declaration_start()
4686 && !self.check(TokenType::RBrace)
4687 && !self.check(TokenType::LBrace)
4688 {
4689 let next = self.current().clone();
4690 if next.value == "as" || next.ttype == TokenType::As {
4691 self.advance();
4692 alias = self.consume_any_ident_or_kw()?.value.clone();
4693 }
4694 }
4695 let listen_loc = Loc {
4696 line: field.line,
4697 column: field.column,
4698 };
4699 if self.check(TokenType::LBrace) {
4700 self.skip_braced_block()?;
4701 }
4702 node.listeners.push(ListenStep {
4703 channel,
4704 channel_is_ref,
4705 event_alias: alias,
4706 loc: listen_loc,
4707 });
4708 } else if self.check(TokenType::LBrace) {
4709 self.skip_braced_block()?;
4710 }
4711 }
4712 self.consume(TokenType::RBrace)?;
4713 Ok(node)
4714 }
4715
4716 fn parse_axonstore(&mut self) -> Result<AxonStoreDefinition, ParseError> {
4717 let tok = self.consume(TokenType::AxonStore)?;
4718 let name = self.consume(TokenType::Identifier)?.value;
4719 let mut node = AxonStoreDefinition {
4720 name,
4721 backend: String::new(),
4722 connection: String::new(),
4723 confidence_floor: None,
4724 isolation: String::new(),
4725 on_breach: String::new(),
4726 capability: String::new(),
4727 column_schema: None,
4728 loc: Loc {
4729 line: tok.line,
4730 column: tok.column,
4731 },
4732 leading_trivia: Vec::new(),
4733 trailing_trivia: Vec::new(),
4734 };
4735 self.consume(TokenType::LBrace)?;
4736 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4737 let field = self.current().clone();
4738 let field_name = field.value.clone();
4739 if field.ttype == TokenType::Schema {
4745 self.advance();
4746 let parsed = self.parse_store_schema_declaration(&node.name, field.line, field.column)?;
4747 node.column_schema = Some(parsed);
4748 continue;
4749 }
4750 self.advance();
4751 if self.check(TokenType::Colon) {
4752 self.advance();
4753 match field_name.as_str() {
4754 "backend" => node.backend = self.consume_any_ident_or_kw()?.value.clone(),
4755 "connection" => {
4756 node.connection = self.consume(TokenType::StringLit)?.value.clone()
4757 }
4758 "confidence_floor" => node.confidence_floor = self.parse_optional_float(),
4759 "isolation" => node.isolation = self.consume_any_ident_or_kw()?.value.clone(),
4760 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
4761 "capability" => {
4765 let slug_tok = self.consume(TokenType::StringLit)?.clone();
4766 if !is_valid_capability_slug(&slug_tok.value) {
4767 return Err(ParseError {
4768 message: format!(
4769 "Invalid capability slug '{}' in axonstore '{}' \
4770 `capability:`. Capability slugs must match \
4771 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
4772 lowercase identifiers starting with a letter. Examples: \
4773 `admin`, `tenant.read`, `hipaa.phi.read`.",
4774 slug_tok.value, node.name
4775 ),
4776 line: slug_tok.line,
4777 column: slug_tok.column,
4778 ..Default::default()
4779 });
4780 }
4781 node.capability = slug_tok.value.clone();
4782 }
4783 _ => self.skip_value(),
4784 }
4785 } else if self.check(TokenType::LBrace) {
4786 self.skip_braced_block()?;
4787 }
4788 }
4789 self.consume(TokenType::RBrace)?;
4790 Ok(node)
4791 }
4792
4793 fn parse_store_schema_declaration(
4805 &mut self,
4806 store_name: &str,
4807 sch_line: u32,
4808 sch_col: u32,
4809 ) -> Result<crate::store_schema::StoreColumnSchema, ParseError> {
4810 use crate::store_schema::{StoreColumn, StoreColumnSchema, StoreColumnType};
4811
4812 if self.check(TokenType::LBrace) {
4814 self.consume(TokenType::LBrace)?;
4815 let mut columns: Vec<StoreColumn> = Vec::new();
4816 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4817 let col_tok = self.current().clone();
4818 let col_name = self.consume_any_ident_or_kw()?.value.clone();
4819 self.consume(TokenType::Colon)?;
4820 let type_tok = self.consume_any_ident_or_kw()?.clone();
4821 let col_type = StoreColumnType::from_token(&type_tok.value).ok_or_else(|| {
4822 let names = StoreColumnType::all_canonical_names();
4823 let suggestion =
4824 crate::smart_suggest::suggest_for(&type_tok.value, &names);
4825 let suggest_suffix = if suggestion.is_empty() {
4826 String::new()
4827 } else {
4828 format!(" {suggestion}")
4829 };
4830 let known = names.join(", ");
4831 ParseError {
4832 message: format!(
4833 "Unknown column type `{}` for column `{}` in \
4834 axonstore `{}` `schema:` block. The closed \
4835 v1.38.0 column-type catalog (Fase 38.b D1) \
4836 is {{{known}}} (plus common lowercase \
4837 aliases — `int`/`integer`/`int4` for \
4838 `Int`, `bool`/`boolean` for `Bool`, etc.).\
4839 {suggest_suffix}",
4840 type_tok.value, col_name, store_name
4841 ),
4842 line: type_tok.line,
4843 column: type_tok.column,
4844 ..Default::default()
4845 }
4846 })?;
4847
4848 let mut col = StoreColumn {
4849 name: col_name,
4850 col_type,
4851 primary_key: false,
4852 auto_increment: false,
4853 not_null: false,
4854 unique: false,
4855 default_value: String::new(),
4856 identity: false,
4861 line: col_tok.line,
4862 column: col_tok.column,
4863 };
4864
4865 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4868 if self.current().ttype != TokenType::Identifier {
4869 break;
4872 }
4873 let constraint = self.current().value.clone();
4874 match constraint.as_str() {
4875 "primary_key" => {
4876 col.primary_key = true;
4877 self.advance();
4878 }
4879 "auto_increment" => {
4880 col.auto_increment = true;
4881 self.advance();
4882 }
4883 "not_null" => {
4884 col.not_null = true;
4885 self.advance();
4886 }
4887 "unique" => {
4888 col.unique = true;
4889 self.advance();
4890 }
4891 "identity" => {
4902 col.identity = true;
4903 self.advance();
4904 }
4905 "default" => {
4906 self.advance();
4907 let dv = self.current().clone();
4908 if matches!(
4909 dv.ttype,
4910 TokenType::StringLit
4911 | TokenType::Integer
4912 | TokenType::Float
4913 ) {
4914 col.default_value = dv.value.clone();
4915 self.advance();
4916 } else {
4917 col.default_value =
4918 self.consume_any_ident_or_kw()?.value.clone();
4919 }
4920 }
4921 _ => break,
4922 }
4923 }
4924
4925 columns.push(col);
4926 }
4927 self.consume(TokenType::RBrace)?;
4928 return Ok(StoreColumnSchema::Inline {
4929 columns,
4930 leading_trivia: Vec::new(),
4931 line: sch_line,
4932 column: sch_col,
4933 });
4934 }
4935
4936 if !self.check(TokenType::Colon) {
4938 let cur = self.current().clone();
4939 return Err(ParseError {
4940 message: format!(
4941 "axonstore `{store_name}` `schema:` declaration expects \
4942 `{{ … }}` (inline columns), `: \"manifest.ref\"` \
4943 (manifest reference), or `: env:VAR` (per-tenant schema \
4944 namespace). Got `{}` instead.",
4945 cur.value
4946 ),
4947 line: cur.line,
4948 column: cur.column,
4949 ..Default::default()
4950 });
4951 }
4952 self.consume(TokenType::Colon)?;
4953
4954 if self.check(TokenType::StringLit) {
4956 let lit = self.consume(TokenType::StringLit)?.clone();
4957 let value = lit.value.clone();
4958 if let Some(var) = value.strip_prefix("env:") {
4959 let var = var.trim();
4960 if var.is_empty() {
4961 return Err(ParseError {
4962 message: format!(
4963 "axonstore `{store_name}` `schema: \"env:\"` is \
4964 missing the variable name after the `env:` \
4965 prefix."
4966 ),
4967 line: lit.line,
4968 column: lit.column,
4969 ..Default::default()
4970 });
4971 }
4972 return Ok(StoreColumnSchema::EnvVar {
4973 var_name: var.to_string(),
4974 line: sch_line,
4975 column: sch_col,
4976 });
4977 }
4978 if value.trim().is_empty() {
4980 return Err(ParseError {
4981 message: format!(
4982 "axonstore `{store_name}` `schema:` manifest reference \
4983 is empty. Expected `\"qualified.name\"` — e.g. \
4984 `\"public.tenants\"`."
4985 ),
4986 line: lit.line,
4987 column: lit.column,
4988 ..Default::default()
4989 });
4990 }
4991 return Ok(StoreColumnSchema::ManifestRef {
4992 qualified_name: value,
4993 line: sch_line,
4994 column: sch_col,
4995 });
4996 }
4997
4998 let env_tok = self.current().clone();
5001 if env_tok.value == "env" {
5002 self.advance();
5003 if !self.check(TokenType::Colon) {
5004 return Err(ParseError {
5005 message: format!(
5006 "axonstore `{store_name}` `schema: env` is missing the \
5007 `:` separator. Expected `schema: env:VAR`."
5008 ),
5009 line: env_tok.line,
5010 column: env_tok.column,
5011 ..Default::default()
5012 });
5013 }
5014 self.advance(); let var_tok = self.consume_any_ident_or_kw()?.clone();
5016 if var_tok.value.trim().is_empty() {
5017 return Err(ParseError {
5018 message: format!(
5019 "axonstore `{store_name}` `schema: env:` is missing \
5020 the variable name."
5021 ),
5022 line: var_tok.line,
5023 column: var_tok.column,
5024 ..Default::default()
5025 });
5026 }
5027 return Ok(StoreColumnSchema::EnvVar {
5028 var_name: var_tok.value.clone(),
5029 line: sch_line,
5030 column: sch_col,
5031 });
5032 }
5033
5034 Err(ParseError {
5035 message: format!(
5036 "axonstore `{store_name}` `schema:` declaration expects \
5037 `{{ … }}` (inline columns), `\"manifest.ref\"` (manifest \
5038 reference), or `env:VAR` (per-tenant schema namespace). \
5039 Got `{}` instead.",
5040 env_tok.value
5041 ),
5042 line: env_tok.line,
5043 column: env_tok.column,
5044 ..Default::default()
5045 })
5046 }
5047
5048 fn parse_resource(&mut self) -> Result<ResourceDefinition, ParseError> {
5055 let tok = self.consume(TokenType::Resource)?;
5056 let name = self.consume(TokenType::Identifier)?.value;
5057 let mut node = ResourceDefinition {
5058 name,
5059 kind: String::new(),
5060 endpoint: String::new(),
5061 capacity: None,
5062 lifetime: "affine".to_string(),
5063 certainty_floor: None,
5064 shield_ref: String::new(),
5065 loc: Loc {
5066 line: tok.line,
5067 column: tok.column,
5068 },
5069 leading_trivia: Vec::new(),
5070 trailing_trivia: Vec::new(),
5071 };
5072 self.consume(TokenType::LBrace)?;
5073 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5074 let field_tok = self.current().clone();
5075 let field_name = field_tok.value.clone();
5076 self.advance();
5077 if !self.check(TokenType::Colon) {
5078 if self.check(TokenType::LBrace) {
5080 self.skip_braced_block()?;
5081 }
5082 continue;
5083 }
5084 self.advance(); match field_name.as_str() {
5086 "kind" => node.kind = self.consume_any_ident_or_kw()?.value,
5087 "endpoint" => node.endpoint = self.consume(TokenType::StringLit)?.value,
5088 "capacity" => {
5089 node.capacity = self.parse_optional_int();
5090 }
5091 "lifetime" => {
5092 let lt_tok = self.consume_any_ident_or_kw()?;
5093 let lt = lt_tok.value;
5094 if !matches!(lt.as_str(), "linear" | "affine" | "persistent") {
5095 return Err(ParseError {
5096 message: format!(
5097 "Invalid lifetime '{lt}' in resource '{}' — \
5098 expected linear | affine | persistent",
5099 node.name
5100 ),
5101 line: lt_tok.line,
5102 column: lt_tok.column,
5103 ..Default::default()
5104 });
5105 }
5106 node.lifetime = lt;
5107 }
5108 "certainty_floor" => {
5109 node.certainty_floor = self.parse_optional_float();
5110 }
5111 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
5112 _ => self.skip_value(),
5113 }
5114 }
5115 self.consume(TokenType::RBrace)?;
5116 Ok(node)
5117 }
5118
5119 fn parse_fabric(&mut self) -> Result<FabricDefinition, ParseError> {
5121 let tok = self.consume(TokenType::Fabric)?;
5122 let name = self.consume(TokenType::Identifier)?.value;
5123 let mut node = FabricDefinition {
5124 name,
5125 provider: String::new(),
5126 region: String::new(),
5127 zones: None,
5128 ephemeral: None,
5129 shield_ref: String::new(),
5130 loc: Loc {
5131 line: tok.line,
5132 column: tok.column,
5133 },
5134 leading_trivia: Vec::new(),
5135 trailing_trivia: Vec::new(),
5136 };
5137 self.consume(TokenType::LBrace)?;
5138 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5139 let field_name = self.current().value.clone();
5140 self.advance();
5141 if !self.check(TokenType::Colon) {
5142 if self.check(TokenType::LBrace) {
5143 self.skip_braced_block()?;
5144 }
5145 continue;
5146 }
5147 self.advance(); match field_name.as_str() {
5149 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
5150 "region" => node.region = self.consume(TokenType::StringLit)?.value,
5151 "zones" => node.zones = self.parse_optional_int(),
5152 "ephemeral" => {
5153 let b = self.parse_bool()?;
5154 node.ephemeral = Some(b);
5155 }
5156 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
5157 _ => self.skip_value(),
5158 }
5159 }
5160 self.consume(TokenType::RBrace)?;
5161 Ok(node)
5162 }
5163
5164 fn parse_manifest(&mut self) -> Result<ManifestDefinition, ParseError> {
5166 let tok = self.consume(TokenType::Manifest)?;
5167 let name = self.consume(TokenType::Identifier)?.value;
5168 let mut node = ManifestDefinition {
5169 name,
5170 resources: Vec::new(),
5171 fabric_ref: String::new(),
5172 region: String::new(),
5173 zones: None,
5174 compliance: Vec::new(),
5175 loc: Loc {
5176 line: tok.line,
5177 column: tok.column,
5178 },
5179 leading_trivia: Vec::new(),
5180 trailing_trivia: Vec::new(),
5181 };
5182 self.consume(TokenType::LBrace)?;
5183 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5184 let field_name = self.current().value.clone();
5185 self.advance();
5186 if !self.check(TokenType::Colon) {
5187 if self.check(TokenType::LBrace) {
5188 self.skip_braced_block()?;
5189 }
5190 continue;
5191 }
5192 self.advance();
5193 match field_name.as_str() {
5194 "resources" => node.resources = self.parse_bracketed_identifiers()?,
5195 "fabric" => node.fabric_ref = self.consume_any_ident_or_kw()?.value,
5196 "region" => node.region = self.consume(TokenType::StringLit)?.value,
5197 "zones" => node.zones = self.parse_optional_int(),
5198 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
5199 _ => self.skip_value(),
5200 }
5201 }
5202 self.consume(TokenType::RBrace)?;
5203 Ok(node)
5204 }
5205
5206 fn parse_observe(&mut self) -> Result<ObserveDefinition, ParseError> {
5208 let tok = self.consume(TokenType::Observe)?;
5209 let name = self.consume(TokenType::Identifier)?.value;
5210 self.consume(TokenType::From)?;
5212 let target = self.consume(TokenType::Identifier)?.value;
5213 let mut node = ObserveDefinition {
5214 name,
5215 target,
5216 sources: Vec::new(),
5217 quorum: None,
5218 timeout: String::new(),
5219 on_partition: "fail".to_string(),
5220 certainty_floor: None,
5221 loc: Loc {
5222 line: tok.line,
5223 column: tok.column,
5224 },
5225 leading_trivia: Vec::new(),
5226 trailing_trivia: Vec::new(),
5227 };
5228 self.consume(TokenType::LBrace)?;
5229 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5230 let field_name = self.current().value.clone();
5231 self.advance();
5232 if !self.check(TokenType::Colon) {
5233 if self.check(TokenType::LBrace) {
5234 self.skip_braced_block()?;
5235 }
5236 continue;
5237 }
5238 self.advance();
5239 match field_name.as_str() {
5240 "sources" => node.sources = self.parse_bracketed_identifiers()?,
5241 "quorum" => node.quorum = self.parse_optional_int(),
5242 "timeout" => {
5243 let t = self.current().clone();
5244 match t.ttype {
5245 TokenType::Duration | TokenType::StringLit => {
5246 self.advance();
5247 node.timeout = t.value;
5248 }
5249 _ => node.timeout = self.consume_any_ident_or_kw()?.value,
5250 }
5251 }
5252 "on_partition" => {
5253 let p_tok = self.consume_any_ident_or_kw()?;
5254 let p = p_tok.value;
5255 if !matches!(p.as_str(), "fail" | "shield_quarantine") {
5256 return Err(ParseError {
5257 message: format!(
5258 "Invalid on_partition '{p}' in observe '{}' — \
5259 expected fail | shield_quarantine",
5260 node.name
5261 ),
5262 line: p_tok.line,
5263 column: p_tok.column,
5264 ..Default::default()
5265 });
5266 }
5267 node.on_partition = p;
5268 }
5269 "certainty_floor" => node.certainty_floor = self.parse_optional_float(),
5270 _ => self.skip_value(),
5271 }
5272 }
5273 self.consume(TokenType::RBrace)?;
5274 Ok(node)
5275 }
5276
5277 fn parse_reconcile(&mut self) -> Result<ReconcileDefinition, ParseError> {
5281 let tok = self.consume(TokenType::Reconcile)?;
5282 let name = self.consume(TokenType::Identifier)?.value;
5283 let mut node = ReconcileDefinition {
5284 name,
5285 observe_ref: String::new(),
5286 threshold: None,
5287 tolerance: None,
5288 on_drift: "provision".to_string(),
5289 shield_ref: String::new(),
5290 mandate_ref: String::new(),
5291 max_retries: 3,
5292 loc: Loc {
5293 line: tok.line,
5294 column: tok.column,
5295 },
5296 leading_trivia: Vec::new(),
5297 trailing_trivia: Vec::new(),
5298 };
5299 self.consume(TokenType::LBrace)?;
5300 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5301 let field_name = self.current().value.clone();
5302 self.advance();
5303 if !self.check(TokenType::Colon) {
5304 if self.check(TokenType::LBrace) {
5305 self.skip_braced_block()?;
5306 }
5307 continue;
5308 }
5309 self.advance();
5310 match field_name.as_str() {
5311 "observe" => node.observe_ref = self.consume_any_ident_or_kw()?.value,
5312 "threshold" => node.threshold = self.parse_optional_float(),
5313 "tolerance" => node.tolerance = self.parse_optional_float(),
5314 "on_drift" => {
5315 let d_tok = self.consume_any_ident_or_kw()?;
5316 let d = d_tok.value;
5317 if !matches!(d.as_str(), "provision" | "alert" | "refine") {
5318 return Err(ParseError {
5319 message: format!(
5320 "Invalid on_drift '{d}' in reconcile '{}' — \
5321 expected provision | alert | refine",
5322 node.name
5323 ),
5324 line: d_tok.line,
5325 column: d_tok.column,
5326 ..Default::default()
5327 });
5328 }
5329 node.on_drift = d;
5330 }
5331 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
5332 "mandate" => node.mandate_ref = self.consume_any_ident_or_kw()?.value,
5333 "max_retries" => {
5334 if let Some(v) = self.parse_optional_int() {
5335 node.max_retries = v;
5336 }
5337 }
5338 _ => self.skip_value(),
5339 }
5340 }
5341 self.consume(TokenType::RBrace)?;
5342 Ok(node)
5343 }
5344
5345 fn parse_lease(&mut self) -> Result<LeaseDefinition, ParseError> {
5347 let tok = self.consume(TokenType::Lease)?;
5348 let name = self.consume(TokenType::Identifier)?.value;
5349 let mut node = LeaseDefinition {
5350 name,
5351 resource_ref: String::new(),
5352 duration: String::new(),
5353 acquire: "on_start".to_string(),
5354 on_expire: "anchor_breach".to_string(),
5355 loc: Loc {
5356 line: tok.line,
5357 column: tok.column,
5358 },
5359 leading_trivia: Vec::new(),
5360 trailing_trivia: Vec::new(),
5361 };
5362 self.consume(TokenType::LBrace)?;
5363 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5364 let field_name = self.current().value.clone();
5365 self.advance();
5366 if !self.check(TokenType::Colon) {
5367 if self.check(TokenType::LBrace) {
5368 self.skip_braced_block()?;
5369 }
5370 continue;
5371 }
5372 self.advance();
5373 match field_name.as_str() {
5374 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
5375 "duration" => {
5376 let t = self.current().clone();
5377 match t.ttype {
5378 TokenType::Duration | TokenType::StringLit => {
5379 self.advance();
5380 node.duration = t.value;
5381 }
5382 _ => node.duration = self.consume_any_ident_or_kw()?.value,
5383 }
5384 }
5385 "acquire" => {
5386 let a_tok = self.consume_any_ident_or_kw()?;
5387 let a = a_tok.value;
5388 if !matches!(a.as_str(), "on_start" | "on_demand") {
5389 return Err(ParseError {
5390 message: format!(
5391 "Invalid acquire '{a}' in lease '{}' — \
5392 expected on_start | on_demand",
5393 node.name
5394 ),
5395 line: a_tok.line,
5396 column: a_tok.column,
5397 ..Default::default()
5398 });
5399 }
5400 node.acquire = a;
5401 }
5402 "on_expire" => {
5403 let e_tok = self.consume_any_ident_or_kw()?;
5404 let e = e_tok.value;
5405 if !matches!(e.as_str(), "anchor_breach" | "release" | "extend") {
5406 return Err(ParseError {
5407 message: format!(
5408 "Invalid on_expire '{e}' in lease '{}' — \
5409 expected anchor_breach | release | extend",
5410 node.name
5411 ),
5412 line: e_tok.line,
5413 column: e_tok.column,
5414 ..Default::default()
5415 });
5416 }
5417 node.on_expire = e;
5418 }
5419 _ => self.skip_value(),
5420 }
5421 }
5422 self.consume(TokenType::RBrace)?;
5423 Ok(node)
5424 }
5425
5426 fn parse_ensemble(&mut self) -> Result<EnsembleDefinition, ParseError> {
5428 let tok = self.consume(TokenType::Ensemble)?;
5429 let name = self.consume(TokenType::Identifier)?.value;
5430 let mut node = EnsembleDefinition {
5431 name,
5432 observations: Vec::new(),
5433 quorum: None,
5434 aggregation: "majority".to_string(),
5435 certainty_mode: "min".to_string(),
5436 loc: Loc {
5437 line: tok.line,
5438 column: tok.column,
5439 },
5440 leading_trivia: Vec::new(),
5441 trailing_trivia: Vec::new(),
5442 };
5443 self.consume(TokenType::LBrace)?;
5444 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5445 let field_name = self.current().value.clone();
5446 self.advance();
5447 if !self.check(TokenType::Colon) {
5448 if self.check(TokenType::LBrace) {
5449 self.skip_braced_block()?;
5450 }
5451 continue;
5452 }
5453 self.advance();
5454 match field_name.as_str() {
5455 "observations" => node.observations = self.parse_bracketed_identifiers()?,
5456 "quorum" => node.quorum = self.parse_optional_int(),
5457 "aggregation" => {
5458 let a_tok = self.consume_any_ident_or_kw()?;
5459 let a = a_tok.value;
5460 if !matches!(a.as_str(), "majority" | "weighted" | "byzantine") {
5461 return Err(ParseError {
5462 message: format!(
5463 "Invalid aggregation '{a}' in ensemble '{}' — \
5464 expected majority | weighted | byzantine",
5465 node.name
5466 ),
5467 line: a_tok.line,
5468 column: a_tok.column,
5469 ..Default::default()
5470 });
5471 }
5472 node.aggregation = a;
5473 }
5474 "certainty_mode" => {
5475 let c_tok = self.consume_any_ident_or_kw()?;
5476 let c = c_tok.value;
5477 if !matches!(c.as_str(), "min" | "weighted" | "harmonic") {
5478 return Err(ParseError {
5479 message: format!(
5480 "Invalid certainty_mode '{c}' in ensemble '{}' — \
5481 expected min | weighted | harmonic",
5482 node.name
5483 ),
5484 line: c_tok.line,
5485 column: c_tok.column,
5486 ..Default::default()
5487 });
5488 }
5489 node.certainty_mode = c;
5490 }
5491 _ => self.skip_value(),
5492 }
5493 }
5494 self.consume(TokenType::RBrace)?;
5495 Ok(node)
5496 }
5497
5498 fn parse_session_definition(&mut self) -> Result<SessionDefinition, ParseError> {
5506 let tok = self.consume(TokenType::Session)?;
5507 let name = self.consume(TokenType::Identifier)?.value;
5508 let mut node = SessionDefinition {
5509 name,
5510 roles: Vec::new(),
5511 loc: Loc {
5512 line: tok.line,
5513 column: tok.column,
5514 },
5515 leading_trivia: Vec::new(),
5516 trailing_trivia: Vec::new(),
5517 };
5518 self.consume(TokenType::LBrace)?;
5519 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5520 let role_tok = self.consume_any_ident_or_kw()?;
5521 self.consume(TokenType::Colon)?;
5522 let steps = self.parse_session_steps()?;
5523 node.roles.push(SessionRole {
5524 name: role_tok.value,
5525 steps,
5526 loc: Loc {
5527 line: role_tok.line,
5528 column: role_tok.column,
5529 },
5530 });
5531 }
5532 self.consume(TokenType::RBrace)?;
5533 Ok(node)
5534 }
5535
5536 fn parse_socket(&mut self) -> Result<SocketDefinition, ParseError> {
5541 let tok = self.consume(TokenType::Socket)?;
5542 let name = self.consume(TokenType::Identifier)?.value;
5543 let mut node = SocketDefinition {
5544 name,
5545 loc: Loc { line: tok.line, column: tok.column },
5546 ..Default::default()
5547 };
5548 self.consume(TokenType::LBrace)?;
5549 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5550 let key = self.consume_any_ident_or_kw()?.value;
5551 self.consume(TokenType::Colon)?;
5552 match key.as_str() {
5553 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
5554 "backpressure" => {
5555 let kind = self.consume_any_ident_or_kw()?.value;
5557 if kind != "credit" {
5558 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
5559 }
5560 self.consume(TokenType::LParen)?;
5561 let n = self
5562 .consume(TokenType::Integer)?
5563 .value
5564 .parse::<i64>()
5565 .map_err(|_| self.error("backpressure credit must be an integer"))?;
5566 self.consume(TokenType::RParen)?;
5567 node.backpressure_credit = Some(n);
5568 }
5569 "reconnect" => {
5570 let mode = self.consume_any_ident_or_kw()?.value;
5571 node.reconnect = mode == "cognitive_state";
5572 }
5573 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
5574 other => return Err(self.error(&format!("unknown socket field `{other}`"))),
5575 }
5576 if self.check(TokenType::Comma) {
5578 self.consume(TokenType::Comma)?;
5579 }
5580 }
5581 self.consume(TokenType::RBrace)?;
5582 Ok(node)
5583 }
5584
5585 fn parse_session_steps(&mut self) -> Result<Vec<SessionStep>, ParseError> {
5587 self.consume(TokenType::LBracket)?;
5588 let mut steps = Vec::new();
5589 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5590 steps.push(self.parse_session_step()?);
5591 if self.check(TokenType::Comma) {
5592 self.advance();
5593 }
5594 }
5595 self.consume(TokenType::RBracket)?;
5596 Ok(steps)
5597 }
5598
5599 fn parse_session_step(&mut self) -> Result<SessionStep, ParseError> {
5600 let tok = self.current().clone();
5601 let loc = Loc { line: tok.line, column: tok.column };
5602 match tok.ttype {
5603 TokenType::Send => {
5604 self.advance();
5605 let msg = self.consume_any_ident_or_kw()?;
5606 Ok(SessionStep { op: "send".into(), message_type: msg.value, loc, ..Default::default() })
5607 }
5608 TokenType::Receive => {
5609 self.advance();
5610 let msg = self.consume_any_ident_or_kw()?;
5611 Ok(SessionStep { op: "receive".into(), message_type: msg.value, loc, ..Default::default() })
5612 }
5613 TokenType::Loop => {
5614 self.advance();
5615 Ok(SessionStep { op: "loop".into(), loc, ..Default::default() })
5616 }
5617 TokenType::End => {
5618 self.advance();
5619 Ok(SessionStep { op: "end".into(), loc, ..Default::default() })
5620 }
5621 TokenType::Identifier if tok.value == "select" || tok.value == "branch" => {
5624 self.parse_session_choice(&tok.value, loc)
5625 }
5626 _ => Err(ParseError {
5627 message: format!(
5628 "Invalid session step '{}' — expected send | receive | loop | end | select | branch",
5629 tok.value
5630 ),
5631 line: tok.line,
5632 column: tok.column,
5633 ..Default::default()
5634 }),
5635 }
5636 }
5637
5638 fn parse_session_choice(&mut self, op: &str, loc: Loc) -> Result<SessionStep, ParseError> {
5641 self.advance(); self.consume(TokenType::LBrace)?;
5643 let mut branches = Vec::new();
5644 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5645 let label_tok = self.consume_any_ident_or_kw()?;
5646 self.consume(TokenType::Colon)?;
5647 let steps = self.parse_session_steps()?;
5648 branches.push(SessionBranch {
5649 label: label_tok.value,
5650 steps,
5651 loc: Loc { line: label_tok.line, column: label_tok.column },
5652 });
5653 if self.check(TokenType::Comma) {
5654 self.advance();
5655 }
5656 }
5657 self.consume(TokenType::RBrace)?;
5658 Ok(SessionStep { op: op.to_string(), branches, loc, ..Default::default() })
5659 }
5660
5661 fn parse_topology(&mut self) -> Result<TopologyDefinition, ParseError> {
5663 let tok = self.consume(TokenType::Topology)?;
5664 let name = self.consume(TokenType::Identifier)?.value;
5665 let mut node = TopologyDefinition {
5666 name,
5667 nodes: Vec::new(),
5668 edges: Vec::new(),
5669 loc: Loc {
5670 line: tok.line,
5671 column: tok.column,
5672 },
5673 leading_trivia: Vec::new(),
5674 trailing_trivia: Vec::new(),
5675 };
5676 self.consume(TokenType::LBrace)?;
5677 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5678 let field_name = self.current().value.clone();
5679 self.advance();
5680 if !self.check(TokenType::Colon) {
5681 if self.check(TokenType::LBrace) {
5682 self.skip_braced_block()?;
5683 }
5684 continue;
5685 }
5686 self.advance();
5687 match field_name.as_str() {
5688 "nodes" => node.nodes = self.parse_bracketed_identifiers()?,
5689 "edges" => node.edges = self.parse_topology_edges()?,
5690 _ => self.skip_value(),
5691 }
5692 }
5693 self.consume(TokenType::RBrace)?;
5694 Ok(node)
5695 }
5696
5697 fn parse_topology_edges(&mut self) -> Result<Vec<TopologyEdge>, ParseError> {
5698 self.consume(TokenType::LBracket)?;
5699 let mut edges = Vec::new();
5700 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5701 edges.push(self.parse_topology_edge()?);
5702 if self.check(TokenType::Comma) {
5703 self.advance();
5704 }
5705 }
5706 self.consume(TokenType::RBracket)?;
5707 Ok(edges)
5708 }
5709
5710 fn parse_topology_edge(&mut self) -> Result<TopologyEdge, ParseError> {
5711 let src_tok = self.consume_any_ident_or_kw()?;
5712 self.consume(TokenType::Arrow)?;
5713 let tgt_tok = self.consume_any_ident_or_kw()?;
5714 self.consume(TokenType::Colon)?;
5715 let sess_tok = self.consume_any_ident_or_kw()?;
5716 Ok(TopologyEdge {
5717 source: src_tok.value,
5718 target: tgt_tok.value,
5719 session_ref: sess_tok.value,
5720 loc: Loc {
5721 line: src_tok.line,
5722 column: src_tok.column,
5723 },
5724 })
5725 }
5726
5727 fn parse_immune(&mut self) -> Result<ImmuneDefinition, ParseError> {
5731 let tok = self.consume(TokenType::Immune)?;
5732 let name = self.consume(TokenType::Identifier)?.value;
5733 let mut node = ImmuneDefinition {
5734 name,
5735 watch: Vec::new(),
5736 sensitivity: None,
5737 baseline: "learned".to_string(),
5738 window: 100,
5739 scope: String::new(),
5740 tau: String::new(),
5741 decay: "exponential".to_string(),
5742 loc: Loc {
5743 line: tok.line,
5744 column: tok.column,
5745 },
5746 leading_trivia: Vec::new(),
5747 trailing_trivia: Vec::new(),
5748 };
5749 self.consume(TokenType::LBrace)?;
5750 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5751 let field_name = self.current().value.clone();
5752 self.advance();
5753 if !self.check(TokenType::Colon) {
5754 if self.check(TokenType::LBrace) {
5755 self.skip_braced_block()?;
5756 }
5757 continue;
5758 }
5759 self.advance();
5760 match field_name.as_str() {
5761 "watch" => node.watch = self.parse_bracketed_identifiers()?,
5762 "sensitivity" => node.sensitivity = self.parse_optional_float(),
5763 "baseline" => node.baseline = self.consume_any_ident_or_kw()?.value,
5764 "window" => {
5765 if let Some(v) = self.parse_optional_int() {
5766 node.window = v;
5767 }
5768 }
5769 "scope" => {
5770 let s_tok = self.consume_any_ident_or_kw()?;
5771 let s = s_tok.value;
5772 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
5773 return Err(ParseError {
5774 message: format!(
5775 "Invalid scope '{s}' in immune '{}' — \
5776 expected tenant | flow | global",
5777 node.name
5778 ),
5779 line: s_tok.line,
5780 column: s_tok.column,
5781 ..Default::default()
5782 });
5783 }
5784 node.scope = s;
5785 }
5786 "tau" => {
5787 let t = self.current().clone();
5788 match t.ttype {
5789 TokenType::Duration | TokenType::StringLit => {
5790 self.advance();
5791 node.tau = t.value;
5792 }
5793 _ => node.tau = self.consume_any_ident_or_kw()?.value,
5794 }
5795 }
5796 "decay" => {
5797 let d_tok = self.consume_any_ident_or_kw()?;
5798 let d = d_tok.value;
5799 if !matches!(d.as_str(), "exponential" | "linear" | "none") {
5800 return Err(ParseError {
5801 message: format!(
5802 "Invalid decay '{d}' in immune '{}' — \
5803 expected exponential | linear | none",
5804 node.name
5805 ),
5806 line: d_tok.line,
5807 column: d_tok.column,
5808 ..Default::default()
5809 });
5810 }
5811 node.decay = d;
5812 }
5813 _ => self.skip_value(),
5814 }
5815 }
5816 self.consume(TokenType::RBrace)?;
5817 Ok(node)
5818 }
5819
5820 fn parse_reflex(&mut self) -> Result<ReflexDefinition, ParseError> {
5822 let tok = self.consume(TokenType::Reflex)?;
5823 let name = self.consume(TokenType::Identifier)?.value;
5824 let mut node = ReflexDefinition {
5825 name,
5826 trigger: String::new(),
5827 on_level: "doubt".to_string(),
5828 action: String::new(),
5829 scope: String::new(),
5830 sla: String::new(),
5831 loc: Loc {
5832 line: tok.line,
5833 column: tok.column,
5834 },
5835 leading_trivia: Vec::new(),
5836 trailing_trivia: Vec::new(),
5837 };
5838 self.consume(TokenType::LBrace)?;
5839 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5840 let field_name = self.current().value.clone();
5841 self.advance();
5842 if !self.check(TokenType::Colon) {
5843 if self.check(TokenType::LBrace) {
5844 self.skip_braced_block()?;
5845 }
5846 continue;
5847 }
5848 self.advance();
5849 match field_name.as_str() {
5850 "trigger" => node.trigger = self.consume_any_ident_or_kw()?.value,
5851 "on_level" => {
5852 let l_tok = self.consume_any_ident_or_kw()?;
5853 let l = l_tok.value;
5854 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
5855 return Err(ParseError {
5856 message: format!(
5857 "Invalid on_level '{l}' in reflex '{}' — \
5858 expected know | believe | speculate | doubt",
5859 node.name
5860 ),
5861 line: l_tok.line,
5862 column: l_tok.column,
5863 ..Default::default()
5864 });
5865 }
5866 node.on_level = l;
5867 }
5868 "action" => {
5869 let a_tok = self.consume_any_ident_or_kw()?;
5870 let a = a_tok.value;
5871 if !matches!(
5872 a.as_str(),
5873 "drop"
5874 | "revoke"
5875 | "emit"
5876 | "redact"
5877 | "quarantine"
5878 | "terminate"
5879 | "alert"
5880 ) {
5881 return Err(ParseError {
5882 message: format!(
5883 "Invalid action '{a}' in reflex '{}' — \
5884 expected drop | revoke | emit | redact | \
5885 quarantine | terminate | alert",
5886 node.name
5887 ),
5888 line: a_tok.line,
5889 column: a_tok.column,
5890 ..Default::default()
5891 });
5892 }
5893 node.action = a;
5894 }
5895 "scope" => {
5896 let s_tok = self.consume_any_ident_or_kw()?;
5897 let s = s_tok.value;
5898 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
5899 return Err(ParseError {
5900 message: format!(
5901 "Invalid scope '{s}' in reflex '{}' — \
5902 expected tenant | flow | global",
5903 node.name
5904 ),
5905 line: s_tok.line,
5906 column: s_tok.column,
5907 ..Default::default()
5908 });
5909 }
5910 node.scope = s;
5911 }
5912 "sla" => {
5913 let t = self.current().clone();
5914 match t.ttype {
5915 TokenType::Duration | TokenType::StringLit => {
5916 self.advance();
5917 node.sla = t.value;
5918 }
5919 _ => node.sla = self.consume_any_ident_or_kw()?.value,
5920 }
5921 }
5922 _ => self.skip_value(),
5923 }
5924 }
5925 self.consume(TokenType::RBrace)?;
5926 Ok(node)
5927 }
5928
5929 fn parse_heal(&mut self) -> Result<HealDefinition, ParseError> {
5931 let tok = self.consume(TokenType::Heal)?;
5932 let name = self.consume(TokenType::Identifier)?.value;
5933 let mut node = HealDefinition {
5934 name,
5935 source: String::new(),
5936 on_level: "doubt".to_string(),
5937 mode: "human_in_loop".to_string(),
5938 scope: String::new(),
5939 review_sla: String::new(),
5940 shield_ref: String::new(),
5941 max_patches: 3,
5942 loc: Loc {
5943 line: tok.line,
5944 column: tok.column,
5945 },
5946 leading_trivia: Vec::new(),
5947 trailing_trivia: Vec::new(),
5948 };
5949 self.consume(TokenType::LBrace)?;
5950 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5951 let field_name = self.current().value.clone();
5952 self.advance();
5953 if !self.check(TokenType::Colon) {
5954 if self.check(TokenType::LBrace) {
5955 self.skip_braced_block()?;
5956 }
5957 continue;
5958 }
5959 self.advance();
5960 match field_name.as_str() {
5961 "source" => node.source = self.consume_any_ident_or_kw()?.value,
5962 "on_level" => {
5963 let l_tok = self.consume_any_ident_or_kw()?;
5964 let l = l_tok.value;
5965 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
5966 return Err(ParseError {
5967 message: format!(
5968 "Invalid on_level '{l}' in heal '{}' — \
5969 expected know | believe | speculate | doubt",
5970 node.name
5971 ),
5972 line: l_tok.line,
5973 column: l_tok.column,
5974 ..Default::default()
5975 });
5976 }
5977 node.on_level = l;
5978 }
5979 "mode" => {
5980 let m_tok = self.consume_any_ident_or_kw()?;
5981 let m = m_tok.value;
5982 if !matches!(m.as_str(), "audit_only" | "human_in_loop" | "adversarial") {
5983 return Err(ParseError {
5984 message: format!(
5985 "Invalid mode '{m}' in heal '{}' — \
5986 expected audit_only | human_in_loop | adversarial",
5987 node.name
5988 ),
5989 line: m_tok.line,
5990 column: m_tok.column,
5991 ..Default::default()
5992 });
5993 }
5994 node.mode = m;
5995 }
5996 "scope" => {
5997 let s_tok = self.consume_any_ident_or_kw()?;
5998 let s = s_tok.value;
5999 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
6000 return Err(ParseError {
6001 message: format!(
6002 "Invalid scope '{s}' in heal '{}' — \
6003 expected tenant | flow | global",
6004 node.name
6005 ),
6006 line: s_tok.line,
6007 column: s_tok.column,
6008 ..Default::default()
6009 });
6010 }
6011 node.scope = s;
6012 }
6013 "review_sla" => {
6014 let t = self.current().clone();
6015 match t.ttype {
6016 TokenType::Duration | TokenType::StringLit => {
6017 self.advance();
6018 node.review_sla = t.value;
6019 }
6020 _ => node.review_sla = self.consume_any_ident_or_kw()?.value,
6021 }
6022 }
6023 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
6024 "max_patches" => {
6025 if let Some(v) = self.parse_optional_int() {
6026 node.max_patches = v;
6027 }
6028 }
6029 _ => self.skip_value(),
6030 }
6031 }
6032 self.consume(TokenType::RBrace)?;
6033 Ok(node)
6034 }
6035
6036 fn parse_component(&mut self) -> Result<ComponentDefinition, ParseError> {
6040 let tok = self.consume(TokenType::Component)?;
6041 let name = self.consume(TokenType::Identifier)?.value;
6042 let mut node = ComponentDefinition {
6043 name,
6044 renders: String::new(),
6045 via_shield: String::new(),
6046 on_interact: String::new(),
6047 render_hint: "custom".to_string(),
6048 loc: Loc {
6049 line: tok.line,
6050 column: tok.column,
6051 },
6052 leading_trivia: Vec::new(),
6053 trailing_trivia: Vec::new(),
6054 };
6055 self.consume(TokenType::LBrace)?;
6056 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6057 let field_name = self.current().value.clone();
6058 self.advance();
6059 if !self.check(TokenType::Colon) {
6060 if self.check(TokenType::LBrace) {
6061 self.skip_braced_block()?;
6062 }
6063 continue;
6064 }
6065 self.advance();
6066 match field_name.as_str() {
6067 "renders" => node.renders = self.consume_any_ident_or_kw()?.value,
6068 "via_shield" => node.via_shield = self.consume_any_ident_or_kw()?.value,
6069 "on_interact" => node.on_interact = self.consume_any_ident_or_kw()?.value,
6070 "render_hint" => {
6071 let h_tok = self.consume_any_ident_or_kw()?;
6072 let h = h_tok.value;
6073 if !matches!(h.as_str(), "card" | "list" | "form" | "chart" | "custom") {
6074 return Err(ParseError {
6075 message: format!(
6076 "Invalid render_hint '{h}' in component '{}' — \
6077 expected card | list | form | chart | custom",
6078 node.name
6079 ),
6080 line: h_tok.line,
6081 column: h_tok.column,
6082 ..Default::default()
6083 });
6084 }
6085 node.render_hint = h;
6086 }
6087 _ => self.skip_value(),
6088 }
6089 }
6090 self.consume(TokenType::RBrace)?;
6091 Ok(node)
6092 }
6093
6094 fn parse_view(&mut self) -> Result<ViewDefinition, ParseError> {
6096 let tok = self.consume(TokenType::View)?;
6097 let name = self.consume(TokenType::Identifier)?.value;
6098 let mut node = ViewDefinition {
6099 name,
6100 title: String::new(),
6101 components: Vec::new(),
6102 route: String::new(),
6103 loc: Loc {
6104 line: tok.line,
6105 column: tok.column,
6106 },
6107 leading_trivia: Vec::new(),
6108 trailing_trivia: Vec::new(),
6109 };
6110 self.consume(TokenType::LBrace)?;
6111 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6112 let field_name = self.current().value.clone();
6113 self.advance();
6114 if !self.check(TokenType::Colon) {
6115 if self.check(TokenType::LBrace) {
6116 self.skip_braced_block()?;
6117 }
6118 continue;
6119 }
6120 self.advance();
6121 match field_name.as_str() {
6122 "title" => node.title = self.consume(TokenType::StringLit)?.value,
6123 "components" => node.components = self.parse_bracketed_identifiers()?,
6124 "route" => node.route = self.consume(TokenType::StringLit)?.value,
6125 _ => self.skip_value(),
6126 }
6127 }
6128 self.consume(TokenType::RBrace)?;
6129 Ok(node)
6130 }
6131
6132 fn parse_axonendpoint(&mut self) -> Result<AxonEndpointDefinition, ParseError> {
6133 let tok = self.consume(TokenType::AxonEndpoint)?;
6134 let name = self.consume(TokenType::Identifier)?.value;
6135 let mut node = AxonEndpointDefinition {
6136 name,
6137 method: String::new(),
6138 path: String::new(),
6139 body_type: String::new(),
6140 execute_flow: String::new(),
6141 output_type: String::new(),
6142 shield_ref: String::new(),
6143 retries: None,
6144 timeout: String::new(),
6145 compliance: Vec::new(),
6146 transport: "json".to_string(),
6148 keepalive: String::new(),
6149 transport_explicit: false,
6155 implicit_transport: String::new(),
6156 requires_capabilities: Vec::new(),
6158 replay_explicit: false,
6162 replay: false,
6163 transport_dialect: String::new(),
6168 has_algebraic_stream_effect: false,
6174 backend: String::new(),
6179 path_params: Vec::new(),
6184 query_params: Vec::new(),
6189 loc: Loc {
6190 line: tok.line,
6191 column: tok.column,
6192 },
6193 leading_trivia: Vec::new(),
6194 trailing_trivia: Vec::new(),
6195 };
6196 self.consume(TokenType::LBrace)?;
6197 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6198 let field_name = self.current().value.clone();
6199 self.advance();
6200 if self.check(TokenType::Colon) {
6201 self.advance();
6202 match field_name.as_str() {
6203 "method" => {
6204 let value_tok = self.consume_any_ident_or_kw()?;
6211 let value_upper = value_tok.value.to_uppercase();
6212 if !axonendpoint_is_valid_method(&value_upper) {
6213 let hint = crate::smart_suggest::suggest_for(
6214 &value_upper,
6215 AXONENDPOINT_METHOD_VALUES,
6216 );
6217 let base = format!(
6218 "Invalid method '{}' in axonendpoint '{}'.",
6219 value_tok.value, node.name
6220 );
6221 let message = if hint.is_empty() {
6222 format!(
6223 "{base} expected GET | POST | PUT | DELETE | PATCH, found {}",
6224 value_tok.value
6225 )
6226 } else {
6227 format!(
6228 "{base} {hint} (expected GET | POST | PUT | DELETE | PATCH, found {})",
6229 value_tok.value
6230 )
6231 };
6232 return Err(ParseError {
6233 message,
6234 line: value_tok.line,
6235 column: value_tok.column,
6236 ..Default::default()
6237 });
6238 }
6239 node.method = value_upper;
6240 }
6241 "path" => {
6242 node.path = self.consume(TokenType::StringLit)?.value.clone();
6243 match extract_path_param_names(&node.path) {
6251 Ok(names) => node.path_params = names,
6252 Err(dup) => {
6253 let cur = self.current().clone();
6254 return Err(ParseError {
6255 message: format!(
6256 "axonendpoint '{}' declares path '{}' \
6257 containing duplicate placeholder '{{{}}}'. \
6258 Each `{{name}}` in a `path:` must be \
6259 unique — the runtime cannot bind two \
6260 path segments to the same name (Fase 37.y D1).",
6261 node.name, node.path, dup,
6262 ),
6263 line: cur.line,
6264 column: cur.column,
6265 ..Default::default()
6266 });
6267 }
6268 }
6269 },
6270 "body" => node.body_type = self.consume_any_ident_or_kw()?.value.clone(),
6271 "query" => {
6272 let lbrace_tok = self.consume(TokenType::LBrace)?;
6291 let block_line = lbrace_tok.line;
6292 if !node.query_params.is_empty() {
6293 return Err(ParseError {
6294 message: format!(
6295 "axonendpoint '{}' declares `query: {{ … }}` \
6296 more than once. The query-parameter block \
6297 is unique per endpoint; combine all params \
6298 into a single block (Fase 37.y D2).",
6299 node.name,
6300 ),
6301 line: lbrace_tok.line,
6302 column: lbrace_tok.column,
6303 ..Default::default()
6304 });
6305 }
6306 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6307 let name_tok = self.consume(TokenType::Identifier)?;
6308 let field_name = name_tok.value.clone();
6309 if node
6311 .query_params
6312 .iter()
6313 .any(|f| f.name == field_name)
6314 {
6315 return Err(ParseError {
6316 message: format!(
6317 "axonendpoint '{}' declares duplicate \
6318 query param '{}' inside `query: {{ … }}`. \
6319 Each name must appear at most once \
6320 (Fase 37.y D2).",
6321 node.name, field_name,
6322 ),
6323 line: name_tok.line,
6324 column: name_tok.column,
6325 ..Default::default()
6326 });
6327 }
6328 self.consume(TokenType::Colon)?;
6329 let type_expr = self.parse_type_expr()?;
6330 if !type_expr.generic_param.is_empty() {
6342 let canonical_hint = if type_expr.name == "Optional" {
6343 format!(
6344 " Use `{}?` (the `?` suffix) for an \
6345 optional query param instead of \
6346 `Optional<{}>`.",
6347 type_expr.generic_param,
6348 type_expr.generic_param,
6349 )
6350 } else if type_expr.name == "List" {
6351 " Multi-value query params (e.g. `?tag=a&tag=b`) \
6352 are honest-deferred from v1.38.5; bind a \
6353 single-value `Text` query param and parse \
6354 the value inside the flow."
6355 .to_string()
6356 } else {
6357 String::new()
6358 };
6359 return Err(ParseError {
6360 message: format!(
6361 "axonendpoint '{}' query param '{}' uses \
6362 a generic type `{}<{}>`. Query params \
6363 take a primitive type from the closed \
6364 catalog ({}); the `?` suffix marks \
6365 optional.{} (Fase 37.y D2).",
6366 node.name,
6367 field_name,
6368 type_expr.name,
6369 type_expr.generic_param,
6370 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | "),
6371 canonical_hint,
6372 ),
6373 line: type_expr.loc.line,
6374 column: type_expr.loc.column,
6375 ..Default::default()
6376 });
6377 }
6378 if !axonendpoint_is_valid_query_param_type(&type_expr.name) {
6382 let hint = crate::smart_suggest::suggest_for(
6389 &type_expr.name,
6390 AXONENDPOINT_QUERY_PARAM_TYPES,
6391 );
6392 let hint_text = if hint.is_empty() {
6393 format!(
6394 " Expected one of: {}.",
6395 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
6396 )
6397 } else {
6398 format!(
6399 " {} Expected one of: {}.",
6400 hint,
6401 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
6402 )
6403 };
6404 return Err(ParseError {
6405 message: format!(
6406 "axonendpoint '{}' query param '{}' has \
6407 unsupported type '{}'.{} (Fase 37.y D2).",
6408 node.name, field_name, type_expr.name,
6409 hint_text,
6410 ),
6411 line: type_expr.loc.line,
6412 column: type_expr.loc.column,
6413 ..Default::default()
6414 });
6415 }
6416 node.query_params.push(TypeField {
6417 name: field_name,
6418 type_expr,
6419 loc: Loc {
6420 line: name_tok.line,
6421 column: name_tok.column,
6422 },
6423 });
6424 if self.check(TokenType::Comma) {
6430 self.advance();
6431 }
6432 let _ = block_line; }
6434 self.consume(TokenType::RBrace)?;
6435 },
6436 "execute" => node.execute_flow = self.consume_any_ident_or_kw()?.value.clone(),
6437 "output" => {
6438 node.output_type = self.parse_output_type_string()?;
6456 }
6457 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6458 "retries" => node.retries = self.parse_optional_int(),
6459 "timeout" => {
6460 let t = self.current().clone();
6461 self.advance();
6462 node.timeout = t.value.clone();
6463 }
6464 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
6465 "replay" => {
6466 let value_tok = self.consume(TokenType::Bool)?;
6473 node.replay = value_tok.value.eq_ignore_ascii_case("true");
6474 node.replay_explicit = true;
6475 }
6476 "requires" => {
6477 let bracket_tok = self.current().clone();
6484 let items = self.parse_bracketed_dot_identifiers()?;
6485 for slug in &items {
6486 if !is_valid_capability_slug(slug) {
6487 return Err(ParseError {
6488 message: format!(
6489 "Invalid capability slug '{slug}' in axonendpoint '{}' \
6490 `requires:`. Capability slugs must match \
6491 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
6492 lowercase identifiers starting with a letter. Examples: \
6493 `admin`, `legal.read`, `hipaa.phi.read`.",
6494 node.name
6495 ),
6496 line: bracket_tok.line,
6497 column: bracket_tok.column,
6498 ..Default::default()
6499 });
6500 }
6501 }
6502 node.requires_capabilities = items;
6503 }
6504 "transport" => {
6508 let value_tok = self.consume_any_ident_or_kw()?;
6509 let value = &value_tok.value;
6510 if !axonendpoint_is_valid_transport(value) {
6511 let hint = crate::smart_suggest::suggest_for(
6512 value,
6513 AXONENDPOINT_TRANSPORT_VALUES,
6514 );
6515 let base = format!(
6516 "Invalid transport '{}' in axonendpoint '{}'.",
6517 value, node.name
6518 );
6519 let message = if hint.is_empty() {
6520 format!("{base} expected json | sse | ndjson, found {value}")
6521 } else {
6522 format!(
6523 "{base} {hint} (expected json | sse | ndjson, found {value})"
6524 )
6525 };
6526 return Err(ParseError {
6527 message,
6528 line: value_tok.line,
6529 column: value_tok.column,
6530 ..Default::default()
6531 });
6532 }
6533 node.transport = value.clone();
6534 node.transport_explicit = true;
6539 if self.check(TokenType::LParen) {
6546 if value != "sse" {
6547 let tok = self.current().clone();
6548 return Err(ParseError {
6549 message: format!(
6550 "Dialect parametrization \
6551 `transport: {value}(<dialect>)` is \
6552 only valid for `sse`; got \
6553 `{value}` in axonendpoint '{}'.",
6554 node.name
6555 ),
6556 line: tok.line,
6557 column: tok.column,
6558 ..Default::default()
6559 });
6560 }
6561 self.advance(); let dialect_tok = self.consume_any_ident_or_kw()?;
6563 let dialect = dialect_tok.value.clone();
6564 if !AXONENDPOINT_TRANSPORT_DIALECTS
6565 .iter()
6566 .any(|&d| d == dialect)
6567 {
6568 let hint = crate::smart_suggest::suggest_for(
6569 &dialect,
6570 AXONENDPOINT_TRANSPORT_DIALECTS,
6571 );
6572 let base = format!(
6573 "Invalid SSE dialect '{dialect}' in axonendpoint '{}'.",
6574 node.name
6575 );
6576 let message = if hint.is_empty() {
6577 format!(
6578 "{base} expected axon | openai | kimi | glm | anthropic, found {dialect}"
6579 )
6580 } else {
6581 format!(
6582 "{base} {hint} (expected axon | openai | kimi | glm | anthropic, found {dialect})"
6583 )
6584 };
6585 return Err(ParseError {
6586 message,
6587 line: dialect_tok.line,
6588 column: dialect_tok.column,
6589 ..Default::default()
6590 });
6591 }
6592 let rparen_tok = self.current().clone();
6594 if !self.check(TokenType::RParen) {
6595 return Err(ParseError {
6596 message: format!(
6597 "Expected `)` after dialect name \
6598 in axonendpoint '{}' \
6599 (transport: sse(<dialect>) grammar).",
6600 node.name
6601 ),
6602 line: rparen_tok.line,
6603 column: rparen_tok.column,
6604 ..Default::default()
6605 });
6606 }
6607 self.advance(); node.transport_dialect = dialect;
6609 }
6610 }
6611 "keepalive" => {
6612 let value_tok = self.current().clone();
6616 self.advance();
6617 let value = &value_tok.value;
6618 if !axonendpoint_is_valid_keepalive(value) {
6619 let hint = crate::smart_suggest::suggest_for(
6620 value,
6621 AXONENDPOINT_KEEPALIVE_VALUES,
6622 );
6623 let base = format!(
6624 "Invalid keepalive '{}' in axonendpoint '{}'.",
6625 value, node.name
6626 );
6627 let message = if hint.is_empty() {
6628 format!("{base} expected 5s | 15s | 30s | 60s, found {value}")
6629 } else {
6630 format!(
6631 "{base} {hint} (expected 5s | 15s | 30s | 60s, found {value})"
6632 )
6633 };
6634 return Err(ParseError {
6635 message,
6636 line: value_tok.line,
6637 column: value_tok.column,
6638 ..Default::default()
6639 });
6640 }
6641 node.keepalive = value.clone();
6642 }
6643 "backend" => {
6644 let value_tok = self.consume_any_ident_or_kw()?;
6652 let value = &value_tok.value;
6653 if !axonendpoint_is_valid_backend(value) {
6654 let hint = crate::smart_suggest::suggest_for(
6655 value,
6656 AXONENDPOINT_BACKEND_VALUES,
6657 );
6658 let expected = AXONENDPOINT_BACKEND_VALUES.join(" | ");
6659 let base = format!(
6660 "Invalid backend '{}' in axonendpoint '{}'.",
6661 value, node.name
6662 );
6663 let message = if hint.is_empty() {
6664 format!("{base} expected {expected}, found {value}")
6665 } else {
6666 format!(
6667 "{base} {hint} (expected {expected}, found {value})"
6668 )
6669 };
6670 return Err(ParseError {
6671 message,
6672 line: value_tok.line,
6673 column: value_tok.column,
6674 ..Default::default()
6675 });
6676 }
6677 node.backend = value.clone();
6678 }
6679 _ => self.skip_value(),
6680 }
6681 } else if self.check(TokenType::LBrace) {
6682 self.skip_braced_block()?;
6683 }
6684 }
6685 self.consume(TokenType::RBrace)?;
6686 Ok(node)
6687 }
6688
6689 fn parse_optional_int(&mut self) -> Option<i64> {
6692 let tok = self.current().clone();
6693 match tok.ttype {
6694 TokenType::Integer => {
6695 self.advance();
6696 tok.value.parse::<i64>().ok()
6697 }
6698 _ => {
6699 self.advance();
6700 None
6701 }
6702 }
6703 }
6704
6705 fn parse_optional_float(&mut self) -> Option<f64> {
6706 let tok = self.current().clone();
6707 match tok.ttype {
6708 TokenType::Float | TokenType::Integer => {
6709 self.advance();
6710 tok.value.parse::<f64>().ok()
6711 }
6712 _ => {
6713 self.advance();
6714 None
6715 }
6716 }
6717 }
6718
6719 fn parse_lambda_data(&mut self) -> Result<LambdaDataDefinition, ParseError> {
6722 let tok = self.consume(TokenType::Lambda)?;
6723 let name = self.consume(TokenType::Identifier)?;
6724 self.consume(TokenType::LBrace)?;
6725
6726 let mut node = LambdaDataDefinition {
6727 name: name.value.clone(),
6728 ontology: String::new(),
6729 certainty: 1.0,
6730 temporal_frame_start: String::new(),
6731 temporal_frame_end: String::new(),
6732 provenance: String::new(),
6733 derivation: String::new(),
6734 loc: Loc {
6735 line: tok.line,
6736 column: tok.column,
6737 },
6738 leading_trivia: Vec::new(),
6739 trailing_trivia: Vec::new(),
6740 };
6741
6742 while !self.check(TokenType::RBrace) {
6743 let field = self.current().clone();
6744 match field.ttype {
6745 TokenType::Ontology => {
6746 self.advance();
6747 self.consume(TokenType::Colon)?;
6748 node.ontology = self.consume(TokenType::StringLit)?.value.clone();
6749 }
6750 TokenType::Certainty => {
6751 self.advance();
6752 self.consume(TokenType::Colon)?;
6753 let val = self.current().clone();
6754 match val.ttype {
6755 TokenType::Float => {
6756 self.advance();
6757 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
6758 }
6759 TokenType::Integer => {
6760 self.advance();
6761 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
6762 }
6763 _ => {
6764 return Err(ParseError {
6765 message: format!(
6766 "Expected number for certainty, got '{}'",
6767 val.value
6768 ),
6769 line: val.line,
6770 column: val.column,
6771 ..Default::default()
6772 });
6773 }
6774 }
6775 }
6776 TokenType::TemporalFrame => {
6777 self.advance();
6778 self.consume(TokenType::Colon)?;
6779 node.temporal_frame_start = self.consume(TokenType::StringLit)?.value.clone();
6780 if self.check(TokenType::StringLit) {
6782 node.temporal_frame_end = self.consume(TokenType::StringLit)?.value.clone();
6783 }
6784 }
6785 TokenType::Provenance => {
6786 self.advance();
6787 self.consume(TokenType::Colon)?;
6788 node.provenance = self.consume(TokenType::StringLit)?.value.clone();
6789 }
6790 TokenType::Derivation => {
6791 self.advance();
6792 self.consume(TokenType::Colon)?;
6793 let d = self.current().clone();
6794 self.advance();
6795 node.derivation = d.value.clone();
6796 }
6797 _ => {
6798 self.advance();
6800 if self.check(TokenType::Colon) {
6801 self.advance();
6802 self.skip_value();
6803 }
6804 }
6805 }
6806 }
6807
6808 self.consume(TokenType::RBrace)?;
6809 Ok(node)
6810 }
6811
6812 fn parse_lambda_data_apply(&mut self) -> Result<LambdaDataApplyNode, ParseError> {
6813 let tok = self.consume(TokenType::Lambda)?;
6814 let lambda_name = self.consume(TokenType::Identifier)?;
6815
6816 let on_tok = self.current().clone();
6818 self.advance();
6819 if on_tok.value != "on" {
6820 return Err(ParseError {
6821 message: format!(
6822 "Expected 'on' after lambda data name in flow step, got '{}'",
6823 on_tok.value
6824 ),
6825 line: on_tok.line,
6826 column: on_tok.column,
6827 ..Default::default()
6828 });
6829 }
6830
6831 let target = self.current().clone();
6832 self.advance();
6833
6834 let mut output_type = String::new();
6835 if self.check(TokenType::Arrow) {
6836 self.advance();
6837 output_type = self.consume(TokenType::Identifier)?.value.clone();
6838 }
6839
6840 Ok(LambdaDataApplyNode {
6841 lambda_data_name: lambda_name.value.clone(),
6842 target: target.value.clone(),
6843 output_type,
6844 loc: Loc {
6845 line: tok.line,
6846 column: tok.column,
6847 },
6848 })
6849 }
6850
6851 fn parse_generic_declaration(&mut self) -> Result<Declaration, ParseError> {
6854 let kw_tok = self.current().clone();
6855 self.advance(); let name = if self.current().ttype == TokenType::Identifier {
6859 let n = self.current().value.clone();
6860 self.advance();
6861 n
6862 } else if !self.check(TokenType::LBrace)
6863 && !self.check(TokenType::LParen)
6864 && !self.check(TokenType::Eof)
6865 && self
6866 .current()
6867 .value
6868 .chars()
6869 .all(|c| c.is_alphanumeric() || c == '_')
6870 {
6871 let n = self.current().value.clone();
6872 self.advance();
6873 n
6874 } else {
6875 String::new()
6876 };
6877
6878 if self.check(TokenType::LParen) {
6880 self.advance();
6881 let mut depth = 1u32;
6882 while depth > 0 && !self.check(TokenType::Eof) {
6883 if self.check(TokenType::LParen) {
6884 depth += 1;
6885 } else if self.check(TokenType::RParen) {
6886 depth -= 1;
6887 }
6888 self.advance();
6889 }
6890 }
6891
6892 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
6894 if self.check(TokenType::Eof) {
6895 break;
6896 }
6897 self.advance();
6898 }
6899
6900 if self.check(TokenType::LBrace) {
6902 self.skip_braced_block()?;
6903 }
6904
6905 Ok(Declaration::Generic(GenericDeclaration {
6906 keyword: kw_tok.value,
6907 name,
6908 loc: Loc {
6909 line: kw_tok.line,
6910 column: kw_tok.column,
6911 },
6912 leading_trivia: Vec::new(),
6913 trailing_trivia: Vec::new(),
6914 }))
6915 }
6916
6917 fn parse_channel(&mut self) -> Result<ChannelDefinition, ParseError> {
6925 let tok = self.consume(TokenType::Channel)?;
6926 let name = self.consume(TokenType::Identifier)?.value;
6927 let mut node = ChannelDefinition {
6928 name: name.clone(),
6929 message: String::new(),
6930 qos: "at_least_once".to_string(),
6931 lifetime: "affine".to_string(),
6932 persistence: "ephemeral".to_string(),
6933 shield_ref: 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_tok = self.current().clone();
6944 let field_name = field_tok.value.clone();
6945 self.advance();
6946 if !self.check(TokenType::Colon) {
6947 if self.check(TokenType::LBrace) {
6948 self.skip_braced_block()?;
6949 }
6950 continue;
6951 }
6952 self.advance();
6953 match field_name.as_str() {
6954 "message" => node.message = self.parse_channel_message_type()?,
6955 "qos" => {
6956 let q_tok = self.consume_any_ident_or_kw()?;
6957 if !matches!(
6958 q_tok.value.as_str(),
6959 "at_most_once" | "at_least_once" | "exactly_once" | "broadcast" | "queue"
6960 ) {
6961 return Err(ParseError {
6962 message: format!(
6963 "Invalid qos '{}' in channel '{}' — \
6964 expected at_most_once | at_least_once | \
6965 exactly_once | broadcast | queue",
6966 q_tok.value, name
6967 ),
6968 line: q_tok.line,
6969 column: q_tok.column,
6970 ..Default::default()
6971 });
6972 }
6973 node.qos = q_tok.value;
6974 }
6975 "lifetime" => {
6976 let lt_tok = self.consume_any_ident_or_kw()?;
6977 if !matches!(lt_tok.value.as_str(), "linear" | "affine" | "persistent") {
6978 return Err(ParseError {
6979 message: format!(
6980 "Invalid lifetime '{}' in channel '{}' — \
6981 expected linear | affine | persistent",
6982 lt_tok.value, name
6983 ),
6984 line: lt_tok.line,
6985 column: lt_tok.column,
6986 ..Default::default()
6987 });
6988 }
6989 node.lifetime = lt_tok.value;
6990 }
6991 "persistence" => {
6992 let p_tok = self.consume_any_ident_or_kw()?;
6993 if !matches!(p_tok.value.as_str(), "ephemeral" | "persistent_axonstore") {
6994 return Err(ParseError {
6995 message: format!(
6996 "Invalid persistence '{}' in channel '{}' — \
6997 expected ephemeral | persistent_axonstore",
6998 p_tok.value, name
6999 ),
7000 line: p_tok.line,
7001 column: p_tok.column,
7002 ..Default::default()
7003 });
7004 }
7005 node.persistence = p_tok.value;
7006 }
7007 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7008 _ => self.skip_value(),
7009 }
7010 }
7011 self.consume(TokenType::RBrace)?;
7012 Ok(node)
7013 }
7014
7015 fn parse_channel_message_type(&mut self) -> Result<String, ParseError> {
7018 let head = self.consume(TokenType::Identifier)?;
7019 let mut spelling = head.value;
7020 if self.check(TokenType::Lt) {
7021 self.advance();
7022 let inner = self.parse_channel_message_type()?;
7023 self.consume(TokenType::Gt)?;
7024 spelling = format!("{}<{}>", spelling, inner);
7025 }
7026 Ok(spelling)
7027 }
7028
7029 fn parse_emit_step(&mut self) -> Result<FlowStep, ParseError> {
7035 let tok = self.consume(TokenType::Emit)?;
7036 let channel = self.consume(TokenType::Identifier)?.value;
7037 self.consume(TokenType::LParen)?;
7038 let value = self.parse_emit_value_ref()?;
7039 self.consume(TokenType::RParen)?;
7040 Ok(FlowStep::Emit(EmitStatement {
7041 channel_ref: channel,
7042 value_ref: value,
7043 loc: Loc {
7044 line: tok.line,
7045 column: tok.column,
7046 },
7047 }))
7048 }
7049
7050 fn parse_emit_value_ref(&mut self) -> Result<String, ParseError> {
7066 let head = self.consume(TokenType::Identifier)?.value;
7067 let mut parts = vec![head];
7068 while self.check(TokenType::Dot) {
7069 self.advance(); let next_tok = self.current().clone();
7071 let valid = !next_tok.value.is_empty()
7072 && next_tok.value.as_bytes()[0].is_ascii_alphabetic()
7073 || next_tok.value.starts_with('_');
7074 if !valid {
7075 return Err(ParseError {
7076 message: format!(
7077 "Expected identifier or keyword after '.' in dotted \
7078 access, found {:?}",
7079 next_tok.value
7080 ),
7081 line: next_tok.line,
7082 column: next_tok.column,
7083 ..Default::default()
7084 });
7085 }
7086 self.advance();
7087 parts.push(next_tok.value);
7088 }
7089 Ok(parts.join("."))
7090 }
7091
7092 fn parse_publish_step(&mut self) -> Result<FlowStep, ParseError> {
7094 let tok = self.consume(TokenType::Publish)?;
7095 let channel = self.consume(TokenType::Identifier)?.value;
7096 self.consume(TokenType::Within)?;
7097 let shield = self.consume(TokenType::Identifier)?.value;
7098 Ok(FlowStep::Publish(PublishStatement {
7099 channel_ref: channel,
7100 shield_ref: shield,
7101 loc: Loc {
7102 line: tok.line,
7103 column: tok.column,
7104 },
7105 }))
7106 }
7107
7108 fn parse_discover_step(&mut self) -> Result<FlowStep, ParseError> {
7110 let tok = self.consume(TokenType::Discover)?;
7111 let cap = self.consume(TokenType::Identifier)?.value;
7112 self.consume(TokenType::As)?;
7113 let alias = self.consume(TokenType::Identifier)?.value;
7114 Ok(FlowStep::Discover(DiscoverStatement {
7115 capability_ref: cap,
7116 alias,
7117 loc: Loc {
7118 line: tok.line,
7119 column: tok.column,
7120 },
7121 }))
7122 }
7123}
7124
7125#[cfg(test)]
7128mod fase13_parser_tests {
7129 use super::*;
7130 use crate::lexer::Lexer;
7131
7132 fn parse(src: &str) -> Result<Program, ParseError> {
7133 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
7134 Parser::new(tokens).parse()
7135 }
7136
7137 #[test]
7138 fn channel_full_parses() {
7139 let src = r#"channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }"#;
7140 let prog = parse(src).expect("parse");
7141 match &prog.declarations[0] {
7142 Declaration::Channel(c) => {
7143 assert_eq!(c.name, "C");
7144 assert_eq!(c.message, "Order");
7145 assert_eq!(c.qos, "at_least_once");
7146 assert_eq!(c.lifetime, "affine");
7147 assert_eq!(c.persistence, "ephemeral");
7148 assert_eq!(c.shield_ref, "Gate");
7149 }
7150 _ => panic!("expected ChannelDefinition"),
7151 }
7152 }
7153
7154 #[test]
7155 fn channel_defaults_match_paper_d1() {
7156 let prog = parse("channel C { message: Order }").expect("parse");
7157 if let Declaration::Channel(c) = &prog.declarations[0] {
7158 assert_eq!(c.qos, "at_least_once"); assert_eq!(c.lifetime, "affine"); assert_eq!(c.persistence, "ephemeral");
7161 assert_eq!(c.shield_ref, "");
7162 } else {
7163 panic!("expected ChannelDefinition");
7164 }
7165 }
7166
7167 #[test]
7168 fn channel_second_order_message_type_parses() {
7169 let prog = parse("channel C { message: Channel<Order> }").expect("parse");
7170 if let Declaration::Channel(c) = &prog.declarations[0] {
7171 assert_eq!(c.message, "Channel<Order>");
7172 } else {
7173 panic!("expected ChannelDefinition");
7174 }
7175 }
7176
7177 #[test]
7178 fn channel_nested_channel_message_type_parses() {
7179 let prog = parse("channel C { message: Channel<Channel<Order>> }").expect("parse");
7180 if let Declaration::Channel(c) = &prog.declarations[0] {
7181 assert_eq!(c.message, "Channel<Channel<Order>>");
7182 } else {
7183 panic!("expected ChannelDefinition");
7184 }
7185 }
7186
7187 #[test]
7188 fn channel_invalid_qos_rejected() {
7189 let err = parse("channel C { message: T qos: bogus }").unwrap_err();
7190 assert!(err.message.contains("Invalid qos"), "got {}", err.message);
7191 }
7192
7193 #[test]
7194 fn channel_invalid_lifetime_rejected() {
7195 let err = parse("channel C { message: T lifetime: eternal }").unwrap_err();
7196 assert!(
7197 err.message.contains("Invalid lifetime"),
7198 "got {}",
7199 err.message
7200 );
7201 }
7202
7203 #[test]
7204 fn channel_invalid_persistence_rejected() {
7205 let err = parse("channel C { message: T persistence: forever }").unwrap_err();
7206 assert!(
7207 err.message.contains("Invalid persistence"),
7208 "got {}",
7209 err.message
7210 );
7211 }
7212
7213 #[test]
7214 fn emit_value_parses() {
7215 let src = "flow f() -> Out { emit C(payload) }";
7216 let prog = parse(src).expect("parse");
7217 if let Declaration::Flow(f) = &prog.declarations[0] {
7218 match &f.body[0] {
7219 FlowStep::Emit(e) => {
7220 assert_eq!(e.channel_ref, "C");
7221 assert_eq!(e.value_ref, "payload");
7222 }
7223 other => panic!("expected Emit, got {:?}", other),
7224 }
7225 } else {
7226 panic!("expected Flow");
7227 }
7228 }
7229
7230 #[test]
7231 fn publish_within_shield_parses() {
7232 let src = "flow f() -> Cap { publish C within Gate }";
7233 let prog = parse(src).expect("parse");
7234 if let Declaration::Flow(f) = &prog.declarations[0] {
7235 match &f.body[0] {
7236 FlowStep::Publish(p) => {
7237 assert_eq!(p.channel_ref, "C");
7238 assert_eq!(p.shield_ref, "Gate");
7239 }
7240 other => panic!("expected Publish, got {:?}", other),
7241 }
7242 } else {
7243 panic!("expected Flow");
7244 }
7245 }
7246
7247 #[test]
7248 fn discover_with_alias_parses() {
7249 let src = "flow f() -> Out { discover C as ch }";
7250 let prog = parse(src).expect("parse");
7251 if let Declaration::Flow(f) = &prog.declarations[0] {
7252 match &f.body[0] {
7253 FlowStep::Discover(d) => {
7254 assert_eq!(d.capability_ref, "C");
7255 assert_eq!(d.alias, "ch");
7256 }
7257 other => panic!("expected Discover, got {:?}", other),
7258 }
7259 } else {
7260 panic!("expected Flow");
7261 }
7262 }
7263
7264 #[test]
7265 fn listen_typed_ref_sets_flag_true() {
7266 let src = "daemon D() { goal: \"x\" listen C as ev { } }";
7267 let prog = parse(src).expect("parse");
7268 if let Declaration::Daemon(d) = &prog.declarations[0] {
7269 assert_eq!(d.listeners.len(), 1);
7270 assert_eq!(d.listeners[0].channel, "C");
7271 assert!(d.listeners[0].channel_is_ref, "typed ref ⇒ true");
7272 } else {
7273 panic!("expected Daemon");
7274 }
7275 }
7276
7277 #[test]
7278 fn listen_string_topic_legacy_flag_false() {
7279 let src = "daemon D() { goal: \"x\" listen \"orders\" as ev { } }";
7280 let prog = parse(src).expect("parse");
7281 if let Declaration::Daemon(d) = &prog.declarations[0] {
7282 assert_eq!(d.listeners.len(), 1);
7283 assert_eq!(d.listeners[0].channel, "orders");
7284 assert!(!d.listeners[0].channel_is_ref, "string topic ⇒ false");
7285 } else {
7286 panic!("expected Daemon");
7287 }
7288 }
7289
7290 fn extract_first_emit(prog: &Program) -> &EmitStatement {
7293 if let Declaration::Flow(f) = &prog.declarations[0] {
7294 if let FlowStep::Emit(e) = &f.body[0] {
7295 return e;
7296 }
7297 }
7298 panic!("expected emit statement at flow body[0]");
7299 }
7300
7301 #[test]
7302 fn emit_accepts_bare_identifier_value_ref() {
7303 let prog = parse("flow f() -> Out { emit Hello(payload) }").expect("parse");
7305 let emit = extract_first_emit(&prog);
7306 assert_eq!(emit.channel_ref, "Hello");
7307 assert_eq!(emit.value_ref, "payload");
7308 }
7309
7310 #[test]
7311 fn emit_accepts_two_segment_dotted_value_ref() {
7312 let prog = parse("flow f() -> Out { emit Hello(Build.output) }").expect("parse");
7314 let emit = extract_first_emit(&prog);
7315 assert_eq!(emit.value_ref, "Build.output");
7316 }
7317
7318 #[test]
7319 fn emit_accepts_three_segment_nested_dotted_value_ref() {
7320 let prog = parse("flow f() -> Out { emit Score(Analyze.result.score) }").expect("parse");
7321 let emit = extract_first_emit(&prog);
7322 assert_eq!(emit.value_ref, "Analyze.result.score");
7323 }
7324
7325 #[test]
7326 fn emit_dotted_with_trailing_dot_fails() {
7327 let result = parse("flow f() -> Out { emit Hello(Build.) }");
7329 assert!(result.is_err(), "expected parse error for trailing dot");
7330 }
7331}
7332
7333#[cfg(test)]
7336mod fase14a_declaration_trivia_tests {
7337 use super::*;
7338 use crate::lexer::Lexer;
7339 use crate::tokens::TriviaKind;
7340
7341 fn parse(src: &str) -> Program {
7342 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
7343 Parser::new(toks).parse().expect("parse")
7344 }
7345
7346 #[test]
7347 fn no_comments_means_empty_trivia_per_decl() {
7348 let prog = parse("flow F() -> Out { }");
7349 assert_eq!(prog.declarations.len(), 1);
7350 assert_eq!(prog.declaration_trivia.len(), 1);
7351 assert!(prog.declaration_trivia[0].leading.is_empty());
7352 assert!(prog.declaration_trivia[0].trailing.is_empty());
7353 }
7354
7355 #[test]
7356 fn doc_line_comment_attaches_as_leading() {
7357 let prog = parse("/// Documents F\nflow F() -> Out { }");
7358 let triv = &prog.declaration_trivia[0];
7359 assert_eq!(triv.leading.len(), 1);
7360 assert_eq!(triv.leading[0].kind, TriviaKind::DocLine);
7361 assert!(triv.leading[0].is_doc());
7362 assert_eq!(triv.leading[0].text, "/// Documents F");
7363 }
7364
7365 #[test]
7366 fn regular_line_comment_attaches_as_leading() {
7367 let prog = parse("// header\nflow F() -> Out { }");
7368 let triv = &prog.declaration_trivia[0];
7369 assert_eq!(triv.leading.len(), 1);
7370 assert_eq!(triv.leading[0].kind, TriviaKind::Line);
7371 assert!(!triv.leading[0].is_doc());
7372 }
7373
7374 #[test]
7375 fn block_doc_comment_attaches_as_leading() {
7376 let prog = parse("/** Doc block */\nflow F() -> Out { }");
7377 let triv = &prog.declaration_trivia[0];
7378 assert_eq!(triv.leading[0].kind, TriviaKind::DocBlock);
7379 assert!(triv.leading[0].is_doc());
7380 }
7381
7382 #[test]
7383 fn multiple_comments_collected_in_source_order() {
7384 let src = "/// First\n/// Second\nflow F() -> Out { }";
7385 let prog = parse(src);
7386 let triv = &prog.declaration_trivia[0];
7387 assert_eq!(triv.leading.len(), 2);
7388 assert_eq!(triv.leading[0].text, "/// First");
7389 assert_eq!(triv.leading[1].text, "/// Second");
7390 }
7391
7392 #[test]
7393 fn three_decls_each_get_own_leading() {
7394 let src = "/// for A\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }\n/// for C\nflow C() -> Out { }";
7395 let prog = parse(src);
7396 assert_eq!(prog.declarations.len(), 3);
7397 assert_eq!(prog.declaration_trivia.len(), 3);
7398 for (idx, name) in ["A", "B", "C"].iter().enumerate() {
7399 let triv = &prog.declaration_trivia[idx];
7400 assert_eq!(triv.leading.len(), 1);
7401 assert_eq!(triv.leading[0].text, format!("/// for {name}"));
7402 }
7403 }
7404
7405 #[test]
7406 fn trailing_comment_attaches_to_last_token_of_decl() {
7407 let prog = parse("flow F() -> Out { } // tail");
7409 let triv = &prog.declaration_trivia[0];
7410 assert_eq!(triv.trailing.len(), 1);
7411 assert_eq!(triv.trailing[0].text, "// tail");
7412 }
7413
7414 #[test]
7415 fn mixed_doc_and_regular_preserve_order_between_decls() {
7416 let src = "/// doc for A\nflow A() -> Out { }\n\n// header line\n/// doc for B\nflow B() -> Out { }";
7417 let prog = parse(src);
7418 assert_eq!(prog.declarations.len(), 2);
7419 assert_eq!(prog.declaration_trivia[0].leading.len(), 1);
7421 assert_eq!(prog.declaration_trivia[1].leading.len(), 2);
7423 assert_eq!(prog.declaration_trivia[1].leading[0].text, "// header line");
7424 assert_eq!(prog.declaration_trivia[1].leading[1].text, "/// doc for B");
7425 }
7426
7427 #[test]
7428 fn parser_unaffected_by_comments_in_grammar_path() {
7429 let src =
7434 "// before flow\nflow /* between flow and name */ F() -> Out {\n // body comment\n}";
7435 let prog = parse(src);
7436 assert_eq!(prog.declarations.len(), 1);
7437 if let Declaration::Flow(f) = &prog.declarations[0] {
7438 assert_eq!(f.name, "F");
7439 } else {
7440 panic!("expected Flow declaration");
7441 }
7442 }
7443}
7444
7445#[cfg(test)]
7454mod fase14b_per_struct_trivia_tests {
7455 use super::*;
7456 use crate::lexer::Lexer;
7457 use crate::tokens::TriviaKind;
7458
7459 fn parse(src: &str) -> Program {
7460 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
7461 Parser::new(toks).parse().expect("parse")
7462 }
7463
7464 #[test]
7465 fn flow_definition_carries_leading_trivia_directly() {
7466 let prog = parse("/// documents F\nflow F() -> Out { }");
7467 if let Declaration::Flow(f) = &prog.declarations[0] {
7468 assert_eq!(f.leading_trivia.len(), 1);
7469 assert_eq!(f.leading_trivia[0].kind, TriviaKind::DocLine);
7470 assert_eq!(f.leading_trivia[0].text, "/// documents F");
7471 assert!(f.trailing_trivia.is_empty());
7472 } else {
7473 panic!("expected Flow declaration");
7474 }
7475 }
7476
7477 #[test]
7478 fn flow_definition_carries_trailing_trivia_directly() {
7479 let prog = parse("flow F() -> Out { } // tail comment");
7480 if let Declaration::Flow(f) = &prog.declarations[0] {
7481 assert_eq!(f.trailing_trivia.len(), 1);
7482 assert_eq!(f.trailing_trivia[0].text, "// tail comment");
7483 } else {
7484 panic!("expected Flow declaration");
7485 }
7486 }
7487
7488 #[test]
7489 fn channel_definition_carries_trivia_directly() {
7490 let src = concat!(
7493 "/// inbound order events\n",
7494 "channel Orders {\n",
7495 " message: Order\n",
7496 " qos: at_least_once\n",
7497 " lifetime: affine\n",
7498 " persistence: ephemeral\n",
7499 " shield: Broker\n",
7500 "}",
7501 );
7502 let prog = parse(src);
7503 if let Declaration::Channel(ch) = &prog.declarations[0] {
7504 assert_eq!(ch.leading_trivia.len(), 1);
7505 assert!(ch.leading_trivia[0].is_doc());
7506 assert_eq!(ch.leading_trivia[0].text, "/// inbound order events");
7507 } else {
7508 panic!("expected Channel declaration");
7509 }
7510 }
7511
7512 #[test]
7513 fn per_struct_fields_match_side_channel() {
7514 let src = "/// for A\n// header for B\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }";
7517 let prog = parse(src);
7518 for (idx, decl) in prog.declarations.iter().enumerate() {
7519 let side = &prog.declaration_trivia[idx];
7520 let (per_lead, per_trail) = match decl {
7521 Declaration::Flow(f) => (&f.leading_trivia, &f.trailing_trivia),
7522 _ => panic!("unexpected variant"),
7523 };
7524 assert_eq!(per_lead.len(), side.leading.len());
7525 assert_eq!(per_trail.len(), side.trailing.len());
7526 for (a, b) in per_lead.iter().zip(side.leading.iter()) {
7527 assert_eq!(a.text, b.text);
7528 assert_eq!(a.kind, b.kind);
7529 }
7530 }
7531 }
7532
7533 #[test]
7534 fn comment_free_program_yields_empty_per_struct_fields() {
7535 let prog = parse("flow F() -> Out { }");
7536 if let Declaration::Flow(f) = &prog.declarations[0] {
7537 assert!(f.leading_trivia.is_empty());
7538 assert!(f.trailing_trivia.is_empty());
7539 } else {
7540 panic!("expected Flow declaration");
7541 }
7542 }
7543}
7544
7545#[cfg(test)]
7554mod fase14c_inner_doc_tests {
7555 use super::*;
7556 use crate::lexer::Lexer;
7557 use crate::tokens::TriviaKind;
7558
7559 fn parse(src: &str) -> Program {
7560 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
7561 Parser::new(toks).parse().expect("parse")
7562 }
7563
7564 #[test]
7565 fn inner_doc_line_reaches_leading_trivia() {
7566 let src = "//! file-level docs\nflow F() -> Out { }";
7567 let prog = parse(src);
7568 let triv = &prog.declaration_trivia[0];
7569 assert_eq!(triv.leading.len(), 1);
7570 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocLine);
7571 assert!(triv.leading[0].is_doc());
7572 assert!(triv.leading[0].is_inner_doc());
7573 assert_eq!(triv.leading[0].text, "//! file-level docs");
7574 assert_eq!(triv.leading[0].stripped_text(), " file-level docs");
7575 }
7576
7577 #[test]
7578 fn inner_doc_block_reaches_leading_trivia() {
7579 let src = "/*! module-level docs */\nflow F() -> Out { }";
7580 let prog = parse(src);
7581 let triv = &prog.declaration_trivia[0];
7582 assert_eq!(triv.leading.len(), 1);
7583 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocBlock);
7584 assert!(triv.leading[0].is_inner_doc());
7585 assert_eq!(triv.leading[0].stripped_text(), " module-level docs ");
7586 }
7587
7588 #[test]
7589 fn outer_and_inner_doc_can_coexist() {
7590 let src = "//! file docs\n/// docs F\nflow F() -> Out { }";
7594 let prog = parse(src);
7595 let triv = &prog.declaration_trivia[0];
7596 assert_eq!(triv.leading.len(), 2);
7597 assert!(triv.leading[0].is_inner_doc());
7598 assert!(triv.leading[1].is_doc());
7599 assert!(!triv.leading[1].is_inner_doc());
7600 }
7601
7602 #[test]
7603 fn inner_doc_reaches_per_struct_fields() {
7604 let src = "//! intro\nflow F() -> Out { }";
7606 let prog = parse(src);
7607 if let Declaration::Flow(f) = &prog.declarations[0] {
7608 assert_eq!(f.leading_trivia.len(), 1);
7609 assert!(f.leading_trivia[0].is_inner_doc());
7610 } else {
7611 panic!("expected Flow declaration");
7612 }
7613 }
7614}
7615
7616#[cfg(test)]
7634mod fase28_recovery_tests {
7635 use super::*;
7636 use crate::lexer::Lexer;
7637
7638 fn lex(src: &str) -> Vec<Token> {
7641 Lexer::new(src, "<test>").tokenize().expect("lex")
7642 }
7643
7644 fn recover(src: &str) -> ParseResult {
7647 Parser::new(lex(src)).parse_with_recovery()
7648 }
7649
7650 fn strict(src: &str) -> Result<Program, ParseError> {
7652 Parser::new(lex(src)).parse()
7653 }
7654
7655 #[test]
7658 fn strict_parse_unchanged_for_clean_source() {
7659 let src = "intent I {}";
7662 let prog = strict(src).expect("clean parse");
7663 assert_eq!(prog.declarations.len(), 1);
7664 }
7665
7666 #[test]
7667 fn strict_parse_still_raises_on_first_error() {
7668 let src = "flow F() { } not_a_keyword flow G() { }";
7673 let _ = strict(src).expect_err("must error fast in strict mode");
7674 }
7675
7676 #[test]
7677 fn recovery_clean_source_yields_no_errors() {
7678 let src = "flow F() { } flow G() { }";
7679 let pr = recover(src);
7680 assert!(pr.is_clean(), "errors: {:?}", pr.errors);
7681 assert_eq!(pr.program.declarations.len(), 2);
7682 }
7683
7684 #[test]
7687 fn single_unknown_top_level_token_recovers() {
7688 let src = "garbage_token flow F() { } flow G() { }";
7690 let pr = recover(src);
7691 assert_eq!(pr.errors.len(), 1, "errors: {:?}", pr.errors);
7692 assert_eq!(pr.program.declarations.len(), 2);
7693 }
7694
7695 #[test]
7696 fn error_in_first_decl_does_not_block_second() {
7697 let src = "flow F() { not_a_step nope } flow G() { }";
7700 let pr = recover(src);
7701 assert!(pr.has_errors(), "expected at least one error");
7702 let names: Vec<&str> = pr
7704 .program
7705 .declarations
7706 .iter()
7707 .filter_map(|d| match d {
7708 Declaration::Flow(f) => Some(f.name.as_str()),
7709 _ => None,
7710 })
7711 .collect();
7712 assert!(names.contains(&"G"), "G not found among {names:?}");
7713 }
7714
7715 #[test]
7716 fn malformed_declaration_then_clean_intent_recovers() {
7717 let src = "flow @ () { } intent I {}";
7718 let pr = recover(src);
7719 assert!(pr.has_errors());
7720 let kinds: Vec<&str> = pr
7721 .program
7722 .declarations
7723 .iter()
7724 .map(|d| match d {
7725 Declaration::Intent(_) => "intent",
7726 Declaration::Flow(_) => "flow",
7727 _ => "other",
7728 })
7729 .collect();
7730 assert!(kinds.contains(&"intent"), "kinds: {kinds:?}");
7731 }
7732
7733 #[test]
7734 fn recovery_does_not_double_count_a_single_error() {
7735 let src = "flow F() { not_a_step }";
7743 let pr = recover(src);
7744 let outer_ghosts = pr
7745 .errors
7746 .iter()
7747 .filter(|e| e.message.contains("at top level"))
7748 .count();
7749 assert_eq!(outer_ghosts, 0, "ghost errors: {:?}", pr.errors);
7750 }
7751
7752 #[test]
7755 fn three_independent_errors_yield_three_entries() {
7756 let src =
7757 "garbage1 flow F() { } garbage2 flow G() { } garbage3 flow H() { }";
7758 let pr = recover(src);
7759 assert_eq!(pr.errors.len(), 3, "errors: {:?}", pr.errors);
7760 assert_eq!(pr.program.declarations.len(), 3);
7761 }
7762
7763 #[test]
7764 fn all_errors_no_valid_declarations() {
7765 let src = "foo bar baz qux";
7766 let pr = recover(src);
7767 assert!(pr.has_errors());
7768 assert!(pr.program.declarations.is_empty());
7769 }
7770
7771 #[test]
7772 fn errors_recorded_in_source_order() {
7773 let src = "x flow A() { } y flow B() { } z flow C() { }";
7774 let pr = recover(src);
7775 assert_eq!(pr.errors.len(), 3);
7776 let lines: Vec<u32> = pr.errors.iter().map(|e| e.line).collect();
7777 assert!(
7780 lines.windows(2).all(|w| w[0] <= w[1]),
7781 "errors out of order: {lines:?}"
7782 );
7783 }
7784
7785 #[test]
7788 fn sync_to_flow_keyword() {
7789 let src = "garbage flow F() { }";
7790 let pr = recover(src);
7791 assert_eq!(pr.program.declarations.len(), 1);
7792 }
7793
7794 #[test]
7795 fn sync_to_intent_keyword() {
7796 let src = "garbage intent I {}";
7797 let pr = recover(src);
7798 assert_eq!(pr.program.declarations.len(), 1);
7799 }
7800
7801 #[test]
7802 fn sync_to_persona_keyword() {
7803 let src = "garbage persona P { name: \"P\" role: \"R\" }";
7804 let pr = recover(src);
7805 assert!(
7806 pr.program
7807 .declarations
7808 .iter()
7809 .any(|d| matches!(d, Declaration::Persona(_))),
7810 "persona not recovered: decls = {:?}",
7811 pr.program.declarations.len()
7812 );
7813 }
7814
7815 #[test]
7816 fn sync_to_run_keyword() {
7817 let src = "garbage run R { input: { user_message: \"hi\" } }";
7818 let pr = recover(src);
7819 assert!(pr.has_errors());
7821 }
7822
7823 #[test]
7826 fn parse_result_has_errors_and_is_clean_invert() {
7827 let pr_clean = recover("flow F() { }");
7828 assert!(pr_clean.is_clean());
7829 assert!(!pr_clean.has_errors());
7830
7831 let pr_err = recover("garbage");
7832 assert!(!pr_err.is_clean());
7833 assert!(pr_err.has_errors());
7834 }
7835
7836 #[test]
7837 fn parse_result_program_field_holds_partial_program() {
7838 let pr = recover("garbage flow F() { }");
7839 assert!(!pr.program.declarations.is_empty());
7840 }
7841
7842 #[test]
7843 fn parse_result_errors_carry_line_and_column() {
7844 let pr = recover("garbage");
7845 assert!(!pr.errors.is_empty());
7846 let e = &pr.errors[0];
7847 assert!(e.line >= 1);
7848 let _ = e.column;
7851 assert!(!e.message.is_empty());
7852 }
7853
7854 #[test]
7855 fn parse_result_debug_renders() {
7856 let pr = recover("flow F() { }");
7857 let s = format!("{pr:?}");
7858 assert!(s.contains("ParseResult"));
7859 }
7860
7861 #[test]
7864 fn empty_source_is_clean() {
7865 let pr = recover("");
7866 assert!(pr.is_clean());
7867 assert!(pr.program.declarations.is_empty());
7868 }
7869
7870 #[test]
7871 fn whitespace_only_source_is_clean() {
7872 let pr = recover(" \n\n\t \n");
7873 assert!(pr.is_clean());
7874 assert!(pr.program.declarations.is_empty());
7875 }
7876
7877 #[test]
7878 fn only_garbage_does_not_crash() {
7879 let pr = recover("foo bar baz { qux quux } corge { grault }");
7881 assert!(pr.has_errors());
7882 }
7883
7884 #[test]
7885 fn unbalanced_close_brace_does_not_crash() {
7886 let pr = recover("} flow F() { }");
7887 let names: Vec<&str> = pr
7889 .program
7890 .declarations
7891 .iter()
7892 .filter_map(|d| match d {
7893 Declaration::Flow(f) => Some(f.name.as_str()),
7894 _ => None,
7895 })
7896 .collect();
7897 assert!(names.contains(&"F"), "F not recovered: {names:?}");
7898 }
7899
7900 #[test]
7901 fn error_at_eof_does_not_loop() {
7902 let pr = recover("flow F() { ");
7904 let _ = pr.errors.len();
7906 }
7907
7908 #[test]
7909 fn nested_braces_inside_error_still_balance() {
7910 let src = "flow F() { not_a_step { inner } } flow G() { }";
7913 let pr = recover(src);
7914 let names: Vec<&str> = pr
7915 .program
7916 .declarations
7917 .iter()
7918 .filter_map(|d| match d {
7919 Declaration::Flow(f) => Some(f.name.as_str()),
7920 _ => None,
7921 })
7922 .collect();
7923 assert!(names.contains(&"G"), "G not recovered: {names:?}");
7924 }
7925
7926 #[derive(Clone, Copy)]
7935 struct Xorshift(u64);
7936 impl Xorshift {
7937 fn next(&mut self) -> u64 {
7938 let mut x = self.0;
7939 x ^= x << 13;
7940 x ^= x >> 7;
7941 x ^= x << 17;
7942 self.0 = x;
7943 x
7944 }
7945 fn pick<T: Copy>(&mut self, slice: &[T]) -> T {
7946 slice[(self.next() as usize) % slice.len()]
7947 }
7948 }
7949
7950 fn mutate(src: &str, rng: &mut Xorshift) -> String {
7951 let mut bytes: Vec<u8> = src.bytes().collect();
7952 if bytes.is_empty() {
7953 return src.to_string();
7954 }
7955 let op = rng.next() % 4;
7956 let pos = (rng.next() as usize) % bytes.len();
7957 let safe: &[u8] = b"abcdefghijklmnopqrstuvwxyz {}();:,_0123456789";
7961 match op {
7962 0 => {
7963 bytes.remove(pos);
7964 }
7965 1 => {
7966 let b = rng.pick(safe);
7967 bytes.insert(pos, b);
7968 }
7969 2 if pos + 1 < bytes.len() => {
7970 bytes.swap(pos, pos + 1);
7971 }
7972 _ => {
7973 let b = rng.pick(safe);
7974 bytes[pos] = b;
7975 }
7976 }
7977 bytes.retain(|b| b.is_ascii());
7980 String::from_utf8_lossy(&bytes).into_owned()
7981 }
7982
7983 #[test]
7984 fn fuzz_recovery_never_crashes() {
7985 let seed_bases = [
7986 "flow F() { }",
7987 "intent I { }",
7988 "persona P { name: \"P\" role: \"R\" }",
7989 "intent J { ask: \"a\" }",
7990 "type T = String",
7991 ];
7992 for (bucket, base) in (0..100u64).zip(seed_bases.iter().cycle()) {
7994 let mut rng = Xorshift(0x1234_5678_9abc_def0_u64.wrapping_add(bucket));
7995 let mut current = (*base).to_string();
7996 for _ in 0..10 {
7997 current = mutate(¤t, &mut rng);
7998 let toks = match Lexer::new(¤t, "<fuzz>").tokenize() {
8001 Ok(t) => t,
8002 Err(_) => continue,
8003 };
8004 let _pr = Parser::new(toks).parse_with_recovery();
8006 }
8007 }
8008 }
8009
8010 #[test]
8013 fn missing_colon_hint_preserved_under_recovery() {
8014 let src = "flow F() { run R { input { user_message: \"hi\" } } }";
8018 let pr = recover(src);
8019 if !pr.errors.is_empty() {
8023 let any_msg = pr.errors.iter().any(|e| !e.message.is_empty());
8024 assert!(any_msg);
8025 }
8026 }
8027
8028 #[test]
8031 fn recovered_declarations_appear_in_source_order() {
8032 let src = "flow A() { } garbage flow B() { } garbage flow C() { }";
8033 let pr = recover(src);
8034 let names: Vec<&str> = pr
8035 .program
8036 .declarations
8037 .iter()
8038 .filter_map(|d| match d {
8039 Declaration::Flow(f) => Some(f.name.as_str()),
8040 _ => None,
8041 })
8042 .collect();
8043 assert_eq!(names, vec!["A", "B", "C"]);
8044 }
8045}
8046
8047#[cfg(test)]
8055mod fase28_source_context_tests {
8056 use super::*;
8057 use crate::lexer::Lexer;
8058
8059 fn snippet(source: &str, line: u32, column: u32, filename: &str) -> String {
8060 SourceSnippet::new(
8061 source.to_string(),
8062 line,
8063 column,
8064 filename.to_string(),
8065 )
8066 .render()
8067 }
8068
8069 #[test]
8072 fn rustc_style_block_for_middle_line() {
8073 let src = "line one\nline two\nline three\nline four\nline five";
8074 let out = snippet(src, 3, 6, "x.axon");
8075 assert!(out.contains("--> x.axon:3:6"));
8076 assert!(out.contains("1 | line one"));
8077 assert!(out.contains("2 | line two"));
8078 assert!(out.contains("3 | line three"));
8079 assert!(out.contains("4 | line four"));
8080 assert!(out.contains("5 | line five"));
8081 assert!(out.contains("\n | ^"), "out:\n{out}");
8083 }
8084
8085 #[test]
8086 fn caret_column_one_renders_correctly() {
8087 let out = snippet("abc\n", 1, 1, "<source>");
8088 assert!(out.contains("\n | ^"));
8089 }
8090
8091 #[test]
8092 fn first_line_clamps_context_before_to_zero() {
8093 let src = "first\nsecond\nthird\nfourth\nfifth";
8094 let out = snippet(src, 1, 1, "<source>");
8095 assert!(out.contains("1 | first"));
8096 assert!(out.contains("2 | second"));
8097 assert!(out.contains("3 | third"));
8098 assert!(!out.contains("4 | fourth"));
8099 }
8100
8101 #[test]
8102 fn last_line_clamps_context_after_to_eof() {
8103 let src = "first\nsecond\nthird\nfourth\nfifth";
8104 let out = snippet(src, 5, 2, "<source>");
8105 assert!(out.contains("5 | fifth"));
8106 assert!(out.contains("3 | third"));
8107 assert!(out.contains("4 | fourth"));
8108 assert!(!out.contains("2 | second"));
8109 }
8110
8111 #[test]
8112 fn gutter_width_grows_with_line_count() {
8113 let src: String = (1..=12).map(|i| format!("line{i}")).collect::<Vec<_>>().join("\n");
8114 let out = snippet(&src, 12, 1, "<source>");
8115 assert!(out.contains("12 | line12"));
8116 assert!(out.contains("10 | line10"));
8117 }
8118
8119 #[test]
8122 fn empty_source_returns_empty() {
8123 assert_eq!(snippet("", 1, 1, "<source>"), "");
8124 }
8125
8126 #[test]
8127 fn zero_line_returns_empty() {
8128 assert_eq!(snippet("hi", 0, 1, "<source>"), "");
8129 }
8130
8131 #[test]
8132 fn out_of_range_line_returns_empty() {
8133 assert_eq!(snippet("hi", 99, 1, "<source>"), "");
8134 }
8135
8136 #[test]
8137 fn caret_clamps_past_eol() {
8138 let out = snippet("hello", 1, 50, "<source>");
8139 assert!(out.contains("\n | ^"), "out:\n{out}");
8140 }
8141
8142 #[test]
8143 fn unicode_codepoint_count_for_caret_clamp() {
8144 let out = snippet("héllo", 1, 99, "<source>");
8146 assert!(out.contains("\n | ^"), "out:\n{out}");
8147 }
8148
8149 #[test]
8150 fn trailing_newline_does_not_create_phantom_last_line() {
8151 let out = snippet("first\nsecond\n", 2, 1, "<source>");
8152 assert!(!out.contains("3 |"));
8153 assert!(out.contains("2 | second"));
8154 }
8155
8156 fn lex(src: &str) -> Vec<Token> {
8159 Lexer::new(src, "<test>").tokenize().expect("lex")
8160 }
8161
8162 #[test]
8163 fn strict_parse_attaches_snippet_when_source_given() {
8164 let src = "garbage_token\nflow F() { }";
8165 let err = Parser::new(lex(src))
8166 .with_source(src, "x.axon")
8167 .parse()
8168 .expect_err("must error");
8169 assert!(err.source_snippet.is_some());
8170 let display = format!("{err}");
8171 assert!(display.contains("--> x.axon:"), "display: {display}");
8172 }
8173
8174 #[test]
8175 fn strict_parse_no_snippet_when_no_source() {
8176 let src = "garbage_token";
8177 let err = Parser::new(lex(src)).parse().expect_err("must error");
8178 assert!(err.source_snippet.is_none());
8179 let display = format!("{err}");
8180 assert!(!display.contains("\n -->"));
8181 }
8182
8183 #[test]
8184 fn every_recovered_error_has_snippet() {
8185 let src = "garbage1\nflow F() { }\ngarbage2\nflow G() { }";
8186 let result = Parser::new(lex(src))
8187 .with_source(src, "multi.axon")
8188 .parse_with_recovery();
8189 assert!(!result.errors.is_empty());
8190 for err in &result.errors {
8191 assert!(err.source_snippet.is_some());
8192 let display = format!("{err}");
8193 assert!(
8194 display.contains("--> multi.axon:"),
8195 "display: {display}"
8196 );
8197 }
8198 }
8199
8200 #[test]
8201 fn recovery_no_snippet_when_no_source() {
8202 let src = "garbage1 garbage2";
8203 let result = Parser::new(lex(src)).parse_with_recovery();
8204 for err in &result.errors {
8205 assert!(err.source_snippet.is_none());
8206 }
8207 }
8208
8209 #[test]
8210 fn snippet_points_at_correct_line_for_each_error() {
8211 let src = "garbage_a\nflow F() { }\ngarbage_b\nflow G() { }";
8212 let result = Parser::new(lex(src))
8213 .with_source(src, "x")
8214 .parse_with_recovery();
8215 for err in &result.errors {
8216 let sn = err.source_snippet.as_ref().expect("snippet");
8217 assert_eq!(sn.line, err.line);
8218 }
8219 }
8220
8221 #[test]
8224 fn legacy_constructor_still_works() {
8225 let src = "flow F() { }";
8226 let prog = Parser::new(lex(src)).parse().expect("clean");
8227 assert_eq!(prog.declarations.len(), 1);
8228 }
8229
8230 #[test]
8231 fn attach_source_idempotent() {
8232 let err = ParseError {
8233 message: "bad".to_string(),
8234 line: 2,
8235 column: 3,
8236 ..Default::default()
8237 };
8238 let err2 = err.clone().attach_source("a\nb\nc\n", "f.axon");
8239 let first = format!("{err2}");
8240 let err3 = err.attach_source("a\nb\nc\n", "f.axon");
8241 let second = format!("{err3}");
8242 assert_eq!(first, second);
8243 }
8244
8245 #[test]
8246 fn attach_source_noop_when_line_zero() {
8247 let err = ParseError {
8248 message: "bad".to_string(),
8249 line: 0,
8250 column: 0,
8251 ..Default::default()
8252 };
8253 let err = err.attach_source("a\nb\nc\n", "f.axon");
8254 assert!(err.source_snippet.is_none());
8255 }
8256
8257 #[test]
8263 fn golden_simple_three_line_block() {
8264 let src = "alpha\nbeta\ngamma";
8265 let out = snippet(src, 2, 3, "g.axon");
8266 let expected = concat!(
8270 " --> g.axon:2:3\n",
8271 " |\n",
8272 "1 | alpha\n",
8273 "2 | beta\n",
8274 " | ^\n",
8275 "3 | gamma",
8276 );
8277 assert_eq!(out, expected);
8278 }
8279
8280 #[test]
8281 fn golden_first_line_caret() {
8282 let src = "abc\ndef\n";
8283 let out = snippet(src, 1, 1, "x");
8284 let expected = concat!(
8285 " --> x:1:1\n",
8286 " |\n",
8287 "1 | abc\n",
8288 " | ^\n",
8289 "2 | def",
8290 );
8291 assert_eq!(out, expected);
8292 }
8293
8294 #[test]
8295 fn golden_two_digit_gutter() {
8296 let src: String = (1..=11)
8297 .map(|i| format!("L{i}"))
8298 .collect::<Vec<_>>()
8299 .join("\n");
8300 let out = snippet(&src, 10, 2, "big");
8301 let expected = concat!(
8302 " --> big:10:2\n",
8303 " |\n",
8304 " 8 | L8\n",
8305 " 9 | L9\n",
8306 "10 | L10\n",
8307 " | ^\n",
8308 "11 | L11",
8309 );
8310 assert_eq!(out, expected);
8311 }
8312}
8313
8314#[cfg(test)]
8321mod fase28_smart_suggest_parser_tests {
8322 use super::*;
8323 use crate::lexer::Lexer;
8324
8325 fn lex(src: &str) -> Vec<Token> {
8326 Lexer::new(src, "<test>").tokenize().expect("lex")
8327 }
8328
8329 #[test]
8330 fn top_level_typo_suggests_flow() {
8331 let src = "flwo F() { }";
8332 let err = Parser::new(lex(src)).parse().expect_err("must error");
8333 assert!(
8334 err.message.contains("Did you mean `flow`?"),
8335 "msg: {}",
8336 err.message
8337 );
8338 }
8339
8340 #[test]
8341 fn top_level_unknown_far_no_suggestion() {
8342 let src = "qwerty F() { }";
8343 let err = Parser::new(lex(src)).parse().expect_err("must error");
8344 assert!(
8345 !err.message.contains("Did you mean"),
8346 "msg: {}",
8347 err.message
8348 );
8349 }
8350
8351 #[test]
8352 fn flow_body_typo_suggests_step() {
8353 let src = "flow F() { stepp S {} }";
8354 let err = Parser::new(lex(src)).parse().expect_err("must error");
8355 assert!(
8356 err.message.contains("Did you mean `step`"),
8357 "msg: {}",
8358 err.message
8359 );
8360 }
8361
8362 #[test]
8363 fn flow_body_typo_suggests_reason() {
8364 let src = "flow F() { reasn R {} }";
8365 let err = Parser::new(lex(src)).parse().expect_err("must error");
8366 assert!(
8367 err.message.contains("Did you mean `reason`?"),
8368 "msg: {}",
8369 err.message
8370 );
8371 }
8372
8373 #[test]
8374 fn recovery_mode_carries_hint() {
8375 let src = "flwo F() { }";
8376 let result = Parser::new(lex(src)).parse_with_recovery();
8377 assert!(
8378 result
8379 .errors
8380 .iter()
8381 .any(|e| e.message.contains("Did you mean `flow`?")),
8382 "errors: {:?}",
8383 result.errors
8384 );
8385 }
8386}
8387
8388#[cfg(test)]
8391mod fase35m_mutate_purge_where_tests {
8392 use super::*;
8393
8394 fn parse(src: &str) -> Program {
8395 let tokens = crate::lexer::Lexer::new(src, "<test>")
8396 .tokenize()
8397 .expect("lex");
8398 Parser::new(tokens).parse().expect("parse")
8399 }
8400
8401 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
8402 for d in &prog.declarations {
8403 if let Declaration::Flow(f) = d {
8404 if f.name == flow {
8405 return f.body.first().expect("flow has at least one step");
8406 }
8407 }
8408 }
8409 panic!("flow `{flow}` not found");
8410 }
8411
8412 #[test]
8413 fn mutate_captures_its_where_clause() {
8414 let prog =
8417 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
8418 match first_step(&prog, "F") {
8419 FlowStep::Mutate(m) => {
8420 assert_eq!(m.store_name, "accounts");
8421 assert_eq!(m.where_expr, "id = 1");
8422 }
8423 other => panic!("expected Mutate, got {other:?}"),
8424 }
8425 }
8426
8427 #[test]
8428 fn purge_captures_its_where_clause() {
8429 let prog =
8430 parse("flow F() -> Unit { purge logs { where: \"ts < 100\" } }");
8431 match first_step(&prog, "F") {
8432 FlowStep::Purge(p) => {
8433 assert_eq!(p.store_name, "logs");
8434 assert_eq!(p.where_expr, "ts < 100");
8435 }
8436 other => panic!("expected Purge, got {other:?}"),
8437 }
8438 }
8439
8440 #[test]
8441 fn mutate_without_a_where_block_is_a_whole_store_op() {
8442 let prog = parse("flow F() -> Unit { mutate accounts }");
8445 match first_step(&prog, "F") {
8446 FlowStep::Mutate(m) => {
8447 assert_eq!(m.store_name, "accounts");
8448 assert_eq!(m.where_expr, "");
8449 }
8450 other => panic!("expected Mutate, got {other:?}"),
8451 }
8452 }
8453}
8454
8455#[cfg(test)]
8458mod fase35o_persist_fields_tests {
8459 use super::*;
8460
8461 fn parse(src: &str) -> Program {
8462 let tokens = crate::lexer::Lexer::new(src, "<test>")
8463 .tokenize()
8464 .expect("lex");
8465 Parser::new(tokens).parse().expect("parse")
8466 }
8467
8468 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
8469 for d in &prog.declarations {
8470 if let Declaration::Flow(f) = d {
8471 if f.name == flow {
8472 return f.body.first().expect("flow has at least one step");
8473 }
8474 }
8475 }
8476 panic!("flow `{flow}` not found");
8477 }
8478
8479 #[test]
8480 fn persist_captures_its_field_block() {
8481 let prog = parse(
8485 "flow F() -> Unit { persist into chat_history { \
8486 session_id: \"${session_id}\" sender: \"user\" \
8487 content: \"${message}\" } }",
8488 );
8489 match first_step(&prog, "F") {
8490 FlowStep::Persist(p) => {
8491 assert_eq!(p.store_name, "chat_history");
8492 assert_eq!(
8493 p.fields,
8494 vec![
8495 ("session_id".to_string(), "${session_id}".to_string()),
8496 ("sender".to_string(), "user".to_string()),
8497 ("content".to_string(), "${message}".to_string()),
8498 ]
8499 );
8500 }
8501 other => panic!("expected Persist, got {other:?}"),
8502 }
8503 }
8504
8505 #[test]
8506 fn persist_without_a_block_keeps_the_user_bindings_fallback() {
8507 let prog = parse("flow F() -> Unit { persist events }");
8510 match first_step(&prog, "F") {
8511 FlowStep::Persist(p) => {
8512 assert_eq!(p.store_name, "events");
8513 assert!(p.fields.is_empty());
8514 }
8515 other => panic!("expected Persist, got {other:?}"),
8516 }
8517 }
8518
8519 #[test]
8520 fn persist_accepts_the_optional_into_connector() {
8521 let with =
8524 parse("flow F() -> Unit { persist into accounts { id: \"1\" } }");
8525 let without =
8526 parse("flow F() -> Unit { persist accounts { id: \"1\" } }");
8527 for prog in [&with, &without] {
8528 match first_step(prog, "F") {
8529 FlowStep::Persist(p) => assert_eq!(p.store_name, "accounts"),
8530 other => panic!("expected Persist, got {other:?}"),
8531 }
8532 }
8533 }
8534
8535 #[test]
8536 fn persist_into_without_a_block_resolves_the_store_name() {
8537 let prog = parse("flow F() -> Unit { persist into events }");
8540 match first_step(&prog, "F") {
8541 FlowStep::Persist(p) => {
8542 assert_eq!(p.store_name, "events");
8543 assert!(p.fields.is_empty());
8544 }
8545 other => panic!("expected Persist, got {other:?}"),
8546 }
8547 }
8548
8549 #[test]
8550 fn persist_fields_lower_into_the_ir() {
8551 let prog = parse(
8554 "flow F() -> Unit { persist into chat { content: \"${msg}\" } }",
8555 );
8556 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
8557 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
8558 match flow.steps.first().expect("one step") {
8559 crate::ir_nodes::IRFlowNode::Persist(p) => {
8560 assert_eq!(p.store_name, "chat");
8561 assert_eq!(
8562 p.fields,
8563 vec![("content".to_string(), "${msg}".to_string())]
8564 );
8565 }
8566 other => panic!("expected IRFlowNode::Persist, got {other:?}"),
8567 }
8568 }
8569}
8570
8571#[cfg(test)]
8574mod fase35p_mutate_fields_tests {
8575 use super::*;
8576
8577 fn parse(src: &str) -> Program {
8578 let tokens = crate::lexer::Lexer::new(src, "<test>")
8579 .tokenize()
8580 .expect("lex");
8581 Parser::new(tokens).parse().expect("parse")
8582 }
8583
8584 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
8585 for d in &prog.declarations {
8586 if let Declaration::Flow(f) = d {
8587 if f.name == flow {
8588 return f.body.first().expect("flow has at least one step");
8589 }
8590 }
8591 }
8592 panic!("flow `{flow}` not found");
8593 }
8594
8595 #[test]
8596 fn mutate_captures_its_set_field_block() {
8597 let prog = parse(
8601 "flow F() -> Unit { mutate accounts { where: \"id = ${id}\" \
8602 balance: \"${new_balance}\" status: \"active\" } }",
8603 );
8604 match first_step(&prog, "F") {
8605 FlowStep::Mutate(m) => {
8606 assert_eq!(m.store_name, "accounts");
8607 assert_eq!(m.where_expr, "id = ${id}");
8608 assert_eq!(
8609 m.fields,
8610 vec![
8611 ("balance".to_string(), "${new_balance}".to_string()),
8612 ("status".to_string(), "active".to_string()),
8613 ]
8614 );
8615 }
8616 other => panic!("expected Mutate, got {other:?}"),
8617 }
8618 }
8619
8620 #[test]
8621 fn mutate_where_only_block_has_no_set_fields() {
8622 let prog =
8625 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
8626 match first_step(&prog, "F") {
8627 FlowStep::Mutate(m) => {
8628 assert_eq!(m.where_expr, "id = 1");
8629 assert!(m.fields.is_empty());
8630 }
8631 other => panic!("expected Mutate, got {other:?}"),
8632 }
8633 }
8634
8635 #[test]
8636 fn mutate_with_no_block_is_a_whole_store_op() {
8637 let prog = parse("flow F() -> Unit { mutate accounts }");
8640 match first_step(&prog, "F") {
8641 FlowStep::Mutate(m) => {
8642 assert_eq!(m.store_name, "accounts");
8643 assert_eq!(m.where_expr, "");
8644 assert!(m.fields.is_empty());
8645 }
8646 other => panic!("expected Mutate, got {other:?}"),
8647 }
8648 }
8649
8650 #[test]
8651 fn mutate_fields_lower_into_the_ir() {
8652 let prog = parse(
8653 "flow F() -> Unit { mutate t { where: \"id = 1\" v: \"${x}\" } }",
8654 );
8655 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
8656 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
8657 match flow.steps.first().expect("one step") {
8658 crate::ir_nodes::IRFlowNode::Mutate(m) => {
8659 assert_eq!(m.where_expr, "id = 1");
8660 assert_eq!(
8661 m.fields,
8662 vec![("v".to_string(), "${x}".to_string())]
8663 );
8664 }
8665 other => panic!("expected IRFlowNode::Mutate, got {other:?}"),
8666 }
8667 }
8668}
8669