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::Window(n) => {
114 n.leading_trivia = leading;
115 n.trailing_trivia = trailing;
116 }
117 Declaration::Pix(n) => {
118 n.leading_trivia = leading;
119 n.trailing_trivia = trailing;
120 }
121 Declaration::Ledger(n) => {
122 n.leading_trivia = leading;
123 n.trailing_trivia = trailing;
124 }
125 Declaration::Psyche(n) => {
126 n.leading_trivia = leading;
127 n.trailing_trivia = trailing;
128 }
129 Declaration::Corpus(n) => {
130 n.leading_trivia = leading;
131 n.trailing_trivia = trailing;
132 }
133 Declaration::Dataspace(n) => {
134 n.leading_trivia = leading;
135 n.trailing_trivia = trailing;
136 }
137 Declaration::Ots(n) => {
138 n.leading_trivia = leading;
139 n.trailing_trivia = trailing;
140 }
141 Declaration::Mandate(n) => {
142 n.leading_trivia = leading;
143 n.trailing_trivia = trailing;
144 }
145 Declaration::Compute(n) => {
146 n.leading_trivia = leading;
147 n.trailing_trivia = trailing;
148 }
149 Declaration::Daemon(n) => {
150 n.leading_trivia = leading;
151 n.trailing_trivia = trailing;
152 }
153 Declaration::Extension(n) => {
154 n.leading_trivia = leading;
155 n.trailing_trivia = trailing;
156 }
157 Declaration::AxonStore(n) => {
158 n.leading_trivia = leading;
159 n.trailing_trivia = trailing;
160 }
161 Declaration::AxonEndpoint(n) => {
162 n.leading_trivia = leading;
163 n.trailing_trivia = trailing;
164 }
165 Declaration::Resource(n) => {
166 n.leading_trivia = leading;
167 n.trailing_trivia = trailing;
168 }
169 Declaration::Fabric(n) => {
170 n.leading_trivia = leading;
171 n.trailing_trivia = trailing;
172 }
173 Declaration::Manifest(n) => {
174 n.leading_trivia = leading;
175 n.trailing_trivia = trailing;
176 }
177 Declaration::Observe(n) => {
178 n.leading_trivia = leading;
179 n.trailing_trivia = trailing;
180 }
181 Declaration::Reconcile(n) => {
182 n.leading_trivia = leading;
183 n.trailing_trivia = trailing;
184 }
185 Declaration::Lease(n) => {
186 n.leading_trivia = leading;
187 n.trailing_trivia = trailing;
188 }
189 Declaration::Ensemble(n) => {
190 n.leading_trivia = leading;
191 n.trailing_trivia = trailing;
192 }
193 Declaration::Session(n) => {
194 n.leading_trivia = leading;
195 n.trailing_trivia = trailing;
196 }
197 Declaration::Topology(n) => {
198 n.leading_trivia = leading;
199 n.trailing_trivia = trailing;
200 }
201 Declaration::Immune(n) => {
202 n.leading_trivia = leading;
203 n.trailing_trivia = trailing;
204 }
205 Declaration::Reflex(n) => {
206 n.leading_trivia = leading;
207 n.trailing_trivia = trailing;
208 }
209 Declaration::Heal(n) => {
210 n.leading_trivia = leading;
211 n.trailing_trivia = trailing;
212 }
213 Declaration::Component(n) => {
214 n.leading_trivia = leading;
215 n.trailing_trivia = trailing;
216 }
217 Declaration::View(n) => {
218 n.leading_trivia = leading;
219 n.trailing_trivia = trailing;
220 }
221 Declaration::Channel(n) => {
222 n.leading_trivia = leading;
223 n.trailing_trivia = trailing;
224 }
225 Declaration::Socket(n) => {
226 n.leading_trivia = leading;
227 n.trailing_trivia = trailing;
228 }
229 Declaration::Upstream(n) => {
230 n.leading_trivia = leading;
231 n.trailing_trivia = trailing;
232 }
233 Declaration::Voice(n) => {
234 n.leading_trivia = leading;
235 n.trailing_trivia = trailing;
236 }
237 Declaration::Cors(n) => {
238 n.leading_trivia = leading;
239 n.trailing_trivia = trailing;
240 }
241 Declaration::Credential(n) => {
242 n.leading_trivia = leading;
243 n.trailing_trivia = trailing;
244 }
245 Declaration::Cache(n) => {
246 n.leading_trivia = leading;
247 n.trailing_trivia = trailing;
248 }
249 Declaration::Savant(n) => {
250 n.leading_trivia = leading;
251 n.trailing_trivia = trailing;
252 }
253 Declaration::Synth(n) => {
254 n.leading_trivia = leading;
255 n.trailing_trivia = trailing;
256 }
257 Declaration::Scope(n) => {
258 n.leading_trivia = leading;
259 n.trailing_trivia = trailing;
260 }
261 Declaration::Observable(n) => {
262 n.leading_trivia = leading;
263 n.trailing_trivia = trailing;
264 }
265 Declaration::Witness(n) => {
266 n.leading_trivia = leading;
267 n.trailing_trivia = trailing;
268 }
269 Declaration::Document(n) => {
270 n.leading_trivia = leading;
271 n.trailing_trivia = trailing;
272 }
273 Declaration::Deliver(n) => {
274 n.leading_trivia = leading;
275 n.trailing_trivia = trailing;
276 }
277 Declaration::Generic(n) => {
278 n.leading_trivia = leading;
279 n.trailing_trivia = trailing;
280 }
281 }
282}
283
284pub const SOURCE_CONTEXT_LINES_BEFORE: usize = 2;
291pub const SOURCE_CONTEXT_LINES_AFTER: usize = 2;
292
293#[derive(Debug, Clone)]
304pub struct SourceSnippet {
305 pub source: String,
306 pub line: u32,
307 pub column: u32,
308 pub filename: String,
309 pub context_before: usize,
310 pub context_after: usize,
311}
312
313impl SourceSnippet {
314 pub fn new(source: String, line: u32, column: u32, filename: String) -> Self {
316 Self {
317 source,
318 line,
319 column,
320 filename,
321 context_before: SOURCE_CONTEXT_LINES_BEFORE,
322 context_after: SOURCE_CONTEXT_LINES_AFTER,
323 }
324 }
325
326 #[must_use]
333 pub fn render(&self) -> String {
334 if self.source.is_empty() || self.line < 1 {
335 return String::new();
336 }
337 let raw: Vec<&str> = self.source.split('\n').collect();
338 let lines: Vec<&str> = if raw.last() == Some(&"") {
341 raw[..raw.len() - 1].to_vec()
342 } else {
343 raw
344 };
345 if lines.is_empty() || self.line as usize > lines.len() {
346 return String::new();
347 }
348
349 let line_idx = self.line as usize;
350 let start = line_idx.saturating_sub(self.context_before).max(1);
351 let end = (line_idx + self.context_after).min(lines.len());
352
353 let gutter = end.to_string().len();
354 let empty_gutter = " ".repeat(gutter);
355
356 let mut out: Vec<String> = Vec::with_capacity(end - start + 4);
357 out.push(format!(
358 "{empty_gutter} --> {}:{}:{}",
359 self.filename, self.line, self.column
360 ));
361 out.push(format!("{empty_gutter} |"));
362 for n in start..=end {
363 let line_text = lines[n - 1];
364 out.push(format!("{n:>gutter$} | {line_text}", gutter = gutter));
365 if n == line_idx {
366 let line_len = line_text.chars().count();
367 let col = (self.column as usize).clamp(1, line_len + 1);
368 out.push(format!(
369 "{empty_gutter} | {pad}^",
370 pad = " ".repeat(col - 1)
371 ));
372 }
373 }
374 out.join("\n")
375 }
376}
377
378#[derive(Debug, Clone, Default)]
379pub struct ParseError {
380 pub message: String,
381 pub line: u32,
382 pub column: u32,
383 pub source_snippet: Option<SourceSnippet>,
390}
391
392impl ParseError {
393 #[must_use]
397 pub fn attach_source(mut self, source: &str, filename: &str) -> Self {
398 if self.line >= 1 {
399 self.source_snippet = Some(SourceSnippet::new(
400 source.to_string(),
401 self.line,
402 self.column,
403 filename.to_string(),
404 ));
405 }
406 self
407 }
408}
409
410impl std::fmt::Display for ParseError {
411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412 write!(f, "[line {}:{}] {}", self.line, self.column, self.message)?;
413 if let Some(snippet) = &self.source_snippet {
414 let block = snippet.render();
415 if !block.is_empty() {
416 write!(f, "\n{block}")?;
417 }
418 }
419 Ok(())
420 }
421}
422
423impl std::error::Error for ParseError {}
424
425#[derive(Debug)]
443pub struct ParseResult {
444 pub program: Program,
445 pub errors: Vec<ParseError>,
446}
447
448impl ParseResult {
449 #[inline]
454 #[must_use]
455 pub fn has_errors(&self) -> bool {
456 !self.errors.is_empty()
457 }
458
459 #[inline]
462 #[must_use]
463 pub fn is_clean(&self) -> bool {
464 self.errors.is_empty()
465 }
466}
467
468#[inline]
482const fn is_top_level_decl_kw_for_recovery(tt: &TokenType) -> bool {
483 matches!(
484 tt,
485 TokenType::Import
486 | TokenType::Persona
487 | TokenType::Context
488 | TokenType::Anchor
489 | TokenType::Memory
490 | TokenType::Tool
491 | TokenType::Type
492 | TokenType::Flow
493 | TokenType::Intent
494 | TokenType::Run
495 | TokenType::Let
496 | TokenType::Know
497 | TokenType::Believe
498 | TokenType::Speculate
499 | TokenType::Doubt
500 | TokenType::Lambda
501 | TokenType::Agent
502 | TokenType::Shield
503 | TokenType::Pix
504 | TokenType::Ledger
505 | TokenType::Psyche
506 | TokenType::Corpus
507 | TokenType::Dataspace
508 | TokenType::Ots
509 | TokenType::Mandate
510 | TokenType::Compute
511 | TokenType::Daemon
512 | TokenType::Savant
514 | TokenType::Synth
515 | TokenType::Scope
517 | TokenType::AxonStore
518 | TokenType::AxonEndpoint
519 | TokenType::Resource
520 | TokenType::Fabric
521 | TokenType::Manifest
522 | TokenType::Observe
523 | TokenType::Reconcile
524 | TokenType::Lease
525 | TokenType::Ensemble
526 | TokenType::Session
527 | TokenType::Topology
528 | TokenType::Immune
529 | TokenType::Reflex
530 | TokenType::Heal
531 | TokenType::Component
532 | TokenType::View
533 | TokenType::Channel
534 | TokenType::Ingest
535 | TokenType::Persist
536 | TokenType::Retrieve
537 | TokenType::Mutate
538 | TokenType::Purge
539 | TokenType::Transact
540 | TokenType::Mcp
541 )
542}
543
544pub const AXONENDPOINT_TRANSPORT_VALUES: &[&str] = &["json", "sse", "ndjson"];
557
558pub const AXONENDPOINT_TRANSPORT_DIALECTS: &[&str] =
602 &["axon", "openai", "kimi", "glm", "anthropic"];
603
604pub const AXONENDPOINT_KEEPALIVE_VALUES: &[&str] = &["5s", "15s", "30s", "60s"];
606
607pub const AXONENDPOINT_METHOD_VALUES: &[&str] =
621 &["GET", "POST", "PUT", "DELETE", "PATCH", "QUERY"];
622
623pub const AXONENDPOINT_BACKEND_VALUES: &[&str] = &[
644 "anthropic",
645 "auto",
646 "gemini",
647 "glm",
648 "kimi",
649 "ollama",
650 "openai",
651 "openrouter",
652 "stub",
653];
654
655#[inline]
656fn axonendpoint_is_valid_transport(s: &str) -> bool {
657 AXONENDPOINT_TRANSPORT_VALUES.iter().any(|&v| v == s)
658}
659
660#[inline]
661fn axonendpoint_is_valid_method(s: &str) -> bool {
662 AXONENDPOINT_METHOD_VALUES.iter().any(|&v| v == s)
663}
664
665#[inline]
666fn axonendpoint_is_valid_backend(s: &str) -> bool {
667 AXONENDPOINT_BACKEND_VALUES.iter().any(|&v| v == s)
668}
669
670#[inline]
671fn axonendpoint_is_valid_keepalive(s: &str) -> bool {
672 AXONENDPOINT_KEEPALIVE_VALUES.iter().any(|&v| v == s)
673}
674
675pub const AXONENDPOINT_QUERY_PARAM_TYPES: &[&str] =
690 &["Text", "Int", "Float", "Bool", "Uuid"];
691
692#[inline]
695pub(crate) fn axonendpoint_is_valid_query_param_type(s: &str) -> bool {
696 AXONENDPOINT_QUERY_PARAM_TYPES.iter().any(|&v| v == s)
697}
698
699pub(crate) fn extract_path_param_names(path: &str) -> Result<Vec<String>, String> {
726 let mut out: Vec<String> = Vec::new();
727 let bytes = path.as_bytes();
728 let mut i = 0;
729 while i < bytes.len() {
730 if bytes[i] != b'{' {
731 i += 1;
732 continue;
733 }
734 let start = i + 1;
737 let mut end = start;
738 while end < bytes.len() && bytes[end] != b'}' {
739 end += 1;
740 }
741 if end == bytes.len() {
742 break;
745 }
746 let raw = &path[start..end];
747 let valid = !raw.is_empty()
749 && raw.bytes().enumerate().all(|(idx, b)| {
750 if idx == 0 {
751 b.is_ascii_alphabetic() || b == b'_'
752 } else {
753 b.is_ascii_alphanumeric() || b == b'_'
754 }
755 });
756 if valid {
757 let name = raw.to_string();
758 if out.iter().any(|existing| existing == &name) {
759 return Err(name);
760 }
761 out.push(name);
762 }
763 i = end + 1;
764 }
765 Ok(out)
766}
767
768pub fn is_valid_capability_slug(slug: &str) -> bool {
782 if slug.is_empty() {
783 return false;
784 }
785 for segment in slug.split('.') {
786 if !is_valid_slug_segment(segment) {
787 return false;
788 }
789 }
790 true
791}
792
793fn is_valid_slug_segment(seg: &str) -> bool {
794 let mut chars = seg.chars();
795 let first = match chars.next() {
796 Some(c) => c,
797 None => return false,
798 };
799 if !first.is_ascii_lowercase() {
800 return false;
801 }
802 chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
803}
804
805#[cfg(test)]
815mod query_param_catalog_tests {
816 use super::{axonendpoint_is_valid_query_param_type, AXONENDPOINT_QUERY_PARAM_TYPES};
817
818 #[test]
819 fn accepts_every_catalog_entry() {
820 for ty in AXONENDPOINT_QUERY_PARAM_TYPES {
821 assert!(
822 axonendpoint_is_valid_query_param_type(ty),
823 "catalog entry `{ty}` must validate"
824 );
825 }
826 }
827
828 #[test]
829 fn rejects_off_catalog_types() {
830 for off in &[
831 "Timestamp", "Date",
833 "DateTime",
834 "List<Text>", "Jsonb", "Bytea",
837 "text", "TEXT",
839 "Number", "", " ", ] {
843 assert!(
844 !axonendpoint_is_valid_query_param_type(off),
845 "off-catalog `{off}` must reject"
846 );
847 }
848 }
849
850 #[test]
851 fn catalog_size_matches_design() {
852 assert_eq!(AXONENDPOINT_QUERY_PARAM_TYPES.len(), 5);
856 }
857}
858
859#[cfg(test)]
860mod query_param_parser_tests {
861 use crate::lexer::Lexer;
862 use crate::parser::Parser;
863
864 fn parse_endpoint_source(src: &str) -> Result<crate::ast::AxonEndpointDefinition, String> {
865 let tokens = Lexer::new(src, "test.axon")
866 .tokenize()
867 .map_err(|e| format!("lex: {}", e.message))?;
868 let mut parser = Parser::new(tokens);
869 let program = parser.parse().map_err(|e| format!("parse: {}", e.message))?;
870 program
871 .declarations
872 .into_iter()
873 .find_map(|d| match d {
874 crate::ast::Declaration::AxonEndpoint(e) => Some(e),
875 _ => None,
876 })
877 .ok_or_else(|| "no axonendpoint in program".to_string())
878 }
879
880 #[test]
881 fn endpoint_with_no_query_block_keeps_empty_vec() {
882 let src = r#"
883 axonendpoint write_secret {
884 method: POST
885 path: "/api/users"
886 body: SecretWriteRequest
887 execute: WriteSecret
888 }
889 "#;
890 let ep = parse_endpoint_source(src).expect("parses");
891 assert!(
892 ep.query_params.is_empty(),
893 "D5 — no `query:` block ⇒ empty query_params"
894 );
895 }
896
897 #[test]
898 fn single_query_param_required() {
899 let src = r#"
900 axonendpoint list_users {
901 method: GET
902 path: "/api/users"
903 query: { status: Text }
904 execute: ListUsers
905 }
906 "#;
907 let ep = parse_endpoint_source(src).expect("parses");
908 assert_eq!(ep.query_params.len(), 1);
909 assert_eq!(ep.query_params[0].name, "status");
910 assert_eq!(ep.query_params[0].type_expr.name, "Text");
911 assert!(!ep.query_params[0].type_expr.optional);
912 }
913
914 #[test]
915 fn optional_query_param_via_question_suffix() {
916 let src = r#"
917 axonendpoint list_users {
918 method: GET
919 path: "/api/users"
920 query: { limit: Int? }
921 execute: ListUsers
922 }
923 "#;
924 let ep = parse_endpoint_source(src).expect("parses");
925 assert_eq!(ep.query_params.len(), 1);
926 assert_eq!(ep.query_params[0].name, "limit");
927 assert_eq!(ep.query_params[0].type_expr.name, "Int");
928 assert!(
929 ep.query_params[0].type_expr.optional,
930 "`?` suffix sets optional"
931 );
932 }
933
934 #[test]
935 fn multiple_query_params_preserve_declaration_order() {
936 let src = r#"
937 axonendpoint search {
938 method: GET
939 path: "/api/search"
940 query: { q: Text, page: Int?, limit: Int?, exact: Bool? }
941 execute: Search
942 }
943 "#;
944 let ep = parse_endpoint_source(src).expect("parses");
945 let names: Vec<&str> = ep.query_params.iter().map(|f| f.name.as_str()).collect();
946 assert_eq!(names, vec!["q", "page", "limit", "exact"]);
947 let types: Vec<&str> = ep
948 .query_params
949 .iter()
950 .map(|f| f.type_expr.name.as_str())
951 .collect();
952 assert_eq!(types, vec!["Text", "Int", "Int", "Bool"]);
953 let optionals: Vec<bool> = ep
954 .query_params
955 .iter()
956 .map(|f| f.type_expr.optional)
957 .collect();
958 assert_eq!(optionals, vec![false, true, true, true]);
959 }
960
961 #[test]
962 fn duplicate_query_param_is_parse_error() {
963 let src = r#"
964 axonendpoint bad {
965 method: GET
966 path: "/api/x"
967 query: { name: Text, name: Int? }
968 execute: Bad
969 }
970 "#;
971 let err = parse_endpoint_source(src).expect_err("must fail");
972 assert!(
973 err.contains("duplicate query param 'name'"),
974 "error must name the duplicate. Got: {err}"
975 );
976 }
977
978 #[test]
979 fn off_catalog_type_with_smart_suggest_hint() {
980 let src = r#"
985 axonendpoint bad {
986 method: GET
987 path: "/api/x"
988 query: { value: Strng }
989 execute: Bad
990 }
991 "#;
992 let err = parse_endpoint_source(src).expect_err("must fail");
993 assert!(
994 err.contains("unsupported type 'Strng'"),
995 "error must name the bad type. Got: {err}"
996 );
997 assert!(
998 err.contains("Expected one of: Text | Int | Float | Bool | Uuid"),
999 "error must list the closed catalog. Got: {err}"
1000 );
1001 }
1002
1003 #[test]
1004 fn close_typo_gets_did_you_mean_hint() {
1005 let src = r#"
1008 axonendpoint bad {
1009 method: GET
1010 path: "/api/x"
1011 query: { value: Txt }
1012 execute: Bad
1013 }
1014 "#;
1015 let err = parse_endpoint_source(src).expect_err("must fail");
1016 assert!(
1017 err.contains("Did you mean") && err.contains("`Text`"),
1018 "smart-suggest must hint `Text`. Got: {err}"
1019 );
1020 }
1021
1022 #[test]
1023 fn every_catalog_type_parses_cleanly() {
1024 for ty in &["Text", "Int", "Float", "Bool", "Uuid"] {
1026 let src = format!(
1027 r#"
1028 axonendpoint x {{
1029 method: GET
1030 path: "/api/x"
1031 query: {{ v: {ty} }}
1032 execute: X
1033 }}
1034 "#
1035 );
1036 let ep = parse_endpoint_source(&src)
1037 .unwrap_or_else(|e| panic!("`{ty}` should parse: {e}"));
1038 assert_eq!(ep.query_params[0].type_expr.name, *ty);
1039 }
1040 }
1041
1042 #[test]
1043 fn comma_optional_between_params() {
1044 let src = r#"
1048 axonendpoint x {
1049 method: GET
1050 path: "/api/x"
1051 query: { a: Text b: Int? }
1052 execute: X
1053 }
1054 "#;
1055 let ep = parse_endpoint_source(src).expect("parses without commas");
1056 assert_eq!(ep.query_params.len(), 2);
1057 }
1058
1059 #[test]
1062 fn double_query_block_is_parse_error() {
1063 let src = r#"
1067 axonendpoint x {
1068 method: GET
1069 path: "/api/x"
1070 query: { a: Text }
1071 query: { b: Int? }
1072 execute: X
1073 }
1074 "#;
1075 let err = parse_endpoint_source(src).expect_err("must fail");
1076 assert!(
1077 err.contains("declares `query: { … }` more than once"),
1078 "error must call out the duplicate block. Got: {err}"
1079 );
1080 assert!(
1081 err.contains("combine all params into a single block"),
1082 "error must hint the canonical fix. Got: {err}"
1083 );
1084 }
1085
1086 #[test]
1087 fn optional_generic_type_is_parse_error_with_canonical_hint() {
1088 let src = r#"
1092 axonendpoint x {
1093 method: GET
1094 path: "/api/x"
1095 query: { value: Optional<Text> }
1096 execute: X
1097 }
1098 "#;
1099 let err = parse_endpoint_source(src).expect_err("must fail");
1100 assert!(
1101 err.contains("generic type `Optional<Text>`"),
1102 "error must name the generic type literally. Got: {err}"
1103 );
1104 assert!(
1105 err.contains("Use `Text?` (the `?` suffix)"),
1106 "error must hint the canonical `Text?` syntax. Got: {err}"
1107 );
1108 }
1109
1110 #[test]
1111 fn list_generic_type_is_parse_error_with_deferral_hint() {
1112 let src = r#"
1117 axonendpoint x {
1118 method: GET
1119 path: "/api/x"
1120 query: { tags: List<Text> }
1121 execute: X
1122 }
1123 "#;
1124 let err = parse_endpoint_source(src).expect_err("must fail");
1125 assert!(
1126 err.contains("generic type `List<Text>`"),
1127 "error must name the generic type. Got: {err}"
1128 );
1129 assert!(
1130 err.contains("Multi-value query params")
1131 && err.contains("honest-deferred"),
1132 "error must mention the multi-value deferral. Got: {err}"
1133 );
1134 }
1135
1136 #[test]
1137 fn other_generic_types_caught_generically() {
1138 let src = r#"
1142 axonendpoint x {
1143 method: GET
1144 path: "/api/x"
1145 query: { value: Stream<Int> }
1146 execute: X
1147 }
1148 "#;
1149 let err = parse_endpoint_source(src).expect_err("must fail");
1150 assert!(
1151 err.contains("generic type `Stream<Int>`"),
1152 "error must name the generic type. Got: {err}"
1153 );
1154 assert!(
1155 err.contains("Text | Int | Float | Bool | Uuid"),
1156 "error must list the closed catalog. Got: {err}"
1157 );
1158 }
1159
1160 #[test]
1161 fn uuid_optional_parses_cleanly() {
1162 let src = r#"
1165 axonendpoint find {
1166 method: GET
1167 path: "/api/x"
1168 query: { after: Uuid? }
1169 execute: Find
1170 }
1171 "#;
1172 let ep = parse_endpoint_source(src).expect("parses");
1173 assert_eq!(ep.query_params.len(), 1);
1174 assert_eq!(ep.query_params[0].name, "after");
1175 assert_eq!(ep.query_params[0].type_expr.name, "Uuid");
1176 assert!(ep.query_params[0].type_expr.optional);
1177 assert_eq!(ep.query_params[0].type_expr.generic_param, "");
1178 }
1179
1180 #[test]
1181 fn empty_query_block_yields_empty_vec() {
1182 let src = r#"
1186 axonendpoint x {
1187 method: GET
1188 path: "/api/x"
1189 query: { }
1190 execute: X
1191 }
1192 "#;
1193 let ep = parse_endpoint_source(src).expect("empty block parses");
1194 assert!(ep.query_params.is_empty());
1195 }
1196
1197 #[test]
1198 fn kivi_secret_write_path_plus_query() {
1199 let src = r#"
1203 axonendpoint write_secret {
1204 method: POST
1205 path: "/api/tenants/{tenant_id}/secrets/{secret_name}"
1206 query: { dry_run: Bool?, overwrite: Bool? }
1207 body: SecretWriteRequest
1208 execute: WriteSecret
1209 }
1210 "#;
1211 let ep = parse_endpoint_source(src).expect("parses");
1212 assert_eq!(ep.path_params, vec!["tenant_id", "secret_name"]);
1214 assert_eq!(ep.query_params.len(), 2);
1216 assert_eq!(ep.query_params[0].name, "dry_run");
1217 assert_eq!(ep.query_params[0].type_expr.name, "Bool");
1218 assert!(ep.query_params[0].type_expr.optional);
1219 assert_eq!(ep.query_params[1].name, "overwrite");
1220 assert_eq!(ep.body_type, "SecretWriteRequest");
1222 }
1223}
1224
1225#[cfg(test)]
1226mod path_param_extraction_tests {
1227 use super::extract_path_param_names;
1228
1229 #[test]
1230 fn empty_path_no_placeholders() {
1231 assert_eq!(extract_path_param_names("/api/users"), Ok(vec![]));
1232 assert_eq!(extract_path_param_names("/"), Ok(vec![]));
1233 assert_eq!(extract_path_param_names(""), Ok(vec![]));
1234 }
1235
1236 #[test]
1237 fn single_placeholder() {
1238 assert_eq!(
1239 extract_path_param_names("/api/users/{id}"),
1240 Ok(vec!["id".to_string()])
1241 );
1242 }
1243
1244 #[test]
1245 fn multiple_placeholders_in_declaration_order() {
1246 assert_eq!(
1247 extract_path_param_names(
1248 "/api/tenants/{tenant_id}/secrets/{secret_name}"
1249 ),
1250 Ok(vec![
1251 "tenant_id".to_string(),
1252 "secret_name".to_string(),
1253 ])
1254 );
1255 }
1256
1257 #[test]
1258 fn kivi_chat_history_path_pattern() {
1259 let names = extract_path_param_names(
1263 "/api/tenants/{tenant_id}/secrets/{secret_name}",
1264 );
1265 assert_eq!(
1266 names,
1267 Ok(vec![
1268 "tenant_id".to_string(),
1269 "secret_name".to_string(),
1270 ])
1271 );
1272 }
1273
1274 #[test]
1275 fn duplicate_placeholder_returns_err() {
1276 assert_eq!(
1277 extract_path_param_names("/api/users/{id}/posts/{id}"),
1278 Err("id".to_string())
1279 );
1280 }
1281
1282 #[test]
1283 fn underscore_and_numeric_in_name() {
1284 assert_eq!(
1285 extract_path_param_names("/api/{user_id}/items/{item_2}"),
1286 Ok(vec!["user_id".to_string(), "item_2".to_string()])
1287 );
1288 }
1289
1290 #[test]
1291 fn leading_underscore_accepted() {
1292 assert_eq!(
1298 extract_path_param_names("/api/{_internal}"),
1299 Ok(vec!["_internal".to_string()])
1300 );
1301 }
1302
1303 #[test]
1304 fn malformed_placeholder_silently_ignored() {
1305 assert_eq!(
1309 extract_path_param_names("/api/{not valid}"),
1310 Ok(vec![])
1311 );
1312 assert_eq!(extract_path_param_names("/api/{}"), Ok(vec![]));
1314 assert_eq!(
1316 extract_path_param_names("/api/{tenant id}/users/{id}"),
1317 Ok(vec!["id".to_string()])
1318 );
1319 }
1320
1321 #[test]
1322 fn unterminated_brace_returns_clean() {
1323 assert_eq!(extract_path_param_names("/api/{id"), Ok(vec![]));
1326 }
1327
1328 #[test]
1329 fn placeholders_at_path_boundaries() {
1330 assert_eq!(
1333 extract_path_param_names("{prefix}/api/users/{id}"),
1334 Ok(vec!["prefix".to_string(), "id".to_string()])
1335 );
1336 assert_eq!(
1337 extract_path_param_names("/api/{id}"),
1338 Ok(vec!["id".to_string()])
1339 );
1340 }
1341
1342 #[test]
1343 fn deduplication_detects_non_adjacent_duplicates() {
1344 assert_eq!(
1346 extract_path_param_names(
1347 "/api/orgs/{org_id}/teams/{team_id}/repos/{org_id}"
1348 ),
1349 Err("org_id".to_string())
1350 );
1351 }
1352
1353 #[test]
1354 fn never_panics_on_arbitrary_input() {
1355 for input in &[
1357 "{",
1358 "}",
1359 "{}",
1360 "{{}}",
1361 "{{{",
1362 "/api/{}/{id}",
1363 "////",
1364 "\u{1F4A1}", "\u{0000}", ] {
1367 let _ = extract_path_param_names(input); }
1369 }
1370}
1371
1372#[cfg(test)]
1373mod capability_slug_tests {
1374 use super::is_valid_capability_slug;
1375
1376 #[test]
1377 fn accepts_canonical_examples() {
1378 assert!(is_valid_capability_slug("admin"));
1379 assert!(is_valid_capability_slug("legal.read"));
1380 assert!(is_valid_capability_slug("hipaa.phi.read"));
1381 assert!(is_valid_capability_slug("bank.officer.senior"));
1382 assert!(is_valid_capability_slug("a"));
1383 assert!(is_valid_capability_slug("a_b"));
1384 assert!(is_valid_capability_slug("a1"));
1385 assert!(is_valid_capability_slug("a.b1_c"));
1386 }
1387
1388 #[test]
1389 fn rejects_empty_string() {
1390 assert!(!is_valid_capability_slug(""));
1391 }
1392
1393 #[test]
1394 fn rejects_uppercase() {
1395 assert!(!is_valid_capability_slug("Admin"));
1396 assert!(!is_valid_capability_slug("admin.READ"));
1397 }
1398
1399 #[test]
1400 fn rejects_digit_first() {
1401 assert!(!is_valid_capability_slug("1admin"));
1402 assert!(!is_valid_capability_slug("admin.1read"));
1403 }
1404
1405 #[test]
1406 fn rejects_hyphen() {
1407 assert!(!is_valid_capability_slug("bank-officer"));
1408 }
1409
1410 #[test]
1411 fn rejects_empty_segments() {
1412 assert!(!is_valid_capability_slug("bank..a"));
1413 assert!(!is_valid_capability_slug(".admin"));
1414 assert!(!is_valid_capability_slug("admin."));
1415 }
1416
1417 #[test]
1418 fn rejects_special_chars() {
1419 assert!(!is_valid_capability_slug("admin@read"));
1420 assert!(!is_valid_capability_slug("admin/read"));
1421 assert!(!is_valid_capability_slug("admin read"));
1422 }
1423}
1424
1425pub struct Parser {
1428 tokens: Vec<Token>,
1429 pos: usize,
1430 leading_trivia: Vec<Vec<Trivia>>,
1435 trailing_trivia: Vec<Vec<Trivia>>,
1439 last_let_value_kind: String,
1443 loop_depth: u32,
1448 source: Option<String>,
1454 filename: String,
1455}
1456
1457impl Parser {
1458 pub fn new(raw_tokens: Vec<Token>) -> Self {
1459 let mut effective: Vec<Token> = Vec::with_capacity(raw_tokens.len());
1469 let mut leading: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1470 let mut trailing: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1471
1472 let mut pending_leading: Vec<Trivia> = Vec::new();
1473 let mut last_effective_line: i64 = -1;
1474 for tok in raw_tokens {
1475 if is_comment_token(&tok.ttype) {
1476 let kind = token_to_trivia_kind(&tok.ttype)
1477 .expect("comment token must map to a trivia kind");
1478 let triv = Trivia {
1479 kind,
1480 text: tok.value,
1481 line: tok.line,
1482 column: tok.column,
1483 };
1484 if !effective.is_empty() && (tok.line as i64) == last_effective_line {
1485 trailing.last_mut().unwrap().push(triv);
1486 } else {
1487 pending_leading.push(triv);
1488 }
1489 } else {
1490 last_effective_line = tok.line as i64;
1491 effective.push(tok);
1492 leading.push(std::mem::take(&mut pending_leading));
1493 trailing.push(Vec::new());
1494 }
1495 }
1496
1497 Parser {
1498 tokens: effective,
1499 pos: 0,
1500 leading_trivia: leading,
1501 trailing_trivia: trailing,
1502 last_let_value_kind: "literal".to_string(),
1503 loop_depth: 0,
1504 source: None,
1505 filename: "<source>".to_string(),
1506 }
1507 }
1508
1509 #[must_use]
1521 pub fn with_source(mut self, source: &str, filename: &str) -> Self {
1522 self.source = Some(source.to_string());
1523 self.filename = filename.to_string();
1524 self
1525 }
1526
1527 pub fn parse(&mut self) -> Result<Program, ParseError> {
1530 let mut program = Program {
1531 declarations: Vec::new(),
1532 declaration_trivia: Vec::new(),
1533 loc: Loc { line: 1, column: 1 },
1534 };
1535 while !self.check(TokenType::Eof) {
1536 let start_pos = self.pos;
1542 let mut decl = match self.parse_declaration() {
1543 Ok(d) => d,
1544 Err(e) => return Err(self.attach_source_to_error(e)),
1545 };
1546 let end_pos = self.pos.saturating_sub(1);
1547 let leading = self
1548 .leading_trivia
1549 .get(start_pos)
1550 .cloned()
1551 .unwrap_or_default();
1552 let trailing = self
1553 .trailing_trivia
1554 .get(end_pos)
1555 .cloned()
1556 .unwrap_or_default();
1557 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1563 program.declarations.push(decl);
1564 program
1565 .declaration_trivia
1566 .push(DeclarationTrivia { leading, trailing });
1567 }
1568 crate::voice_desugar::expand(&mut program);
1575 crate::upstream_presets::expand(&mut program);
1576 Ok(program)
1577 }
1578
1579 pub fn parse_with_recovery(&mut self) -> ParseResult {
1612 let mut program = Program {
1613 declarations: Vec::new(),
1614 declaration_trivia: Vec::new(),
1615 loc: Loc { line: 1, column: 1 },
1616 };
1617 let mut errors: Vec<ParseError> = Vec::new();
1618
1619 while !self.check(TokenType::Eof) {
1620 let start_pos = self.pos;
1621 match self.parse_declaration() {
1622 Ok(mut decl) => {
1623 let end_pos = self.pos.saturating_sub(1);
1624 let leading = self
1625 .leading_trivia
1626 .get(start_pos)
1627 .cloned()
1628 .unwrap_or_default();
1629 let trailing = self
1630 .trailing_trivia
1631 .get(end_pos)
1632 .cloned()
1633 .unwrap_or_default();
1634 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1635 program.declarations.push(decl);
1636 program
1637 .declaration_trivia
1638 .push(DeclarationTrivia { leading, trailing });
1639 }
1640 Err(err) => {
1641 errors.push(self.attach_source_to_error(err));
1645 if self.pos == start_pos && !self.check(TokenType::Eof) {
1650 self.advance();
1651 }
1652 self.advance_to_sync_point();
1653 }
1654 }
1655 }
1656
1657 ParseResult { program, errors }
1658 }
1659
1660 fn attach_source_to_error(&self, err: ParseError) -> ParseError {
1665 match &self.source {
1666 Some(src) if err.line >= 1 => err.attach_source(src, &self.filename),
1667 _ => err,
1668 }
1669 }
1670
1671 fn advance_to_sync_point(&mut self) {
1676 let mut depth: i32 = 0;
1677 while !self.check(TokenType::Eof) {
1678 let tt = self.current().ttype.clone();
1679 if is_top_level_decl_kw_for_recovery(&tt) && depth <= 0 {
1683 return;
1684 }
1685 if matches!(tt, TokenType::LBrace) {
1686 depth += 1;
1687 } else if matches!(tt, TokenType::RBrace) {
1688 depth -= 1;
1689 }
1690 self.advance();
1691 }
1692 }
1693
1694 fn current(&self) -> &Token {
1697 if self.pos >= self.tokens.len() {
1698 self.tokens.last().unwrap() } else {
1700 &self.tokens[self.pos]
1701 }
1702 }
1703
1704 fn advance(&mut self) -> &Token {
1705 let idx = self.pos;
1706 if self.pos < self.tokens.len() {
1707 self.pos += 1;
1708 }
1709 &self.tokens[idx]
1710 }
1711
1712 fn check(&self, tt: TokenType) -> bool {
1713 self.current().ttype == tt
1714 }
1715
1716 fn consume(&mut self, expected: TokenType) -> Result<Token, ParseError> {
1717 let tok = self.current().clone();
1718 if tok.ttype != expected {
1719 return Err(ParseError {
1720 message: format!(
1721 "Expected {:?}, found {:?}('{}')",
1722 expected, tok.ttype, tok.value
1723 ),
1724 line: tok.line,
1725 column: tok.column,
1726 ..Default::default()
1727 });
1728 }
1729 self.pos += 1;
1730 Ok(tok)
1731 }
1732
1733 fn error(&self, message: &str) -> ParseError {
1735 let tok = self.current();
1736 ParseError { message: message.to_string(), line: tok.line, column: tok.column, ..Default::default() }
1737 }
1738
1739 fn consume_any_ident_or_kw(&mut self) -> Result<Token, ParseError> {
1741 let tok = self.current().clone();
1742 match tok.ttype {
1743 TokenType::Identifier
1744 | TokenType::Bool
1745 | TokenType::StringLit
1746 | TokenType::Integer
1747 | TokenType::Float => {
1748 self.pos += 1;
1749 Ok(tok)
1750 }
1751 _ => {
1752 if !tok.value.is_empty()
1754 && tok.value.chars().all(|c| c.is_alphanumeric() || c == '_')
1755 && tok.ttype != TokenType::Eof
1756 {
1757 self.pos += 1;
1758 Ok(tok)
1759 } else {
1760 Err(ParseError {
1761 message: format!(
1762 "Expected identifier or keyword value, found {:?}('{}')",
1763 tok.ttype, tok.value
1764 ),
1765 line: tok.line,
1766 column: tok.column,
1767 ..Default::default()
1768 })
1769 }
1770 }
1771 }
1772 }
1773
1774 fn consume_number(&mut self) -> Result<f64, ParseError> {
1775 let tok = self.current().clone();
1776 match tok.ttype {
1777 TokenType::Float | TokenType::Integer => {
1778 self.pos += 1;
1779 tok.value.parse::<f64>().map_err(|_| ParseError {
1780 message: format!("Invalid number '{}'", tok.value),
1781 line: tok.line,
1782 column: tok.column,
1783 ..Default::default()
1784 })
1785 }
1786 _ => Err(ParseError {
1787 message: format!("Expected number, found {:?}('{}')", tok.ttype, tok.value),
1788 line: tok.line,
1789 column: tok.column,
1790 ..Default::default()
1791 }),
1792 }
1793 }
1794
1795 fn parse_bool(&mut self) -> Result<bool, ParseError> {
1796 let tok = self.consume(TokenType::Bool)?;
1797 Ok(tok.value == "true")
1798 }
1799
1800 fn loc_of(&self, tok: &Token) -> Loc {
1801 Loc {
1802 line: tok.line,
1803 column: tok.column,
1804 }
1805 }
1806
1807 fn check_comparison(&self) -> bool {
1808 matches!(
1809 self.current().ttype,
1810 TokenType::Lt
1811 | TokenType::Gt
1812 | TokenType::Lte
1813 | TokenType::Gte
1814 | TokenType::Eq
1815 | TokenType::Neq
1816 )
1817 }
1818
1819 fn check_run_modifier(&self) -> bool {
1820 matches!(
1821 self.current().ttype,
1822 TokenType::As
1823 | TokenType::Within
1824 | TokenType::ConstrainedBy
1825 | TokenType::OnFailure
1826 | TokenType::OutputTo
1827 | TokenType::Effort
1828 )
1829 }
1830
1831 fn parse_string_list(&mut self) -> Result<Vec<String>, ParseError> {
1834 self.consume(TokenType::LBracket)?;
1835 let mut items = Vec::new();
1836 items.push(self.consume(TokenType::StringLit)?.value);
1837 while self.check(TokenType::Comma) {
1838 self.advance();
1839 items.push(self.consume(TokenType::StringLit)?.value);
1840 }
1841 self.consume(TokenType::RBracket)?;
1842 Ok(items)
1843 }
1844
1845 fn parse_bracketed_strings(&mut self) -> Result<Vec<String>, ParseError> {
1854 self.consume(TokenType::LBracket)?;
1855 let mut items = Vec::new();
1856 if !self.check(TokenType::RBracket) {
1857 items.push(self.consume(TokenType::StringLit)?.value);
1858 while self.check(TokenType::Comma) {
1859 self.advance();
1860 if self.check(TokenType::RBracket) {
1861 break; }
1863 items.push(self.consume(TokenType::StringLit)?.value);
1864 }
1865 }
1866 self.consume(TokenType::RBracket)?;
1867 Ok(items)
1868 }
1869
1870 fn parse_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1871 let mut names = Vec::new();
1872 names.push(self.consume(TokenType::Identifier)?.value);
1873 while self.check(TokenType::Comma) {
1874 self.advance();
1875 names.push(self.consume(TokenType::Identifier)?.value);
1876 }
1877 Ok(names)
1878 }
1879
1880 fn parse_bracketed_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1881 self.consume(TokenType::LBracket)?;
1882 let items = self.parse_extended_identifier_list()?;
1883 self.consume(TokenType::RBracket)?;
1884 Ok(items)
1885 }
1886
1887 fn parse_extended_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1888 let mut items = Vec::new();
1889 items.push(self.consume_any_ident_or_kw()?.value);
1890 while self.check(TokenType::Comma) {
1891 self.advance();
1892 items.push(self.consume_any_ident_or_kw()?.value);
1893 }
1894 Ok(items)
1895 }
1896
1897 fn parse_dotted_identifier(&mut self) -> Result<String, ParseError> {
1898 let mut parts = vec![self.consume_any_ident_or_kw()?.value];
1899 while self.check(TokenType::Dot) {
1900 self.advance();
1901 parts.push(self.consume_any_ident_or_kw()?.value);
1902 }
1903 Ok(parts.join("."))
1904 }
1905
1906 fn parse_expression_string(&mut self) -> Result<String, ParseError> {
1907 if self.check(TokenType::LBracket) {
1908 let items = self.parse_bracketed_dot_identifiers()?;
1909 return Ok(format!("[{}]", items.join(", ")));
1910 }
1911 self.parse_dotted_identifier()
1912 }
1913
1914 fn parse_bracketed_dot_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1915 self.consume(TokenType::LBracket)?;
1916 let mut items = vec![self.parse_dotted_identifier()?];
1917 while self.check(TokenType::Comma) {
1918 self.advance();
1919 items.push(self.parse_dotted_identifier()?);
1920 }
1921 self.consume(TokenType::RBracket)?;
1922 Ok(items)
1923 }
1924
1925 fn parse_argument_list(&mut self) -> Result<Vec<String>, ParseError> {
1926 let mut args = Vec::new();
1927 while !self.check(TokenType::RParen) {
1928 let tok = self.current().clone();
1929 match tok.ttype {
1930 TokenType::StringLit | TokenType::Integer | TokenType::Float => {
1931 self.advance();
1932 args.push(tok.value);
1933 }
1934 TokenType::Identifier => {
1935 self.advance();
1936 let mut val = tok.value;
1937 if self.check(TokenType::Dot) {
1938 self.advance();
1939 val.push('.');
1940 val.push_str(&self.consume_any_ident_or_kw()?.value);
1941 }
1942 args.push(val);
1943 }
1944 _ => {
1945 self.advance();
1946 let key = tok.value;
1947 if self.check(TokenType::Colon) {
1948 self.advance();
1949 let v = self.advance().value.clone();
1950 args.push(format!("{key}:{v}"));
1951 } else {
1952 args.push(key);
1953 }
1954 }
1955 }
1956 if self.check(TokenType::Comma) {
1957 self.advance();
1958 }
1959 }
1960 Ok(args)
1961 }
1962
1963 fn skip_value(&mut self) {
1965 match self.current().ttype {
1966 TokenType::LBracket => {
1967 self.advance();
1968 let mut depth = 1u32;
1969 while depth > 0 && !self.check(TokenType::Eof) {
1970 if self.check(TokenType::LBracket) {
1971 depth += 1;
1972 } else if self.check(TokenType::RBracket) {
1973 depth -= 1;
1974 }
1975 self.advance();
1976 }
1977 }
1978 TokenType::LBrace => {
1979 self.advance();
1980 let mut depth = 1u32;
1981 while depth > 0 && !self.check(TokenType::Eof) {
1982 if self.check(TokenType::LBrace) {
1983 depth += 1;
1984 } else if self.check(TokenType::RBrace) {
1985 depth -= 1;
1986 }
1987 self.advance();
1988 }
1989 }
1990 TokenType::Lt => {
1991 self.advance();
1993 let mut depth = 1u32;
1994 while depth > 0 && !self.check(TokenType::Eof) {
1995 if self.check(TokenType::Lt) {
1996 depth += 1;
1997 } else if self.check(TokenType::Gt) {
1998 depth -= 1;
1999 }
2000 self.advance();
2001 }
2002 }
2003 _ => {
2004 self.advance();
2005 while self.check(TokenType::Dot) {
2006 self.advance();
2007 self.advance();
2008 }
2009 }
2010 }
2011 }
2012
2013 fn skip_braced_block(&mut self) -> Result<(), ParseError> {
2015 self.consume(TokenType::LBrace)?;
2016 let mut depth = 1u32;
2017 while depth > 0 {
2018 if self.check(TokenType::Eof) {
2019 let tok = self.current();
2020 return Err(ParseError {
2021 message: "Unterminated block — expected '}'".to_string(),
2022 line: tok.line,
2023 column: tok.column,
2024 ..Default::default()
2025 });
2026 }
2027 if self.check(TokenType::LBrace) {
2028 depth += 1;
2029 } else if self.check(TokenType::RBrace) {
2030 depth -= 1;
2031 }
2032 self.advance();
2033 }
2034 Ok(())
2035 }
2036
2037 fn at_declaration_start(&self) -> bool {
2038 is_declaration_keyword(&self.current().ttype) || self.check(TokenType::Eof)
2039 }
2040
2041 fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
2044 let tok = self.current().clone();
2045
2046 match tok.ttype {
2047 TokenType::Import => self.parse_import().map(Declaration::Import),
2048 TokenType::Persona => self.parse_persona().map(Declaration::Persona),
2049 TokenType::Context => self.parse_context().map(Declaration::Context),
2050 TokenType::Anchor => self.parse_anchor().map(Declaration::Anchor),
2051 TokenType::Memory => self.parse_memory().map(Declaration::Memory),
2052 TokenType::Tool => self.parse_tool().map(Declaration::Tool),
2053 TokenType::Type => self.parse_type_def().map(Declaration::Type),
2054 TokenType::Flow => self.parse_flow().map(Declaration::Flow),
2055 TokenType::Intent => self.parse_intent().map(Declaration::Intent),
2056 TokenType::Run => self.parse_run().map(Declaration::Run),
2057 TokenType::Let => self.parse_let().map(Declaration::Let),
2058 TokenType::Know | TokenType::Believe | TokenType::Speculate | TokenType::Doubt => {
2059 self.parse_epistemic_block().map(Declaration::Epistemic)
2060 }
2061 TokenType::Lambda => self.parse_lambda_data().map(Declaration::LambdaData),
2062
2063 TokenType::Agent => self.parse_agent().map(Declaration::Agent),
2065 TokenType::Shield => self.parse_shield().map(Declaration::Shield),
2066 TokenType::Window => self.parse_window().map(Declaration::Window),
2068 TokenType::Pix => self.parse_pix().map(Declaration::Pix),
2069 TokenType::Ledger => self.parse_ledger().map(Declaration::Ledger),
2070 TokenType::Psyche => self.parse_psyche().map(Declaration::Psyche),
2071 TokenType::Corpus => self.parse_corpus().map(Declaration::Corpus),
2072 TokenType::Dataspace => self.parse_dataspace().map(Declaration::Dataspace),
2073 TokenType::Ots => self.parse_ots().map(Declaration::Ots),
2074 TokenType::Mandate => self.parse_mandate().map(Declaration::Mandate),
2075 TokenType::Compute => self.parse_compute().map(Declaration::Compute),
2076 TokenType::Daemon => self.parse_daemon().map(Declaration::Daemon),
2077 TokenType::Extension => self.parse_extension().map(Declaration::Extension),
2078 TokenType::AxonStore => self.parse_axonstore().map(Declaration::AxonStore),
2079 TokenType::AxonEndpoint => self.parse_axonendpoint().map(Declaration::AxonEndpoint),
2080
2081 TokenType::Resource => self.parse_resource().map(Declaration::Resource),
2083 TokenType::Fabric => self.parse_fabric().map(Declaration::Fabric),
2084 TokenType::Manifest => self.parse_manifest().map(Declaration::Manifest),
2085 TokenType::Observe => self.parse_observe().map(Declaration::Observe),
2086
2087 TokenType::Reconcile => self.parse_reconcile().map(Declaration::Reconcile),
2089 TokenType::Lease => self.parse_lease().map(Declaration::Lease),
2090 TokenType::Ensemble => self.parse_ensemble().map(Declaration::Ensemble),
2091
2092 TokenType::Session => self.parse_session_definition().map(Declaration::Session),
2094 TokenType::Topology => self.parse_topology().map(Declaration::Topology),
2095
2096 TokenType::Socket => self.parse_socket().map(Declaration::Socket),
2098
2099 TokenType::Upstream => self.parse_upstream().map(Declaration::Upstream),
2101
2102 TokenType::Voice => self.parse_voice().map(Declaration::Voice),
2104
2105 TokenType::Cors => self.parse_cors().map(Declaration::Cors),
2107
2108 TokenType::Cache => self.parse_cache().map(Declaration::Cache),
2110 TokenType::Document => self.parse_document().map(Declaration::Document),
2111
2112 TokenType::Deliver => self.parse_deliver().map(Declaration::Deliver),
2114
2115 TokenType::Savant => self.parse_savant().map(Declaration::Savant),
2117
2118 TokenType::Synth => self.parse_synth().map(Declaration::Synth),
2120
2121 TokenType::Scope => self.parse_scope().map(Declaration::Scope),
2123
2124 TokenType::Credential => self.parse_credential().map(Declaration::Credential),
2126
2127 TokenType::Observable => self.parse_observable().map(Declaration::Observable),
2129
2130 TokenType::Witness => self.parse_witness().map(Declaration::Witness),
2132
2133 TokenType::Immune => self.parse_immune().map(Declaration::Immune),
2135 TokenType::Reflex => self.parse_reflex().map(Declaration::Reflex),
2136 TokenType::Heal => self.parse_heal().map(Declaration::Heal),
2137
2138 TokenType::Component => self.parse_component().map(Declaration::Component),
2140 TokenType::View => self.parse_view().map(Declaration::View),
2141
2142 TokenType::Channel => self.parse_channel().map(Declaration::Channel),
2144
2145 TokenType::Ingest
2148 | TokenType::Persist
2149 | TokenType::Retrieve
2150 | TokenType::Mutate
2151 | TokenType::Purge
2152 | TokenType::Transact => self.parse_generic_declaration(),
2153
2154 TokenType::Mcp => self.parse_generic_declaration(),
2156
2157 _ => {
2158 let hint = crate::smart_suggest::suggest_for(
2162 &tok.value,
2163 crate::smart_suggest::TOP_LEVEL_KEYWORD_NAMES,
2164 );
2165 let base = format!(
2166 "Unexpected token at top level: '{}' — expected declaration \
2167 (persona, context, anchor, flow, run, ...)",
2168 tok.value
2169 );
2170 let message = if hint.is_empty() {
2171 base
2172 } else {
2173 format!("{base}. {hint}")
2174 };
2175 Err(ParseError {
2176 message,
2177 line: tok.line,
2178 column: tok.column,
2179 ..Default::default()
2180 })
2181 }
2182 }
2183 }
2184
2185 fn parse_import(&mut self) -> Result<ImportNode, ParseError> {
2188 let tok = self.consume(TokenType::Import)?;
2189 let loc = self.loc_of(&tok);
2190
2191 let mut path_parts = Vec::new();
2192
2193 if self.check(TokenType::At) {
2195 self.advance();
2196 let first = self.consume(TokenType::Identifier)?;
2197 path_parts.push(format!("@{}", first.value));
2198 } else {
2199 let first = self.consume(TokenType::Identifier)?;
2200 path_parts.push(first.value);
2201 }
2202
2203 while self.check(TokenType::Dot) {
2204 self.advance();
2205 if self.check(TokenType::LBrace) {
2206 break;
2207 }
2208 let part = self.consume(TokenType::Identifier)?;
2209 path_parts.push(part.value);
2210 }
2211
2212 let mut names = Vec::new();
2213 if self.check(TokenType::LBrace) {
2214 self.advance();
2215 names = self.parse_identifier_list()?;
2216 self.consume(TokenType::RBrace)?;
2217 }
2218
2219 if self.current().value == "with" {
2221 self.advance();
2222 self.advance(); if self.check(TokenType::LBrace) {
2224 self.skip_braced_block()?;
2225 }
2226 }
2227
2228 Ok(ImportNode {
2229 module_path: path_parts,
2230 names,
2231 loc,
2232 leading_trivia: Vec::new(),
2233 trailing_trivia: Vec::new(),
2234 })
2235 }
2236
2237 fn parse_persona(&mut self) -> Result<PersonaDefinition, ParseError> {
2240 let tok = self.consume(TokenType::Persona)?;
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 = PersonaDefinition {
2246 name,
2247 domain: Vec::new(),
2248 tone: String::new(),
2249 confidence_threshold: None,
2250 cite_sources: None,
2251 refuse_if: Vec::new(),
2252 language: String::new(),
2253 description: String::new(),
2254 loc,
2255 leading_trivia: Vec::new(),
2256 trailing_trivia: Vec::new(),
2257 };
2258
2259 while !self.check(TokenType::RBrace) {
2260 let field_name = self.current().value.clone();
2261 self.advance();
2262 self.consume(TokenType::Colon)?;
2263
2264 match field_name.as_str() {
2265 "domain" => node.domain = self.parse_string_list()?,
2266 "tone" => node.tone = self.consume_any_ident_or_kw()?.value,
2267 "confidence_threshold" => node.confidence_threshold = Some(self.consume_number()?),
2268 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2269 "refuse_if" => node.refuse_if = self.parse_bracketed_identifiers()?,
2270 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2271 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2272 _ => self.skip_value(),
2273 }
2274 }
2275 self.consume(TokenType::RBrace)?;
2276 Ok(node)
2277 }
2278
2279 fn parse_context(&mut self) -> Result<ContextDefinition, ParseError> {
2282 let tok = self.consume(TokenType::Context)?;
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 = ContextDefinition {
2288 name,
2289 memory_scope: String::new(),
2290 language: String::new(),
2291 depth: String::new(),
2292 max_tokens: None,
2293 temperature: None,
2294 cite_sources: None,
2295 now_tz: None,
2296 loc,
2297 leading_trivia: Vec::new(),
2298 trailing_trivia: Vec::new(),
2299 };
2300
2301 while !self.check(TokenType::RBrace) {
2302 let field_name = self.current().value.clone();
2303 self.advance();
2304 self.consume(TokenType::Colon)?;
2305
2306 match field_name.as_str() {
2307 "memory" => node.memory_scope = self.consume_any_ident_or_kw()?.value,
2308 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2309 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
2310 "now" => node.now_tz = Some(self.consume(TokenType::StringLit)?.value),
2312 "max_tokens" => {
2313 node.max_tokens = Some(
2314 self.consume(TokenType::Integer)?
2315 .value
2316 .parse::<i64>()
2317 .unwrap_or(0),
2318 )
2319 }
2320 "temperature" => node.temperature = Some(self.consume_number()?),
2321 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2322 _ => self.skip_value(),
2323 }
2324 }
2325 self.consume(TokenType::RBrace)?;
2326 Ok(node)
2327 }
2328
2329 fn parse_anchor(&mut self) -> Result<AnchorConstraint, ParseError> {
2332 let tok = self.consume(TokenType::Anchor)?;
2333 let loc = self.loc_of(&tok);
2334 let name = self.consume(TokenType::Identifier)?.value;
2335 self.consume(TokenType::LBrace)?;
2336
2337 let mut node = AnchorConstraint {
2338 name,
2339 require: String::new(),
2340 reject: Vec::new(),
2341 enforce: String::new(),
2342 description: String::new(),
2343 confidence_floor: None,
2344 unknown_response: String::new(),
2345 on_violation: String::new(),
2346 on_violation_target: String::new(),
2347 loc,
2348 leading_trivia: Vec::new(),
2349 trailing_trivia: Vec::new(),
2350 };
2351
2352 while !self.check(TokenType::RBrace) {
2353 let field_name = self.current().value.clone();
2354 self.advance();
2355 self.consume(TokenType::Colon)?;
2356
2357 match field_name.as_str() {
2358 "require" => node.require = self.consume_any_ident_or_kw()?.value,
2359 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2360 "reject" => node.reject = self.parse_bracketed_identifiers()?,
2361 "enforce" => node.enforce = self.consume_any_ident_or_kw()?.value,
2362 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
2363 "unknown_response" => {
2364 node.unknown_response = self.consume(TokenType::StringLit)?.value
2365 }
2366 "on_violation" => {
2367 let action = self.consume_any_ident_or_kw()?.value;
2369 node.on_violation = action.clone();
2370 if action == "raise" || action == "fallback" {
2371 node.on_violation_target = self.consume_any_ident_or_kw()?.value;
2372 }
2373 }
2374 _ => self.skip_value(),
2375 }
2376 }
2377 self.consume(TokenType::RBrace)?;
2378 Ok(node)
2379 }
2380
2381 fn parse_memory(&mut self) -> Result<MemoryDefinition, ParseError> {
2384 let tok = self.consume(TokenType::Memory)?;
2385 let loc = self.loc_of(&tok);
2386 let name = self.consume(TokenType::Identifier)?.value;
2387 self.consume(TokenType::LBrace)?;
2388
2389 let mut node = MemoryDefinition {
2390 name,
2391 store: String::new(),
2392 backend: String::new(),
2393 retrieval: String::new(),
2394 decay: String::new(),
2395 loc,
2396 leading_trivia: Vec::new(),
2397 trailing_trivia: Vec::new(),
2398 };
2399
2400 while !self.check(TokenType::RBrace) {
2401 let field_name = self.current().value.clone();
2402 self.advance();
2403 self.consume(TokenType::Colon)?;
2404
2405 match field_name.as_str() {
2406 "store" => node.store = self.consume_any_ident_or_kw()?.value,
2407 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
2408 "retrieval" => node.retrieval = self.consume_any_ident_or_kw()?.value,
2409 "decay" => {
2410 if self.check(TokenType::Duration) {
2411 node.decay = self.advance().value.clone();
2412 } else {
2413 node.decay = self.consume_any_ident_or_kw()?.value;
2414 }
2415 }
2416 _ => self.skip_value(),
2417 }
2418 }
2419 self.consume(TokenType::RBrace)?;
2420 Ok(node)
2421 }
2422
2423 fn parse_tool(&mut self) -> Result<ToolDefinition, ParseError> {
2426 let tok = self.consume(TokenType::Tool)?;
2427 let loc = self.loc_of(&tok);
2428 let name = self.consume(TokenType::Identifier)?.value;
2429 self.consume(TokenType::LBrace)?;
2430
2431 let mut node = ToolDefinition {
2432 name,
2433 provider: String::new(),
2434 max_results: None,
2435 filter_expr: String::new(),
2436 timeout: String::new(),
2437 runtime: String::new(),
2438 sandbox: None,
2439 effects: None,
2440 parameters: Vec::new(),
2441 output_type: None,
2442 secret: String::new(),
2443 secret_partition: String::new(),
2444 target: None,
2445 risk: None,
2446 argv: Vec::new(),
2447 cache: String::new(),
2448 scrape: None,
2449 loc,
2450 leading_trivia: Vec::new(),
2451 trailing_trivia: Vec::new(),
2452 };
2453
2454 let mut unknown_fields: Vec<(String, u32, u32)> = Vec::new();
2461
2462 while !self.check(TokenType::RBrace) {
2463 let field_tok = self.current().clone();
2464 let field_name = field_tok.value.clone();
2465 self.advance();
2466 self.consume(TokenType::Colon)?;
2467
2468 match field_name.as_str() {
2469 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
2470 "max_results" => {
2471 node.max_results = Some(
2472 self.consume(TokenType::Integer)?
2473 .value
2474 .parse::<i64>()
2475 .unwrap_or(0),
2476 )
2477 }
2478 "filter" => node.filter_expr = self.parse_filter_expression()?,
2479 "timeout" => node.timeout = self.consume(TokenType::Duration)?.value,
2480 "runtime" => node.runtime = self.consume_any_ident_or_kw()?.value,
2481 "sandbox" => node.sandbox = Some(self.parse_bool()?),
2482 "effects" => node.effects = Some(self.parse_effect_row()?),
2483 "parameters" => node.parameters = self.parse_tool_param_schema()?,
2485 "output_type" => node.output_type = Some(self.parse_output_type_string()?),
2486 "secret" => node.secret = self.parse_dotted_identifier()?,
2490 "secret_partition" => {
2496 node.secret_partition = self.consume_any_ident_or_kw()?.value
2497 }
2498 "target" => node.target = Some(self.consume_any_ident_or_kw()?.value),
2500 "risk" => node.risk = Some(self.consume_any_ident_or_kw()?.value),
2501 "argv" => node.argv = self.parse_bracketed_strings()?,
2505 "cache" => node.cache = self.consume_any_ident_or_kw()?.value,
2508 "scrape" => node.scrape = Some(self.parse_scrape_spec()?),
2511 _ => {
2512 unknown_fields.push((field_name, field_tok.line, field_tok.column));
2513 self.skip_value();
2514 }
2515 }
2516 }
2517 self.consume(TokenType::RBrace)?;
2518
2519 if node.target.is_some() || node.scrape.is_some() {
2527 if let Some((field_name, line, column)) = unknown_fields.into_iter().next() {
2528 let (surface, valid) = if node.target.is_some() {
2529 (
2530 "technician tool (§Fase 84 D84.13)",
2531 "provider, parameters, output_type, timeout, effects, target, risk, argv",
2532 )
2533 } else {
2534 (
2535 "web-acquisition tool (§Fase 98 D98.2)",
2536 "provider, parameters, output_type, timeout, effects, secret, \
2537 secret_partition, cache, scrape",
2538 )
2539 };
2540 return Err(ParseError {
2541 message: format!(
2542 "unknown field `{field_name}` in {surface} `{}` — this tool uses \
2543 strict field checking; valid fields: {valid}",
2544 node.name
2545 ),
2546 line,
2547 column,
2548 ..Default::default()
2549 });
2550 }
2551 }
2552 Ok(node)
2553 }
2554
2555 fn parse_scrape_spec(&mut self) -> Result<crate::ast::ScrapeSpec, ParseError> {
2560 let open = self.consume(TokenType::LBrace)?;
2561 let loc = self.loc_of(&open);
2562 let mut spec = crate::ast::ScrapeSpec {
2563 loc,
2564 ..Default::default()
2565 };
2566 while !self.check(TokenType::RBrace) {
2567 let field_tok = self.current().clone();
2568 let field_name = field_tok.value.clone();
2569 self.advance();
2570 self.consume(TokenType::Colon)?;
2571 match field_name.as_str() {
2572 "engine" => spec.engine = Some(self.consume_any_ident_or_kw()?.value),
2573 "impersonate" => spec.impersonate = Some(self.consume_any_ident_or_kw()?.value),
2574 "render_wait" => spec.render_wait = Some(self.consume(TokenType::Duration)?.value),
2575 "proxy" => spec.proxy = self.parse_dotted_identifier()?,
2576 "respect_robots" => spec.respect_robots = Some(self.parse_bool()?),
2577 "extract" => spec.extract = self.parse_bracketed_strings()?,
2578 "adaptive" => spec.adaptive = Some(self.parse_bool()?),
2579 "similarity_floor" => spec.similarity_floor = self.parse_optional_float(),
2580 "follow" => spec.follow = self.consume(TokenType::StringLit)?.value,
2581 "max_depth" => {
2582 spec.max_depth =
2583 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2584 }
2585 "max_pages" => {
2586 spec.max_pages =
2587 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2588 }
2589 "concurrency" => {
2590 spec.concurrency =
2591 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2592 }
2593 "politeness" => spec.politeness = self.consume_any_ident_or_kw()?.value,
2594 "checkpoint" => spec.checkpoint = self.consume_any_ident_or_kw()?.value,
2595 other => {
2596 return Err(self.error(&format!(
2597 "unknown scrape field `{other}` — the `scrape: {{ … }}` block is a \
2598 closed catalog (§Fase 98 D98.2); valid fields: engine, impersonate, \
2599 render_wait, proxy, respect_robots, extract, adaptive, \
2600 similarity_floor, follow, max_depth, max_pages, concurrency, \
2601 politeness, checkpoint"
2602 )));
2603 }
2604 }
2605 }
2606 self.consume(TokenType::RBrace)?;
2607 Ok(spec)
2608 }
2609
2610 fn parse_tool_param_schema(&mut self) -> Result<Vec<Parameter>, ParseError> {
2616 self.consume(TokenType::LBrace)?;
2617 let mut params = Vec::new();
2618 while !self.check(TokenType::RBrace) {
2619 let name = self.consume_any_ident_or_kw()?;
2623 let ploc = self.loc_of(&name);
2624 self.consume(TokenType::Colon)?;
2625 let type_expr = self.parse_type_expr()?;
2626 params.push(Parameter {
2627 name: name.value,
2628 type_expr,
2629 loc: ploc,
2630 });
2631 if self.check(TokenType::Comma) {
2632 self.advance();
2633 } else {
2634 break;
2635 }
2636 }
2637 self.consume(TokenType::RBrace)?;
2638 Ok(params)
2639 }
2640
2641 fn parse_filter_expression(&mut self) -> Result<String, ParseError> {
2642 let name = self.consume_any_ident_or_kw()?.value;
2643 if self.check(TokenType::LParen) {
2644 self.advance();
2645 let mut parts = vec![name, "(".to_string()];
2646 while !self.check(TokenType::RParen) {
2647 parts.push(self.advance().value.clone());
2648 }
2649 self.consume(TokenType::RParen)?;
2650 parts.push(")".to_string());
2651 Ok(parts.join(""))
2652 } else {
2653 Ok(name)
2654 }
2655 }
2656
2657 fn parse_effect_row(&mut self) -> Result<EffectRow, ParseError> {
2658 let tok = self.consume(TokenType::Lt)?;
2659 let loc = self.loc_of(&tok);
2660 let mut effects = Vec::new();
2661 let mut epistemic_level = String::new();
2662
2663 while !self.check(TokenType::Gt) {
2664 let name = self.consume_any_ident_or_kw()?.value;
2665 if self.check(TokenType::Colon) {
2666 self.advance();
2667 let level = self.parse_qualifier_value()?;
2684 if name == "epistemic" {
2685 epistemic_level = level;
2686 } else {
2687 effects.push(format!("{name}:{level}"));
2688 }
2689 } else {
2690 effects.push(name);
2691 }
2692 if self.check(TokenType::Comma) {
2693 self.advance();
2694 }
2695 }
2696 self.consume(TokenType::Gt)?;
2697
2698 Ok(EffectRow {
2699 effects,
2700 epistemic_level,
2701 loc,
2702 })
2703 }
2704
2705 fn parse_qualifier_value(&mut self) -> Result<String, ParseError> {
2714 let mut buf = self.consume_dotted_slug_segment()?;
2715 loop {
2716 let sep = if self.check(TokenType::Dot) {
2717 '.'
2718 } else if self.check(TokenType::Colon) {
2719 ':'
2720 } else {
2721 break;
2722 };
2723 self.advance();
2724 let part = self.consume_dotted_slug_segment()?;
2725 buf.push(sep);
2726 buf.push_str(&part);
2727 }
2728 Ok(buf)
2729 }
2730
2731 fn consume_dotted_slug_segment(&mut self) -> Result<String, ParseError> {
2743 let first = self.consume_any_ident_or_kw()?;
2744 let mut buf = first.value.clone();
2745 let mut next_line = first.line;
2746 let mut next_col = first.column + first.value.chars().count() as u32;
2747 loop {
2748 let cur = self.current();
2749 let is_segment_token = matches!(cur.ttype, TokenType::Identifier | TokenType::Integer,);
2750 if !is_segment_token {
2751 break;
2752 }
2753 if cur.line != next_line || cur.column != next_col {
2754 break;
2755 }
2756 buf.push_str(&cur.value);
2757 next_col = cur.column + cur.value.chars().count() as u32;
2758 next_line = cur.line;
2759 self.pos += 1;
2760 }
2761 Ok(buf)
2762 }
2763
2764 fn parse_type_def(&mut self) -> Result<TypeDefinition, ParseError> {
2767 let tok = self.consume(TokenType::Type)?;
2768 let loc = self.loc_of(&tok);
2769 let name = self.consume(TokenType::Identifier)?.value;
2770
2771 let mut node = TypeDefinition {
2772 name,
2773 fields: Vec::new(),
2774 range_constraint: None,
2775 where_clause: None,
2776 compliance: Vec::new(),
2777 loc: loc.clone(),
2778 leading_trivia: Vec::new(),
2779 trailing_trivia: Vec::new(),
2780 };
2781
2782 if self.check(TokenType::LParen) {
2784 self.advance();
2785 let min_val = self.consume_number()?;
2786 self.consume(TokenType::DotDot)?;
2787 let max_val = self.consume_number()?;
2788 self.consume(TokenType::RParen)?;
2789 node.range_constraint = Some(RangeConstraint {
2790 min_value: min_val,
2791 max_value: max_val,
2792 loc: loc.clone(),
2793 });
2794 }
2795
2796 if self.check(TokenType::Where) {
2798 self.advance();
2799 let mut expr_parts = Vec::new();
2800 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
2801 if self.check(TokenType::Eof) {
2802 break;
2803 }
2804 expr_parts.push(self.advance().value.clone());
2805 }
2806 node.where_clause = Some(WhereClause {
2807 expression: expr_parts.join(" "),
2808 loc: loc.clone(),
2809 });
2810 }
2811
2812 if self.check(TokenType::Identifier) && self.current().value == "compliance" {
2815 self.advance();
2816 node.compliance = self.parse_bracketed_identifiers()?;
2817 }
2818
2819 if self.check(TokenType::LBrace) {
2821 self.advance();
2822 while !self.check(TokenType::RBrace) {
2823 let field_name = self.consume(TokenType::Identifier)?;
2824 let field_loc = self.loc_of(&field_name);
2825 self.consume(TokenType::Colon)?;
2826 let type_expr = self.parse_type_expr()?;
2827 node.fields.push(TypeField {
2828 name: field_name.value,
2829 type_expr,
2830 loc: field_loc,
2831 });
2832 if self.check(TokenType::Comma) {
2833 self.advance();
2834 }
2835 }
2836 self.consume(TokenType::RBrace)?;
2837 }
2838
2839 Ok(node)
2840 }
2841
2842 fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
2843 let name_tok = self.consume(TokenType::Identifier)?;
2844 let loc = self.loc_of(&name_tok);
2845 let mut generic_param = String::new();
2846 let mut optional = false;
2847
2848 if self.check(TokenType::Lt) {
2849 self.advance();
2850 let inner = self.parse_type_expr()?;
2860 generic_param = if inner.generic_param.is_empty() {
2861 inner.name
2862 } else {
2863 format!("{}<{}>", inner.name, inner.generic_param)
2864 };
2865 self.consume(TokenType::Gt)?;
2866 }
2867 if self.check(TokenType::LBracket) {
2871 self.advance();
2872 if matches!(self.current().ttype, TokenType::Integer | TokenType::Float) {
2873 generic_param = self.advance().value.clone();
2874 } else {
2875 let inner = self.parse_type_expr()?;
2876 generic_param = if inner.generic_param.is_empty() {
2877 inner.name
2878 } else {
2879 format!("{}[{}]", inner.name, inner.generic_param)
2880 };
2881 }
2882 self.consume(TokenType::RBracket)?;
2883 }
2884 if self.check(TokenType::Question) {
2885 self.advance();
2886 optional = true;
2887 }
2888
2889 Ok(TypeExpr {
2890 name: name_tok.value,
2891 generic_param,
2892 optional,
2893 loc,
2894 })
2895 }
2896
2897 fn parse_output_type_string(&mut self) -> Result<String, ParseError> {
2922 let expr = self.parse_type_expr()?;
2923 let mut s = expr.name;
2924 if !expr.generic_param.is_empty() {
2925 s.push('<');
2926 s.push_str(&expr.generic_param);
2927 s.push('>');
2928 }
2929 if expr.optional {
2930 s.push('?');
2931 }
2932 Ok(s)
2933 }
2934
2935 fn parse_flow(&mut self) -> Result<FlowDefinition, ParseError> {
2938 let tok = self.consume(TokenType::Flow)?;
2939 let loc = self.loc_of(&tok);
2940 let name = self.consume(TokenType::Identifier)?.value;
2941
2942 self.consume(TokenType::LParen)?;
2943 let mut parameters = Vec::new();
2944 if !self.check(TokenType::RParen) {
2945 parameters = self.parse_param_list()?;
2946 }
2947 self.consume(TokenType::RParen)?;
2948
2949 let mut return_type = None;
2950 if self.check(TokenType::Arrow) {
2951 self.advance();
2952 return_type = Some(self.parse_type_expr()?);
2953 }
2954
2955 self.consume(TokenType::LBrace)?;
2956 let mut body = Vec::new();
2957 while !self.check(TokenType::RBrace) {
2958 body.push(self.parse_flow_step()?);
2959 }
2960 self.consume(TokenType::RBrace)?;
2961
2962 Ok(FlowDefinition {
2963 name,
2964 parameters,
2965 return_type,
2966 body,
2967 loc,
2968 leading_trivia: Vec::new(),
2969 trailing_trivia: Vec::new(),
2970 })
2971 }
2972
2973 fn parse_param_list(&mut self) -> Result<Vec<Parameter>, ParseError> {
2974 let mut params = Vec::new();
2975
2976 let name = self.consume(TokenType::Identifier)?;
2977 let ploc = self.loc_of(&name);
2978 self.consume(TokenType::Colon)?;
2979 let type_expr = self.parse_type_expr()?;
2980 params.push(Parameter {
2981 name: name.value,
2982 type_expr,
2983 loc: ploc,
2984 });
2985
2986 while self.check(TokenType::Comma) {
2987 self.advance();
2988 let name = self.consume(TokenType::Identifier)?;
2989 let ploc = self.loc_of(&name);
2990 self.consume(TokenType::Colon)?;
2991 let type_expr = self.parse_type_expr()?;
2992 params.push(Parameter {
2993 name: name.value,
2994 type_expr,
2995 loc: ploc,
2996 });
2997 }
2998 Ok(params)
2999 }
3000
3001 fn parse_flow_step(&mut self) -> Result<FlowStep, ParseError> {
3004 let tok = self.current().clone();
3005
3006 match tok.ttype {
3007 TokenType::Step => self.parse_step().map(FlowStep::Step),
3008 TokenType::If => self.parse_if().map(FlowStep::If),
3009 TokenType::For => self.parse_for_in().map(FlowStep::ForIn),
3010 TokenType::Let => self.parse_let().map(FlowStep::Let),
3011 TokenType::Return => self.parse_return().map(FlowStep::Return),
3012 TokenType::Break => self.parse_break().map(FlowStep::Break),
3013 TokenType::Continue => self.parse_continue().map(FlowStep::Continue),
3014 TokenType::Lambda => self.parse_lambda_data_apply().map(FlowStep::LambdaDataApply),
3015
3016 TokenType::Probe => self.parse_flow_step_simple("probe").map(|l| FlowStep::Probe(ProbeStep { target: l.1, loc: l.0 })),
3018 TokenType::Reason => self.parse_flow_step_simple("reason").map(|l| FlowStep::Reason(ReasonStep { strategy: String::new(), target: l.1, loc: l.0 })),
3019 TokenType::Validate => self.parse_flow_step_simple("validate").map(|l| FlowStep::Validate(ValidateStep { target: l.1, rule: String::new(), loc: l.0 })),
3020 TokenType::Refine => self.parse_flow_step_simple("refine").map(|l| FlowStep::Refine(RefineStep { target: l.1, strategy: String::new(), loc: l.0 })),
3021 TokenType::Weave => self.parse_weave_step(),
3022 TokenType::Use => self.parse_use_step(),
3023 TokenType::Remember => self.parse_remember_step(),
3024 TokenType::Recall => self.parse_recall_step(),
3025 TokenType::Par => self.parse_par_block().map(FlowStep::Par),
3026 TokenType::Hibernate => self.parse_hibernate_step(),
3027 TokenType::Deliberate => self.parse_block_step("deliberate").map(|l| FlowStep::Deliberate(DeliberateBlock { loc: l })),
3028 TokenType::Consensus => self.parse_block_step("consensus").map(|l| FlowStep::Consensus(ConsensusBlock { loc: l })),
3029 TokenType::Forge => self.parse_forge_step().map(FlowStep::Forge),
3030 TokenType::Focus => self.parse_focus_step(),
3031 TokenType::Grad => self.parse_grad_step(),
3032 TokenType::Associate => self.parse_associate_step(),
3033 TokenType::Aggregate => self.parse_aggregate_step(),
3034 TokenType::Explore => self.parse_explore_step(),
3035 TokenType::Ingest => self.parse_ingest_step(),
3036 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 })),
3037 TokenType::Stream => self.parse_block_step("stream").map(|l| FlowStep::Stream(StreamBlock { loc: l })),
3038 TokenType::Navigate => self.parse_navigate_step(),
3039 TokenType::Drill => self.parse_drill_step(),
3040 TokenType::Trail => self.parse_flow_step_simple("trail").map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 })),
3041 TokenType::Corroborate => self.parse_corroborate_step(),
3042 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 })),
3043 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 })),
3044 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 })),
3045 TokenType::Listen => self.parse_listen_step(),
3046 TokenType::Daemon => self.parse_flow_step_simple("daemon").map(|l| FlowStep::DaemonStep(DaemonStepNode { daemon_ref: l.1, loc: l.0 })),
3047 TokenType::Emit => self.parse_emit_step(),
3049 TokenType::Mint => self.parse_mint_step(),
3051 TokenType::Rotate => self.parse_rotate_step(),
3054 TokenType::Publish => self.parse_publish_step(),
3055 TokenType::Discover => self.parse_discover_step(),
3056 TokenType::Persist => self.parse_persist_step(),
3057 TokenType::Retrieve => self.parse_retrieve_step(),
3058 TokenType::Mutate => self.parse_mutate_step(),
3059 TokenType::Purge => self.parse_store_where_step().map(|(loc, store_name, where_expr)| FlowStep::Purge(PurgeStep { store_name, where_expr, loc })),
3060 TokenType::Transact => self.parse_block_step("transact").map(|l| FlowStep::Transact(TransactBlock { loc: l })),
3061 TokenType::Warden => self.parse_warden().map(FlowStep::Warden),
3063 TokenType::Quant => self.parse_quant().map(FlowStep::Quant),
3065 TokenType::Yield => self.parse_yield().map(FlowStep::Yield),
3067 TokenType::Run => self.parse_run().map(FlowStep::Run),
3071
3072 _ => {
3073 let hint = crate::smart_suggest::suggest_for(
3077 &tok.value,
3078 crate::smart_suggest::FLOW_BODY_KEYWORD_NAMES,
3079 );
3080 let base = format!(
3081 "Unexpected token in flow body: '{}' — expected step, if, for, let, return, ...",
3082 tok.value
3083 );
3084 let message = if hint.is_empty() {
3085 base
3086 } else {
3087 format!("{base}. {hint}")
3088 };
3089 Err(ParseError {
3090 message,
3091 line: tok.line,
3092 column: tok.column,
3093 ..Default::default()
3094 })
3095 }
3096 }
3097 }
3098
3099 fn parse_step(&mut self) -> Result<StepNode, ParseError> {
3102 let tok = self.consume(TokenType::Step)?;
3103 let loc = self.loc_of(&tok);
3104 let name = self.consume(TokenType::Identifier)?.value;
3105
3106 let mut persona_ref = String::new();
3107 if self.check(TokenType::Use) {
3108 self.advance();
3109 persona_ref = self.consume_any_ident_or_kw()?.value;
3110 }
3111
3112 self.consume(TokenType::LBrace)?;
3113
3114 let mut node = StepNode {
3115 name,
3116 persona_ref,
3117 given: String::new(),
3118 ask: String::new(),
3119 output_type: String::new(),
3120 confidence_floor: None,
3121 navigate_ref: String::new(),
3122 apply_ref: String::new(),
3123 requires_context: None,
3124 now_tz: None,
3125 loc,
3126 };
3127
3128 while !self.check(TokenType::RBrace) {
3129 let inner = self.current().clone();
3130
3131 match inner.ttype {
3132 TokenType::Given => {
3133 self.advance();
3134 self.consume(TokenType::Colon)?;
3135 node.given = self.parse_expression_string()?;
3136 }
3137 TokenType::Ask => {
3138 self.advance();
3139 self.consume(TokenType::Colon)?;
3140 node.ask = self.consume(TokenType::StringLit)?.value;
3141 }
3142 TokenType::Output => {
3143 self.advance();
3153 self.consume(TokenType::Colon)?;
3154 node.output_type = self.parse_output_type_string()?;
3155 }
3156 TokenType::Navigate => {
3157 self.advance();
3158 self.consume(TokenType::Colon)?;
3159 node.navigate_ref = self.parse_dotted_identifier()?;
3160 }
3161 TokenType::Identifier if inner.value == "confidence_floor" => {
3162 self.advance();
3163 self.consume(TokenType::Colon)?;
3164 node.confidence_floor = Some(self.consume_number()?);
3165 }
3166 TokenType::Identifier if inner.value == "apply" => {
3167 self.advance();
3168 self.consume(TokenType::Colon)?;
3169 node.apply_ref = self.consume_any_ident_or_kw()?.value;
3170 }
3171 TokenType::Identifier if inner.value == "requires_context" => {
3179 self.advance();
3180 self.consume(TokenType::Colon)?;
3181 let num = self.current().clone();
3182 let bad = |tok: &crate::tokens::Token| ParseError {
3183 message: format!(
3184 "`requires_context:` must be a positive integer token count \
3185 (got '{}')",
3186 tok.value
3187 ),
3188 line: tok.line,
3189 column: tok.column,
3190 ..Default::default()
3191 };
3192 if num.ttype != TokenType::Integer {
3193 return Err(bad(&num));
3194 }
3195 let value = num.value.parse::<u32>().map_err(|_| bad(&num))?;
3196 self.advance();
3197 node.requires_context = Some(value);
3198 }
3199 TokenType::Identifier if inner.value == "now" => {
3204 self.advance();
3205 self.consume(TokenType::Colon)?;
3206 let tz = self.current().clone();
3207 if tz.ttype != TokenType::StringLit {
3208 return Err(ParseError {
3209 message: format!(
3210 "`now:` must be an IANA timezone string literal like \
3211 \"America/Bogota\" or \"UTC\" (got '{}')",
3212 tz.value
3213 ),
3214 line: tz.line,
3215 column: tz.column,
3216 ..Default::default()
3217 });
3218 }
3219 self.advance();
3220 node.now_tz = Some(tz.value);
3221 }
3222 TokenType::Use => {
3232 let tool = self
3233 .tokens
3234 .get(self.pos + 1)
3235 .map(|t| t.value.as_str())
3236 .filter(|v| !v.is_empty())
3237 .unwrap_or("<Tool>");
3238 return Err(ParseError {
3239 message: format!(
3240 "`use` is not valid inside a `step {{ }}` body — the tool dispatch \
3241 would be silently dropped. To invoke a tool, either write the \
3242 flow-level step `use {tool} on <arg>` (outside this block), or bind \
3243 it inside this step with `apply: {tool}`. To attach a persona, put \
3244 it in the step header: `step <name> use <Persona> {{ … }}`."
3245 ),
3246 line: inner.line,
3247 column: inner.column,
3248 ..Default::default()
3249 });
3250 }
3251 TokenType::Probe
3253 | TokenType::Reason
3254 | TokenType::Weave
3255 | TokenType::Stream => {
3256 self.skip_flow_step_structural()?;
3257 }
3258 _ => {
3259 return Err(ParseError {
3260 message: format!(
3261 "Unexpected token in step body: '{}' — expected given, ask, \
3262 probe, reason, weave, stream, output, confidence_floor, navigate, \
3263 apply, requires_context, now",
3264 inner.value
3265 ),
3266 line: inner.line,
3267 column: inner.column,
3268 ..Default::default()
3269 });
3270 }
3271 }
3272 }
3273 self.consume(TokenType::RBrace)?;
3274 Ok(node)
3275 }
3276
3277 fn skip_flow_step_structural(&mut self) -> Result<(), ParseError> {
3279 self.advance();
3281 while !self.check(TokenType::LBrace)
3283 && !self.check(TokenType::RBrace)
3284 && !self.check(TokenType::Eof)
3285 {
3286 let tt = &self.current().ttype;
3288 if matches!(
3289 tt,
3290 TokenType::Step
3291 | TokenType::Given
3292 | TokenType::Ask
3293 | TokenType::Output
3294 | TokenType::Navigate
3295 | TokenType::Use
3296 | TokenType::Probe
3297 | TokenType::Reason
3298 | TokenType::Weave
3299 | TokenType::Stream
3300 | TokenType::If
3301 | TokenType::For
3302 | TokenType::Let
3303 | TokenType::Return
3304 ) {
3305 return Ok(());
3306 }
3307 self.advance();
3308 }
3309 if self.check(TokenType::LBrace) {
3311 self.skip_braced_block()?;
3312 }
3313 Ok(())
3314 }
3315
3316 fn parse_intent(&mut self) -> Result<IntentNode, ParseError> {
3319 let tok = self.consume(TokenType::Intent)?;
3320 let loc = self.loc_of(&tok);
3321 let name = self.consume(TokenType::Identifier)?.value;
3322 self.consume(TokenType::LBrace)?;
3323
3324 let mut node = IntentNode {
3325 name,
3326 given: String::new(),
3327 ask: String::new(),
3328 output_type: None,
3329 confidence_floor: None,
3330 loc,
3331 leading_trivia: Vec::new(),
3332 trailing_trivia: Vec::new(),
3333 };
3334
3335 while !self.check(TokenType::RBrace) {
3336 let field_name = self.current().value.clone();
3337 self.advance();
3338 self.consume(TokenType::Colon)?;
3339
3340 match field_name.as_str() {
3341 "given" => node.given = self.consume(TokenType::Identifier)?.value,
3342 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
3343 "output" => node.output_type = Some(self.parse_type_expr()?),
3344 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
3345 _ => self.skip_value(),
3346 }
3347 }
3348 self.consume(TokenType::RBrace)?;
3349 Ok(node)
3350 }
3351
3352 fn parse_run(&mut self) -> Result<RunStatement, ParseError> {
3355 let tok = self.consume(TokenType::Run)?;
3356 let loc = self.loc_of(&tok);
3357 let flow_name = self.consume(TokenType::Identifier)?.value;
3358
3359 self.consume(TokenType::LParen)?;
3360 let mut arguments = Vec::new();
3361 if !self.check(TokenType::RParen) {
3362 arguments = self.parse_argument_list()?;
3363 }
3364 self.consume(TokenType::RParen)?;
3365
3366 let mut node = RunStatement {
3367 flow_name,
3368 arguments,
3369 persona: String::new(),
3370 context: String::new(),
3371 anchors: Vec::new(),
3372 on_failure: String::new(),
3373 on_failure_params: Vec::new(),
3374 output_to: String::new(),
3375 effort: String::new(),
3376 loc,
3377 leading_trivia: Vec::new(),
3378 trailing_trivia: Vec::new(),
3379 };
3380
3381 while self.check_run_modifier() {
3382 let mod_tok = self.current().clone();
3383 match mod_tok.ttype {
3384 TokenType::As => {
3385 self.advance();
3386 node.persona = self.consume(TokenType::Identifier)?.value;
3387 }
3388 TokenType::Within => {
3389 self.advance();
3390 node.context = self.consume(TokenType::Identifier)?.value;
3391 }
3392 TokenType::ConstrainedBy => {
3393 self.advance();
3394 node.anchors = self.parse_bracketed_identifiers()?;
3395 }
3396 TokenType::OnFailure => {
3397 self.advance();
3398 self.consume(TokenType::Colon)?;
3399 node.on_failure = self.consume_any_ident_or_kw()?.value;
3400 if self.check(TokenType::LParen) {
3402 self.advance();
3403 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3404 let key = self.consume_any_ident_or_kw()?.value;
3405 self.consume(TokenType::Colon)?;
3406 let val = self.consume_any_ident_or_kw()?.value;
3407 node.on_failure_params.push((key, val));
3408 if self.check(TokenType::Comma) {
3409 self.advance();
3410 }
3411 }
3412 if self.check(TokenType::RParen) {
3413 self.advance();
3414 }
3415 }
3416 }
3417 TokenType::OutputTo => {
3418 self.advance();
3419 self.consume(TokenType::Colon)?;
3420 node.output_to = self.consume(TokenType::StringLit)?.value;
3421 }
3422 TokenType::Effort => {
3423 self.advance();
3424 self.consume(TokenType::Colon)?;
3425 node.effort = self.consume_any_ident_or_kw()?.value;
3426 }
3427 _ => break,
3428 }
3429 }
3430
3431 Ok(node)
3432 }
3433
3434 fn parse_epistemic_block(&mut self) -> Result<EpistemicBlock, ParseError> {
3437 let tok = self.current().clone();
3438 let mode = match tok.ttype {
3439 TokenType::Know => "know",
3440 TokenType::Believe => "believe",
3441 TokenType::Speculate => "speculate",
3442 TokenType::Doubt => "doubt",
3443 _ => unreachable!(),
3444 };
3445 self.advance();
3446 let loc = self.loc_of(&tok);
3447
3448 self.consume(TokenType::LBrace)?;
3449 let mut body = Vec::new();
3450 while !self.check(TokenType::RBrace) {
3451 body.push(self.parse_declaration()?);
3452 }
3453 self.consume(TokenType::RBrace)?;
3454
3455 Ok(EpistemicBlock {
3456 mode: mode.to_string(),
3457 body,
3458 loc,
3459 leading_trivia: Vec::new(),
3460 trailing_trivia: Vec::new(),
3461 })
3462 }
3463
3464 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
3472 self.parse_expr_bp(0)
3473 }
3474
3475 fn parse_expr_bp(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
3476 let mut lhs = match self.current().ttype {
3479 TokenType::Minus => {
3480 self.advance();
3481 Expr::Unary(UnOp::Neg, Box::new(self.parse_expr_bp(6)?))
3482 }
3483 TokenType::Not => {
3484 self.advance();
3485 Expr::Unary(UnOp::Not, Box::new(self.parse_expr_bp(6)?))
3486 }
3487 _ => self.parse_postfix()?,
3488 };
3489 while let Some((op, lbp)) = Self::binop_of(self.current().ttype.clone()) {
3491 if lbp < min_bp {
3492 break;
3493 }
3494 self.advance();
3495 let rhs = self.parse_expr_bp(lbp + 1)?;
3496 lhs = Expr::Binary(op, Box::new(lhs), Box::new(rhs));
3497 }
3498 Ok(lhs)
3499 }
3500
3501 fn binop_of(t: TokenType) -> Option<(BinOp, u8)> {
3504 Some(match t {
3505 TokenType::Or => (BinOp::Or, 1),
3506 TokenType::And => (BinOp::And, 2),
3507 TokenType::Eq => (BinOp::Eq, 3),
3508 TokenType::Neq => (BinOp::Ne, 3),
3509 TokenType::Lt => (BinOp::Lt, 3),
3510 TokenType::Lte => (BinOp::Le, 3),
3511 TokenType::Gt => (BinOp::Gt, 3),
3512 TokenType::Gte => (BinOp::Ge, 3),
3513 TokenType::Plus => (BinOp::Add, 4),
3514 TokenType::Minus => (BinOp::Sub, 4),
3515 TokenType::Star => (BinOp::Mul, 5),
3516 TokenType::Slash => (BinOp::Div, 5),
3517 TokenType::Percent => (BinOp::Mod, 5),
3518 _ => return None,
3519 })
3520 }
3521
3522 fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
3528 let mut expr = self.parse_expr_atom()?;
3529 loop {
3530 if self.check(TokenType::Dot) {
3531 self.advance();
3532 let name = self.consume_any_ident_or_kw()?.value;
3533 if let Some(builtin) = Builtin::from_name(&name) {
3534 let mut args = vec![expr];
3535 if self.check(TokenType::LParen) {
3536 self.advance();
3537 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3538 args.push(self.parse_expr_bp(0)?);
3539 if self.check(TokenType::Comma) {
3540 self.advance();
3541 } else {
3542 break;
3543 }
3544 }
3545 self.consume(TokenType::RParen)?;
3546 }
3547 expr = Expr::Call(builtin, args);
3548 } else {
3549 expr = match expr {
3553 Expr::Ref(p) => Expr::Ref(format!("{p}.{name}")),
3554 other => Expr::Field(Box::new(other), name),
3555 };
3556 }
3557 } else if self.check(TokenType::LBracket) {
3558 self.advance();
3560 let index = self.parse_expr_bp(0)?;
3561 self.consume(TokenType::RBracket)?;
3562 expr = Expr::Index(Box::new(expr), Box::new(index));
3563 } else {
3564 break;
3565 }
3566 }
3567 Ok(expr)
3568 }
3569
3570 fn parse_expr_atom(&mut self) -> Result<Expr, ParseError> {
3571 let tok = self.current().clone();
3572 match tok.ttype {
3573 TokenType::Integer => {
3574 self.advance();
3575 let lit = tok
3576 .value
3577 .parse::<i64>()
3578 .map(ExprLit::Int)
3579 .or_else(|_| tok.value.parse::<f64>().map(ExprLit::Float))
3580 .map_err(|_| ParseError {
3581 message: format!("invalid integer literal '{}'", tok.value),
3582 line: tok.line,
3583 column: tok.column,
3584 ..Default::default()
3585 })?;
3586 Ok(Expr::Lit(lit))
3587 }
3588 TokenType::Float => {
3589 self.advance();
3590 let f = tok.value.parse::<f64>().map_err(|_| ParseError {
3591 message: format!("invalid float literal '{}'", tok.value),
3592 line: tok.line,
3593 column: tok.column,
3594 ..Default::default()
3595 })?;
3596 Ok(Expr::Lit(ExprLit::Float(f)))
3597 }
3598 TokenType::Bool => {
3599 self.advance();
3600 Ok(Expr::Lit(ExprLit::Bool(tok.value == "true")))
3601 }
3602 TokenType::StringLit => {
3603 self.advance();
3604 Ok(Expr::Lit(ExprLit::Str(tok.value)))
3605 }
3606 TokenType::LParen => {
3607 self.advance();
3608 let inner = self.parse_expr_bp(0)?;
3609 self.consume(TokenType::RParen)?;
3610 Ok(inner)
3611 }
3612 _ => {
3613 Ok(Expr::Ref(self.consume_any_ident_or_kw()?.value))
3617 }
3618 }
3619 }
3620
3621 fn expr_lit_surface(lit: &ExprLit) -> String {
3626 match lit {
3627 ExprLit::Int(i) => i.to_string(),
3628 ExprLit::Float(f) => f.to_string(),
3629 ExprLit::Bool(b) => b.to_string(),
3630 ExprLit::Str(s) => s.clone(),
3631 }
3632 }
3633
3634 fn expr_leaf_surface(expr: &Expr) -> Option<String> {
3635 match expr {
3636 Expr::Ref(p) => Some(p.clone()),
3637 Expr::Lit(l) => Some(Self::expr_lit_surface(l)),
3638 _ => None,
3639 }
3640 }
3641
3642 fn expr_legacy_leaf(expr: &Expr) -> Option<(String, String, String)> {
3646 match expr {
3647 Expr::Ref(p) => Some((p.clone(), String::new(), String::new())),
3648 Expr::Binary(op, l, r) => {
3649 let op_s = match op {
3650 BinOp::Eq => "==",
3651 BinOp::Ne => "!=",
3652 BinOp::Lt => "<",
3653 BinOp::Le => "<=",
3654 BinOp::Gt => ">",
3655 BinOp::Ge => ">=",
3656 _ => return None,
3657 };
3658 let lhs = match &**l {
3659 Expr::Ref(p) => p.clone(),
3660 _ => return None,
3661 };
3662 let rhs = Self::expr_leaf_surface(r)?;
3663 Some((lhs, op_s.to_string(), rhs))
3664 }
3665 _ => None,
3666 }
3667 }
3668
3669 fn collect_or_leaves(expr: &Expr, out: &mut Vec<(String, String, String)>) -> bool {
3672 match expr {
3673 Expr::Binary(BinOp::Or, l, r) => {
3674 Self::collect_or_leaves(l, out) && Self::collect_or_leaves(r, out)
3675 }
3676 _ => match Self::expr_legacy_leaf(expr) {
3677 Some(t) => {
3678 out.push(t);
3679 true
3680 }
3681 None => false,
3682 },
3683 }
3684 }
3685
3686 #[allow(clippy::type_complexity)]
3692 fn cond_as_legacy(
3693 expr: &Expr,
3694 ) -> Option<(String, String, String, Vec<(String, String, String)>, String)> {
3695 let mut leaves = Vec::new();
3696 if !Self::collect_or_leaves(expr, &mut leaves) || leaves.is_empty() {
3697 return None;
3698 }
3699 let (c0, o0, v0) = leaves[0].clone();
3700 let rest = leaves[1..].to_vec();
3701 let conjunctor = if rest.is_empty() {
3702 String::new()
3703 } else {
3704 "or".to_string()
3705 };
3706 Some((c0, o0, v0, rest, conjunctor))
3707 }
3708
3709 fn parse_if(&mut self) -> Result<ConditionalNode, ParseError> {
3710 let tok = self.consume(TokenType::If)?;
3711 let loc = self.loc_of(&tok);
3712
3713 let expr = self.parse_expr()?;
3718 let (condition, comparison_op, comparison_value, conditions, conjunctor, cond) =
3719 match Self::cond_as_legacy(&expr) {
3720 Some((c, o, v, more, conj)) => (c, o, v, more, conj, None),
3721 None => (
3722 String::new(),
3723 String::new(),
3724 String::new(),
3725 Vec::new(),
3726 String::new(),
3727 Some(expr),
3728 ),
3729 };
3730
3731 let mut then_body = Vec::new();
3732 let mut else_body = Vec::new();
3733
3734 if self.check(TokenType::Arrow) {
3736 self.advance();
3737 then_body.push(self.parse_flow_step()?);
3738 } else if self.check(TokenType::LBrace) {
3739 self.advance();
3740 while !self.check(TokenType::RBrace) {
3741 then_body.push(self.parse_flow_step()?);
3742 }
3743 self.consume(TokenType::RBrace)?;
3744 }
3745
3746 if self.check(TokenType::Else) {
3748 self.advance();
3749 if self.check(TokenType::Arrow) {
3750 self.advance();
3751 else_body.push(self.parse_flow_step()?);
3752 } else if self.check(TokenType::LBrace) {
3753 self.advance();
3754 while !self.check(TokenType::RBrace) {
3755 else_body.push(self.parse_flow_step()?);
3756 }
3757 self.consume(TokenType::RBrace)?;
3758 }
3759 }
3760
3761 Ok(ConditionalNode {
3762 condition,
3763 comparison_op,
3764 comparison_value,
3765 then_body,
3766 else_body,
3767 conditions,
3768 conjunctor,
3769 cond,
3770 loc,
3771 })
3772 }
3773
3774 fn parse_for_in(&mut self) -> Result<ForInStatement, ParseError> {
3777 let tok = self.consume(TokenType::For)?;
3778 let loc = self.loc_of(&tok);
3779 let variable = self.consume(TokenType::Identifier)?.value;
3780 self.consume(TokenType::In)?;
3781 let iterable = self.parse_dotted_identifier()?;
3782
3783 self.consume(TokenType::LBrace)?;
3784 self.loop_depth += 1;
3791 let body_result = (|| -> Result<Vec<FlowStep>, ParseError> {
3792 let mut body = Vec::new();
3793 while !self.check(TokenType::RBrace) {
3794 body.push(self.parse_flow_step()?);
3795 }
3796 Ok(body)
3797 })();
3798 self.loop_depth -= 1;
3799 let body = body_result?;
3800 self.consume(TokenType::RBrace)?;
3801
3802 Ok(ForInStatement {
3803 variable,
3804 iterable,
3805 body,
3806 loc,
3807 })
3808 }
3809
3810 fn parse_break(&mut self) -> Result<BreakStatement, ParseError> {
3813 let tok = self.consume(TokenType::Break)?;
3814 let loc = self.loc_of(&tok);
3815 if self.loop_depth == 0 {
3816 return Err(ParseError {
3817 message: "'break' outside of a for-in loop body".to_string(),
3818 line: tok.line,
3819 column: tok.column,
3820 ..Default::default()
3821 });
3822 }
3823 Ok(BreakStatement { loc })
3824 }
3825
3826 fn parse_continue(&mut self) -> Result<ContinueStatement, ParseError> {
3829 let tok = self.consume(TokenType::Continue)?;
3830 let loc = self.loc_of(&tok);
3831 if self.loop_depth == 0 {
3832 return Err(ParseError {
3833 message: "'continue' outside of a for-in loop body".to_string(),
3834 line: tok.line,
3835 column: tok.column,
3836 ..Default::default()
3837 });
3838 }
3839 Ok(ContinueStatement { loc })
3840 }
3841
3842 fn parse_let(&mut self) -> Result<LetStatement, ParseError> {
3845 let tok = self.consume(TokenType::Let)?;
3846 let loc = self.loc_of(&tok);
3847
3848 let name = self.consume_any_ident_or_kw()?.value;
3850 let type_annotation = if self.check(TokenType::Colon) {
3852 self.advance();
3853 Some(self.parse_type_expr()?)
3854 } else {
3855 None
3856 };
3857 self.consume(TokenType::Assign)?;
3858 self.last_let_value_kind = "literal".to_string();
3861 let (value, value_ast) = self.parse_let_value_expr_with_ast()?;
3862
3863 Ok(LetStatement {
3864 identifier: name,
3865 value_expr: value,
3866 value_kind: self.last_let_value_kind.clone(),
3867 type_annotation,
3868 value_ast,
3869 loc,
3870 leading_trivia: Vec::new(),
3871 trailing_trivia: Vec::new(),
3872 })
3873 }
3874
3875 fn parse_let_value_expr(&mut self) -> Result<String, ParseError> {
3876 let atom = self.parse_let_atom()?;
3877
3878 if matches!(
3880 self.current().ttype,
3881 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3882 ) {
3883 let mut parts = vec![atom];
3884 while matches!(
3885 self.current().ttype,
3886 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3887 ) {
3888 parts.push(self.advance().value.clone());
3889 parts.push(self.parse_let_atom()?);
3890 }
3891 self.last_let_value_kind = "expression".to_string();
3892 return Ok(parts.join(" "));
3893 }
3894 Ok(atom)
3895 }
3896
3897 fn parse_let_value_expr_with_ast(&mut self) -> Result<(String, Option<Expr>), ParseError> {
3907 if self.check(TokenType::LBracket) {
3908 self.last_let_value_kind = "literal".to_string();
3909 return Ok((self.parse_let_list_literal()?, None));
3910 }
3911 let expr = self.parse_expr()?;
3912 Ok(match expr {
3913 Expr::Lit(lit) => {
3914 self.last_let_value_kind = "literal".to_string();
3915 (Self::expr_lit_surface(&lit), None)
3916 }
3917 Expr::Ref(p) => {
3918 self.last_let_value_kind = "reference".to_string();
3919 (p, None)
3920 }
3921 other => {
3922 self.last_let_value_kind = "expression".to_string();
3923 (Self::render_expr(&other), Some(other))
3924 }
3925 })
3926 }
3927
3928 fn render_expr(e: &Expr) -> String {
3931 match e {
3932 Expr::Lit(l) => Self::expr_lit_surface(l),
3933 Expr::Ref(p) => p.clone(),
3934 Expr::Unary(UnOp::Neg, x) => format!("-{}", Self::render_expr(x)),
3935 Expr::Unary(UnOp::Not, x) => format!("not {}", Self::render_expr(x)),
3936 Expr::Binary(op, l, r) => {
3937 let sym = match op {
3938 BinOp::Add => "+",
3939 BinOp::Sub => "-",
3940 BinOp::Mul => "*",
3941 BinOp::Div => "/",
3942 BinOp::Mod => "%",
3943 BinOp::Eq => "==",
3944 BinOp::Ne => "!=",
3945 BinOp::Lt => "<",
3946 BinOp::Le => "<=",
3947 BinOp::Gt => ">",
3948 BinOp::Ge => ">=",
3949 BinOp::And => "and",
3950 BinOp::Or => "or",
3951 };
3952 format!("({} {sym} {})", Self::render_expr(l), Self::render_expr(r))
3953 }
3954 Expr::Call(b, args) => {
3955 let recv = args.first().map(Self::render_expr).unwrap_or_default();
3956 let rest: Vec<String> = args.iter().skip(1).map(Self::render_expr).collect();
3957 if rest.is_empty() {
3958 format!("{recv}.{}", b.surface())
3959 } else {
3960 format!("{recv}.{}({})", b.surface(), rest.join(", "))
3961 }
3962 }
3963 Expr::Field(b, f) => format!("{}.{f}", Self::render_expr(b)),
3964 Expr::Index(b, i) => format!("{}[{}]", Self::render_expr(b), Self::render_expr(i)),
3965 }
3966 }
3967
3968 fn parse_let_atom(&mut self) -> Result<String, ParseError> {
3969 let tok = self.current().clone();
3970
3971 match tok.ttype {
3972 TokenType::StringLit => {
3973 self.last_let_value_kind = "literal".to_string();
3974 self.advance();
3975 Ok(tok.value)
3976 }
3977 TokenType::Integer | TokenType::Float => {
3978 self.last_let_value_kind = "literal".to_string();
3979 self.advance();
3980 Ok(tok.value)
3981 }
3982 TokenType::Bool => {
3983 self.last_let_value_kind = "literal".to_string();
3984 self.advance();
3985 Ok(tok.value)
3986 }
3987 TokenType::Identifier => {
3988 self.last_let_value_kind = "reference".to_string();
3989 self.parse_dotted_identifier()
3990 }
3991 TokenType::LBracket => {
3992 self.last_let_value_kind = "literal".to_string();
3993 self.parse_let_list_literal()
3994 }
3995 _ => {
3996 if self.pos + 1 < self.tokens.len()
3998 && self.tokens[self.pos + 1].ttype == TokenType::Dot
3999 {
4000 self.last_let_value_kind = "reference".to_string();
4001 return self.parse_dotted_identifier();
4002 }
4003 Err(ParseError {
4004 message: format!(
4005 "Expected value expression, found {:?}('{}')",
4006 tok.ttype, tok.value
4007 ),
4008 line: tok.line,
4009 column: tok.column,
4010 ..Default::default()
4011 })
4012 }
4013 }
4014 }
4015
4016 fn parse_let_list_literal(&mut self) -> Result<String, ParseError> {
4017 self.consume(TokenType::LBracket)?;
4018 let mut items = Vec::new();
4019 if !self.check(TokenType::RBracket) {
4020 items.push(self.parse_let_value_expr()?);
4021 while self.check(TokenType::Comma) {
4022 self.advance();
4023 if self.check(TokenType::RBracket) {
4024 break; }
4026 items.push(self.parse_let_value_expr()?);
4027 }
4028 }
4029 self.consume(TokenType::RBracket)?;
4030 Ok(format!("[{}]", items.join(", ")))
4031 }
4032
4033 fn parse_return(&mut self) -> Result<ReturnStatement, ParseError> {
4036 let tok = self.consume(TokenType::Return)?;
4037 let loc = self.loc_of(&tok);
4038 let value = self.parse_let_value_expr()?;
4039 Ok(ReturnStatement {
4040 value_expr: value,
4041 loc,
4042 })
4043 }
4044
4045 fn parse_flow_step_simple(&mut self, _kw: &str) -> Result<(Loc, String), ParseError> {
4049 let tok = self.current().clone();
4050 self.advance(); let target = if self.at_declaration_start()
4052 || self.check(TokenType::RBrace)
4053 || self.check(TokenType::Eof)
4054 {
4055 String::new()
4056 } else {
4057 self.consume_any_ident_or_kw()?.value.clone()
4058 };
4059 if self.check(TokenType::LBrace) {
4061 self.skip_braced_block()?;
4062 }
4063 Ok((
4064 Loc {
4065 line: tok.line,
4066 column: tok.column,
4067 },
4068 target,
4069 ))
4070 }
4071
4072 fn parse_block_step(&mut self, _kw: &str) -> Result<Loc, ParseError> {
4074 let tok = self.current().clone();
4075 self.advance();
4076 while !self.check(TokenType::LBrace)
4078 && !self.check(TokenType::RBrace)
4079 && !self.check(TokenType::Eof)
4080 && !self.at_declaration_start()
4081 {
4082 self.advance();
4083 }
4084 if self.check(TokenType::LBrace) {
4085 self.skip_braced_block()?;
4086 }
4087 Ok(Loc {
4088 line: tok.line,
4089 column: tok.column,
4090 })
4091 }
4092
4093 fn parse_forge_step(&mut self) -> Result<ForgeBlock, ParseError> {
4100 let tok = self.consume(TokenType::Forge)?;
4101 let name = self.consume(TokenType::Identifier)?.value;
4102 let mut node = ForgeBlock {
4103 name,
4104 novelty: 0.5,
4105 depth: 1,
4106 branches: 1,
4107 loc: Loc { line: tok.line, column: tok.column },
4108 ..Default::default()
4109 };
4110 self.consume(TokenType::LParen)?;
4112 let arg = self.consume_any_ident_or_kw()?.value;
4113 self.consume(TokenType::Colon)?;
4114 if arg != "seed" {
4115 return Err(self.error(&format!(
4116 "forge '{}' expects `seed:` as its argument, found `{arg}`",
4117 node.name
4118 )));
4119 }
4120 node.seed = self.consume(TokenType::StringLit)?.value;
4121 self.consume(TokenType::RParen)?;
4122 self.consume(TokenType::Arrow)?;
4124 node.output_type = self.consume_any_ident_or_kw()?.value;
4125 self.consume(TokenType::LBrace)?;
4127 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4128 let field = self.consume_any_ident_or_kw()?.value;
4129 self.consume(TokenType::Colon)?;
4130 match field.as_str() {
4131 "mode" => node.mode = self.consume_any_ident_or_kw()?.value,
4132 "novelty" => node.novelty = self.consume_number()?,
4133 "depth" => {
4134 node.depth = self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4135 }
4136 "branches" => {
4137 node.branches =
4138 self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4139 }
4140 "constraints" => node.constraints_ref = self.consume_any_ident_or_kw()?.value,
4141 other => {
4142 return Err(self.error(&format!("unknown forge field `{other}`")))
4143 }
4144 }
4145 if self.check(TokenType::Comma) {
4146 self.consume(TokenType::Comma)?;
4147 }
4148 }
4149 self.consume(TokenType::RBrace)?;
4150 Ok(node)
4151 }
4152
4153 fn parse_par_block(&mut self) -> Result<ParBlock, ParseError> {
4162 let tok = self.current().clone();
4163 self.advance(); self.consume(TokenType::LBrace)?;
4165 let mut branches: Vec<Vec<FlowStep>> = Vec::new();
4166 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4167 branches.push(vec![self.parse_flow_step()?]);
4168 }
4169 self.consume(TokenType::RBrace)?;
4170 Ok(ParBlock {
4171 branches,
4172 loc: Loc {
4173 line: tok.line,
4174 column: tok.column,
4175 },
4176 })
4177 }
4178
4179 fn parse_warden(&mut self) -> Result<WardenBlock, ParseError> {
4196 let tok = self.consume(TokenType::Warden)?;
4197 self.consume(TokenType::LParen)?;
4199 let target = self.consume_any_ident_or_kw()?.value;
4200 self.consume(TokenType::RParen)?;
4201 self.consume(TokenType::Within)?;
4203 let scope_ref = self.consume(TokenType::Identifier)?.value;
4204 let mut block = WardenBlock {
4205 target,
4206 scope_ref,
4207 body: Vec::new(),
4208 loc: Loc {
4209 line: tok.line,
4210 column: tok.column,
4211 },
4212 };
4213 self.consume(TokenType::LBrace)?;
4215 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4216 block.body.push(self.parse_flow_step()?);
4217 }
4218 self.consume(TokenType::RBrace)?;
4219 Ok(block)
4220 }
4221
4222 fn parse_scope(&mut self) -> Result<ScopeDefinition, ParseError> {
4227 let tok = self.consume(TokenType::Scope)?;
4228 let name = self.consume(TokenType::Identifier)?.value;
4229 let mut node = ScopeDefinition {
4230 name,
4231 loc: Loc {
4232 line: tok.line,
4233 column: tok.column,
4234 },
4235 ..Default::default()
4236 };
4237 self.consume(TokenType::LBrace)?;
4238 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4239 let key = self.consume_any_ident_or_kw()?.value;
4240 self.consume(TokenType::Colon)?;
4241 match key.as_str() {
4242 "targets" => {
4243 self.consume(TokenType::LBracket)?;
4244 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4245 let t = if self.check(TokenType::StringLit) {
4246 self.consume(TokenType::StringLit)?.value
4247 } else {
4248 self.consume_any_ident_or_kw()?.value
4249 };
4250 node.targets.push(t);
4251 if self.check(TokenType::Comma) {
4252 self.advance();
4253 }
4254 }
4255 self.consume(TokenType::RBracket)?;
4256 }
4257 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
4258 "approver" => {
4259 if self.current().value == "requires" {
4261 self.advance();
4262 }
4263 node.approver = self.consume(TokenType::StringLit)?.value;
4264 }
4265 other => {
4266 return Err(self.error(&format!(
4267 "unknown scope field `{other}` in scope `{}` — expected \
4268 `targets` / `depth` / `approver`",
4269 node.name
4270 )))
4271 }
4272 }
4273 if self.check(TokenType::Comma) {
4274 self.consume(TokenType::Comma)?;
4275 }
4276 }
4277 self.consume(TokenType::RBrace)?;
4278 Ok(node)
4279 }
4280
4281 fn parse_quant(&mut self) -> Result<QuantBlock, ParseError> {
4282 let tok = self.current().clone();
4283 self.advance(); let mut block = QuantBlock {
4286 encoding: None,
4287 observable: None,
4288 qubits: None,
4289 depth: None,
4290 bandwidth: None,
4291 reupload: None,
4292 effect: "quant_sim".to_string(),
4295 body: Vec::new(),
4296 loc: Loc {
4297 line: tok.line,
4298 column: tok.column,
4299 },
4300 };
4301
4302 if self.check(TokenType::LParen) {
4304 self.advance();
4305 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
4306 let key = self.consume_any_ident_or_kw()?.value;
4307 self.consume(TokenType::Colon)?;
4308 match key.as_str() {
4309 "encoding" => {
4310 block.encoding = Some(self.consume_any_ident_or_kw()?.value)
4311 }
4312 "observable" => {
4313 block.observable = Some(self.parse_dotted_identifier()?)
4314 }
4315 "qubits" => block.qubits = Some(self.consume_number()? as i64),
4316 "depth" => block.depth = Some(self.consume_number()? as i64),
4317 "bandwidth" => block.bandwidth = Some(self.consume_number()?),
4318 "reupload" => block.reupload = Some(self.consume_number()? as i64),
4320 "backend" => block.effect = self.consume_any_ident_or_kw()?.value,
4322 other => {
4323 return Err(ParseError {
4324 message: format!(
4325 "Unknown `quant` attribute `{other}` — expected one of \
4326 encoding, observable, qubits, depth, bandwidth, reupload, backend"
4327 ),
4328 line: self.current().line,
4329 column: self.current().column,
4330 ..Default::default()
4331 });
4332 }
4333 }
4334 if self.check(TokenType::Comma) {
4336 self.advance();
4337 }
4338 }
4339 self.consume(TokenType::RParen)?;
4340 }
4341
4342 self.consume(TokenType::LBrace)?;
4344 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4345 block.body.push(self.parse_flow_step()?);
4346 }
4347 self.consume(TokenType::RBrace)?;
4348
4349 Ok(block)
4350 }
4351
4352 fn parse_yield(&mut self) -> Result<YieldStatement, ParseError> {
4356 let tok = self.consume(TokenType::Yield)?;
4357 let loc = self.loc_of(&tok);
4358 self.last_let_value_kind = "literal".to_string();
4359 let value_expr = self.parse_let_value_expr()?;
4360 Ok(YieldStatement {
4361 value_expr,
4362 value_kind: self.last_let_value_kind.clone(),
4363 loc,
4364 })
4365 }
4366
4367 fn parse_apply_step(&mut self, _kw: &str) -> Result<(Loc, String, String, String), ParseError> {
4369 let tok = self.current().clone();
4370 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
4372 let mut target = String::new();
4373 let mut output_type = String::new();
4374 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4376 let next = self.current().clone();
4377 if next.value == "on" {
4378 self.advance();
4379 target = self.consume_any_ident_or_kw()?.value.clone();
4380 }
4381 }
4382 if self.check(TokenType::Arrow) {
4384 self.advance();
4385 output_type = self.consume_any_ident_or_kw()?.value.clone();
4386 }
4387 if self.check(TokenType::LBrace) {
4389 self.skip_braced_block()?;
4390 }
4391 Ok((
4392 Loc {
4393 line: tok.line,
4394 column: tok.column,
4395 },
4396 name,
4397 target,
4398 output_type,
4399 ))
4400 }
4401
4402 fn parse_weave_step(&mut self) -> Result<FlowStep, ParseError> {
4403 let tok = self.current().clone();
4404 self.advance();
4405 let mut node = WeaveStep {
4406 sources: Vec::new(),
4407 target: String::new(),
4408 format_type: String::new(),
4409 priority: Vec::new(),
4410 style: String::new(),
4411 loc: Loc {
4412 line: tok.line,
4413 column: tok.column,
4414 },
4415 };
4416 if self.check(TokenType::LBrace) {
4417 self.advance();
4418 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4419 let f = self.current().value.clone();
4420 self.advance();
4421 if self.check(TokenType::Colon) {
4422 self.advance();
4423 match f.as_str() {
4424 "sources" => node.sources = self.parse_bracketed_identifiers()?,
4425 "target" => node.target = self.consume_any_ident_or_kw()?.value.clone(),
4426 "format" => {
4427 node.format_type = self.consume_any_ident_or_kw()?.value.clone()
4428 }
4429 "priority" => node.priority = self.parse_bracketed_identifiers()?,
4430 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
4431 _ => self.skip_value(),
4432 }
4433 }
4434 }
4435 if self.check(TokenType::RBrace) {
4436 self.advance();
4437 }
4438 }
4439 Ok(FlowStep::Weave(node))
4440 }
4441
4442 fn parse_use_step(&mut self) -> Result<FlowStep, ParseError> {
4443 let tok = self.current().clone();
4444 self.advance();
4445 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
4446 let args = if self.check(TokenType::LParen) {
4457 UseArgs::Named(self.parse_named_arg_list()?)
4458 } else {
4459 let mut argument = String::new();
4460 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4461 let next = self.current().clone();
4462 if next.value == "on" {
4463 self.advance();
4464 argument = self.consume_any_ident_or_kw()?.value.clone();
4465 }
4466 }
4467 UseArgs::LegacyPositional(argument)
4468 };
4469 if self.check(TokenType::LBrace) {
4470 self.skip_braced_block()?;
4471 }
4472 Ok(FlowStep::UseTool(UseToolStep {
4473 tool_name,
4474 args,
4475 loc: Loc {
4476 line: tok.line,
4477 column: tok.column,
4478 },
4479 }))
4480 }
4481
4482 fn parse_named_arg_list(&mut self) -> Result<Vec<(String, String, String)>, ParseError> {
4488 self.consume(TokenType::LParen)?;
4489 let mut args = Vec::new();
4490 while !self.check(TokenType::RParen) {
4491 let name = self.consume_any_ident_or_kw()?.value;
4494 self.consume(TokenType::Assign)?;
4495 let value = self.parse_let_atom()?;
4496 let value_kind = self.last_let_value_kind.clone();
4500 args.push((name, value, value_kind));
4501 if self.check(TokenType::Comma) {
4502 self.advance();
4503 } else {
4504 break;
4505 }
4506 }
4507 self.consume(TokenType::RParen)?;
4508 Ok(args)
4509 }
4510
4511 fn parse_remember_step(&mut self) -> Result<FlowStep, ParseError> {
4512 let tok = self.current().clone();
4513 self.advance();
4514 let expr = self.consume_any_ident_or_kw()?.value.clone();
4515 let mut mem = String::new();
4516 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4517 let next = self.current().clone();
4518 if next.value == "in" || next.ttype == TokenType::In {
4519 self.advance();
4520 mem = self.consume_any_ident_or_kw()?.value.clone();
4521 }
4522 }
4523 Ok(FlowStep::Remember(RememberStep {
4524 expression: expr,
4525 memory_target: mem,
4526 loc: Loc {
4527 line: tok.line,
4528 column: tok.column,
4529 },
4530 }))
4531 }
4532
4533 fn parse_recall_step(&mut self) -> Result<FlowStep, ParseError> {
4534 let tok = self.current().clone();
4535 self.advance();
4536 let query = if self.check(TokenType::StringLit) {
4537 self.consume(TokenType::StringLit)?.value.clone()
4538 } else {
4539 self.consume_any_ident_or_kw()?.value.clone()
4540 };
4541 let mut mem = String::new();
4542 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4543 let next = self.current().clone();
4544 if next.value == "from" || next.ttype == TokenType::From {
4545 self.advance();
4546 mem = self.consume_any_ident_or_kw()?.value.clone();
4547 }
4548 }
4549 Ok(FlowStep::Recall(RecallStep {
4550 query,
4551 memory_source: mem,
4552 loc: Loc {
4553 line: tok.line,
4554 column: tok.column,
4555 },
4556 }))
4557 }
4558
4559 fn parse_hibernate_step(&mut self) -> Result<FlowStep, ParseError> {
4560 let tok = self.current().clone();
4561 self.advance();
4562 let mut event = String::new();
4563 let mut timeout = String::new();
4564 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4565 event = self.consume_any_ident_or_kw()?.value.clone();
4566 }
4567 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4568 let next = self.current().clone();
4569 if next.ttype == TokenType::Duration {
4570 self.advance();
4571 timeout = next.value.clone();
4572 }
4573 }
4574 Ok(FlowStep::Hibernate(HibernateStep {
4575 event_name: event,
4576 timeout,
4577 loc: Loc {
4578 line: tok.line,
4579 column: tok.column,
4580 },
4581 }))
4582 }
4583
4584 fn parse_grad_step(&mut self) -> Result<FlowStep, ParseError> {
4593 let tok = self.current().clone();
4594 self.advance();
4595 let target = self.consume_any_ident_or_kw()?.value.clone();
4596 let mut wrt: Vec<String> = Vec::new();
4597 let mut output = String::new();
4598 if !self.at_declaration_start() && self.current().value == "wrt" {
4599 self.advance();
4600 if self.check(TokenType::LBracket) {
4601 wrt = self.parse_bracketed_identifiers()?;
4602 } else {
4603 wrt.push(self.consume_any_ident_or_kw()?.value.clone());
4604 }
4605 }
4606 if !self.at_declaration_start() && self.current().value == "as" {
4607 self.advance();
4608 output = self.consume_any_ident_or_kw()?.value.clone();
4609 }
4610 Ok(FlowStep::Grad(GradStep {
4611 target,
4612 wrt,
4613 output,
4614 loc: Loc {
4615 line: tok.line,
4616 column: tok.column,
4617 },
4618 }))
4619 }
4620
4621 fn parse_focus_step(&mut self) -> Result<FlowStep, ParseError> {
4622 let tok = self.current().clone();
4623 self.advance();
4624 let expression = if self.at_declaration_start()
4625 || self.check(TokenType::RBrace)
4626 || self.check(TokenType::Eof)
4627 {
4628 String::new()
4629 } else {
4630 self.consume_any_ident_or_kw()?.value.clone()
4631 };
4632 let mut where_expr = String::new();
4633 let mut select: Vec<String> = Vec::new();
4634 let mut output = String::new();
4635 if self.check(TokenType::LBrace) {
4636 self.advance();
4637 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4638 if self.check(TokenType::Comma) {
4639 self.advance();
4640 continue;
4641 }
4642 let f = self.current().value.clone();
4643 self.advance();
4644 if self.check(TokenType::Colon) {
4645 self.advance();
4646 match f.as_str() {
4647 "where" => {
4648 where_expr = self.consume(TokenType::StringLit)?.value.clone()
4649 }
4650 "select" => select = self.parse_bracketed_identifiers()?,
4651 "as" | "alias" => {
4652 output = self.consume_any_ident_or_kw()?.value.clone()
4653 }
4654 _ => self.skip_value(),
4655 }
4656 }
4657 }
4658 if self.check(TokenType::RBrace) {
4659 self.advance();
4660 }
4661 }
4662 Ok(FlowStep::Focus(FocusStep {
4663 expression,
4664 where_expr,
4665 select,
4666 output,
4667 loc: Loc {
4668 line: tok.line,
4669 column: tok.column,
4670 },
4671 }))
4672 }
4673
4674 fn parse_associate_step(&mut self) -> Result<FlowStep, ParseError> {
4675 let tok = self.current().clone();
4676 self.advance();
4677 let left = self.consume_any_ident_or_kw()?.value.clone();
4678 let mut right = String::new();
4679 let mut using = String::new();
4680 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4681 right = self.consume_any_ident_or_kw()?.value.clone();
4682 }
4683 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4684 let next = self.current().clone();
4685 if next.value == "using" {
4686 self.advance();
4687 using = self.consume_any_ident_or_kw()?.value.clone();
4688 }
4689 }
4690 let mut output = String::new();
4691 if self.check(TokenType::LBrace) {
4692 self.advance();
4693 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4694 let f = self.current().value.clone();
4695 self.advance();
4696 if self.check(TokenType::Colon) {
4697 self.advance();
4698 match f.as_str() {
4699 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
4700 _ => self.skip_value(),
4701 }
4702 }
4703 }
4704 if self.check(TokenType::RBrace) {
4705 self.advance();
4706 }
4707 }
4708 Ok(FlowStep::Associate(AssociateStep {
4709 left,
4710 right,
4711 using_field: using,
4712 output,
4713 loc: Loc {
4714 line: tok.line,
4715 column: tok.column,
4716 },
4717 }))
4718 }
4719
4720 fn parse_aggregate_step(&mut self) -> Result<FlowStep, ParseError> {
4721 let tok = self.current().clone();
4722 self.advance();
4723 let target = self.consume_any_ident_or_kw()?.value.clone();
4724 let mut group_by = Vec::new();
4725 let mut alias = String::new();
4726 let mut compute: Vec<String> = Vec::new();
4727 let mut where_expr = String::new();
4728 if self.check(TokenType::LBrace) {
4729 self.advance();
4730 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4731 let f = self.current().value.clone();
4732 self.advance();
4733 if self.check(TokenType::Colon) {
4734 self.advance();
4735 match f.as_str() {
4736 "group_by" => group_by = self.parse_bracketed_identifiers()?,
4737 "alias" | "as" => alias = self.consume_any_ident_or_kw()?.value.clone(),
4738 "compute" => compute = self.parse_bracketed_aggregates()?,
4741 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
4743 _ => self.skip_value(),
4744 }
4745 }
4746 }
4747 if self.check(TokenType::RBrace) {
4748 self.advance();
4749 }
4750 }
4751 Ok(FlowStep::Aggregate(AggregateStep {
4752 target,
4753 group_by,
4754 alias,
4755 compute,
4756 where_expr,
4757 loc: Loc {
4758 line: tok.line,
4759 column: tok.column,
4760 },
4761 }))
4762 }
4763
4764 fn parse_explore_step(&mut self) -> Result<FlowStep, ParseError> {
4765 let tok = self.current().clone();
4766 self.advance();
4767 let target = self.consume_any_ident_or_kw()?.value.clone();
4768 let mut limit = None;
4769 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4770 if self.current().ttype == TokenType::Integer {
4771 limit = self.current().value.parse::<i64>().ok();
4772 self.advance();
4773 }
4774 }
4775 let mut output = String::new();
4776 if self.check(TokenType::LBrace) {
4777 self.advance();
4778 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4779 let f = self.current().value.clone();
4780 self.advance();
4781 if self.check(TokenType::Colon) {
4782 self.advance();
4783 match f.as_str() {
4784 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
4785 _ => self.skip_value(),
4786 }
4787 }
4788 }
4789 if self.check(TokenType::RBrace) {
4790 self.advance();
4791 }
4792 }
4793 Ok(FlowStep::ExploreStep(ExploreStepNode {
4794 target,
4795 limit,
4796 output,
4797 loc: Loc {
4798 line: tok.line,
4799 column: tok.column,
4800 },
4801 }))
4802 }
4803
4804 fn parse_bracketed_aggregates(&mut self) -> Result<Vec<String>, ParseError> {
4807 let mut out = Vec::new();
4808 self.consume(TokenType::LBracket)?;
4809 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4810 let name = self.consume_any_ident_or_kw()?.value.clone();
4811 if self.check(TokenType::LParen) {
4812 self.advance();
4813 let col = self.consume_any_ident_or_kw()?.value.clone();
4814 self.consume(TokenType::RParen)?;
4815 out.push(format!("{name}({col})"));
4816 } else {
4817 out.push(name);
4818 }
4819 if self.check(TokenType::Comma) {
4820 self.advance();
4821 }
4822 }
4823 self.consume(TokenType::RBracket)?;
4824 Ok(out)
4825 }
4826
4827 fn parse_ingest_step(&mut self) -> Result<FlowStep, ParseError> {
4842 let tok = self.current().clone();
4843 self.advance();
4844 let source = self.consume_any_ident_or_kw()?.value.clone();
4845 let mut target = String::new();
4846 let mut format = String::new();
4847 let mut max_bytes: Option<u64> = None;
4848 let mut max_rows: Option<u64> = None;
4849 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4850 let next = self.current().clone();
4851 if next.value == "into" || next.ttype == TokenType::Into {
4852 self.advance();
4853 target = self.consume_any_ident_or_kw()?.value.clone();
4854 }
4855 }
4856 if self.check(TokenType::LBrace) {
4857 self.consume(TokenType::LBrace)?;
4858 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4859 if self.check(TokenType::Comma) {
4861 self.advance();
4862 continue;
4863 }
4864 let entry = self.current().clone();
4865 match entry.value.as_str() {
4866 "format" => {
4867 self.advance();
4868 self.consume(TokenType::Colon)?;
4869 format = self.consume_any_ident_or_kw()?.value.clone();
4870 }
4871 "limits" => {
4872 self.advance();
4873 self.consume(TokenType::LBrace)?;
4874 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4875 let bound = self.current().clone();
4876 self.advance();
4877 self.consume(TokenType::Colon)?;
4878 let num_tok = self.consume(TokenType::Integer)?.clone();
4879 let value = num_tok.value.parse::<u64>().map_err(|_| ParseError {
4880 message: format!(
4881 "ingest `limits` bound `{}` must be a non-negative \
4882 integer byte/row count, got `{}`.",
4883 bound.value, num_tok.value
4884 ),
4885 line: num_tok.line,
4886 column: num_tok.column,
4887 ..Default::default()
4888 })?;
4889 match bound.value.as_str() {
4890 "max_bytes" => max_bytes = Some(value),
4891 "max_rows" => max_rows = Some(value),
4892 other => {
4893 return Err(ParseError {
4894 message: format!(
4895 "Unknown ingest limit `{other}`. The closed \
4896 limits grammar is `max_bytes: <N>` and \
4897 `max_rows: <N>` — bounds enforced on the raw \
4898 stream BEFORE parsing (§100).",
4899 ),
4900 line: bound.line,
4901 column: bound.column,
4902 ..Default::default()
4903 });
4904 }
4905 }
4906 if self.check(TokenType::Comma) {
4907 self.advance();
4908 }
4909 }
4910 self.consume(TokenType::RBrace)?;
4911 }
4912 other => {
4913 return Err(ParseError {
4914 message: format!(
4915 "Unknown entry `{other}` in ingest body. The closed \
4916 grammar is `format: csv|json` and \
4917 `limits {{ max_bytes: <N>, max_rows: <N> }}`.",
4918 ),
4919 line: entry.line,
4920 column: entry.column,
4921 ..Default::default()
4922 });
4923 }
4924 }
4925 }
4926 self.consume(TokenType::RBrace)?;
4927 }
4928 Ok(FlowStep::Ingest(IngestStep {
4929 source,
4930 target,
4931 format,
4932 max_bytes,
4933 max_rows,
4934 loc: Loc {
4935 line: tok.line,
4936 column: tok.column,
4937 },
4938 }))
4939 }
4940
4941 fn parse_navigate_step(&mut self) -> Result<FlowStep, ParseError> {
4942 let tok = self.current().clone();
4943 self.advance();
4944 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
4945 let mut node = NavigateStep {
4946 pix_name,
4947 corpus_name: String::new(),
4948 query_expr: String::new(),
4949 trail_enabled: false,
4950 output_name: String::new(),
4951 seed: String::new(),
4952 budget: None,
4953 where_expr: String::new(),
4954 loc: Loc {
4955 line: tok.line,
4956 column: tok.column,
4957 },
4958 };
4959 if self.check(TokenType::LBrace) {
4960 self.advance();
4961 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4962 let f = self.current().value.clone();
4963 self.advance();
4964 if self.check(TokenType::Colon) {
4965 self.advance();
4966 match f.as_str() {
4967 "corpus" => {
4968 node.corpus_name = self.consume_any_ident_or_kw()?.value.clone()
4969 }
4970 "query" => {
4971 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
4972 }
4973 "trail" => {
4974 node.trail_enabled = self.consume_any_ident_or_kw()?.value == "true"
4975 }
4976 "output" | "as" => {
4977 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
4978 }
4979 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
4981 "budget" => node.budget = self.parse_optional_int(),
4982 "where" => {
4990 node.where_expr = self.consume(TokenType::StringLit)?.value.clone()
4991 }
4992 _ => self.skip_value(),
4993 }
4994 }
4995 }
4996 if self.check(TokenType::RBrace) {
4997 self.advance();
4998 }
4999 }
5000 Ok(FlowStep::Navigate(node))
5001 }
5002
5003 fn parse_drill_step(&mut self) -> Result<FlowStep, ParseError> {
5004 let tok = self.current().clone();
5005 self.advance();
5006 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
5007 let mut node = DrillStep {
5008 pix_name,
5009 subtree_path: String::new(),
5010 query_expr: String::new(),
5011 output_name: String::new(),
5012 loc: Loc {
5013 line: tok.line,
5014 column: tok.column,
5015 },
5016 };
5017 if self.check(TokenType::LBrace) {
5018 self.advance();
5019 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5020 let f = self.current().value.clone();
5021 self.advance();
5022 if self.check(TokenType::Colon) {
5023 self.advance();
5024 match f.as_str() {
5025 "subtree" | "path" => {
5026 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
5027 }
5028 "query" => {
5029 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
5030 }
5031 "output" | "as" => {
5032 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5033 }
5034 _ => self.skip_value(),
5035 }
5036 }
5037 }
5038 if self.check(TokenType::RBrace) {
5039 self.advance();
5040 }
5041 }
5042 Ok(FlowStep::Drill(node))
5043 }
5044
5045 fn parse_corroborate_step(&mut self) -> Result<FlowStep, ParseError> {
5046 let tok = self.current().clone();
5047 self.advance();
5048 let nav_ref = self.consume_any_ident_or_kw()?.value.clone();
5049 let mut output = String::new();
5050 if self.check(TokenType::Arrow) {
5051 self.advance();
5052 output = self.consume_any_ident_or_kw()?.value.clone();
5053 }
5054 Ok(FlowStep::Corroborate(CorroborateStep {
5055 navigate_ref: nav_ref,
5056 output_name: output,
5057 loc: Loc {
5058 line: tok.line,
5059 column: tok.column,
5060 },
5061 }))
5062 }
5063
5064 fn parse_listen_step(&mut self) -> Result<FlowStep, ParseError> {
5065 let tok = self.current().clone();
5066 self.advance();
5067 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
5071 (self.consume(TokenType::StringLit)?.value.clone(), false)
5072 } else {
5073 (self.consume_any_ident_or_kw()?.value.clone(), true)
5074 };
5075 let mut alias = String::new();
5076 if !self.at_declaration_start()
5077 && !self.check(TokenType::RBrace)
5078 && !self.check(TokenType::LBrace)
5079 {
5080 let next = self.current().clone();
5081 if next.value == "as" || next.ttype == TokenType::As {
5082 self.advance();
5083 alias = self.consume_any_ident_or_kw()?.value.clone();
5084 }
5085 }
5086 let body = self.parse_listener_body()?;
5090 Ok(FlowStep::Listen(ListenStep {
5091 channel,
5092 channel_is_ref,
5093 event_alias: alias,
5094 body,
5095 loc: Loc {
5096 line: tok.line,
5097 column: tok.column,
5098 },
5099 }))
5100 }
5101
5102 fn parse_listener_body(&mut self) -> Result<Vec<FlowStep>, ParseError> {
5107 let mut body = Vec::new();
5108 if self.check(TokenType::LBrace) {
5109 self.advance(); while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5111 body.push(self.parse_flow_step()?);
5112 }
5113 self.consume(TokenType::RBrace)?;
5114 }
5115 Ok(body)
5116 }
5117
5118 fn parse_retrieve_step(&mut self) -> Result<FlowStep, ParseError> {
5119 let tok = self.current().clone();
5120 self.advance();
5121 let store = self.consume_any_ident_or_kw()?.value.clone();
5122 let mut where_expr = String::new();
5123 let mut alias = String::new();
5124 let mut order_by = String::new();
5125 let mut limit_expr = String::new();
5126 let mut aggregate = String::new();
5127 let mut group_by = String::new();
5128 let mut cache = String::new();
5129 if self.check(TokenType::LBrace) {
5130 self.advance();
5131 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5132 let f = self.current().value.clone();
5133 self.advance();
5134 if self.check(TokenType::Colon) {
5135 self.advance();
5136 match f.as_str() {
5137 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
5138 "as" | "alias" => alias = self.consume_any_ident_or_kw()?.value.clone(),
5139 "order_by" => {
5142 order_by = self.consume(TokenType::StringLit)?.value.clone()
5143 }
5144 "limit" => {
5149 let t = self.current().clone();
5150 match t.ttype {
5151 TokenType::Integer | TokenType::StringLit => {
5152 limit_expr = t.value.clone();
5153 self.advance();
5154 }
5155 _ => self.skip_value(),
5156 }
5157 }
5158 "aggregate" => {
5165 aggregate = self.consume(TokenType::StringLit)?.value.clone()
5166 }
5167 "group_by" => {
5168 group_by = self.consume(TokenType::StringLit)?.value.clone()
5169 }
5170 "cache" => cache = self.consume_any_ident_or_kw()?.value.clone(),
5176 _ => self.skip_value(),
5177 }
5178 }
5179 }
5180 if self.check(TokenType::RBrace) {
5181 self.advance();
5182 }
5183 }
5184 Ok(FlowStep::Retrieve(RetrieveStep {
5185 store_name: store,
5186 where_expr,
5187 alias,
5188 order_by,
5189 limit_expr,
5190 aggregate,
5191 group_by,
5192 cache,
5193 loc: Loc {
5194 line: tok.line,
5195 column: tok.column,
5196 },
5197 }))
5198 }
5199
5200 fn parse_store_where_step(
5213 &mut self,
5214 ) -> Result<(Loc, String, String), ParseError> {
5215 let tok = self.current().clone();
5216 self.advance(); let store = if self.at_declaration_start()
5218 || self.check(TokenType::RBrace)
5219 || self.check(TokenType::Eof)
5220 {
5221 String::new()
5222 } else {
5223 self.consume_any_ident_or_kw()?.value.clone()
5224 };
5225 let mut where_expr = String::new();
5226 if self.check(TokenType::LBrace) {
5227 self.advance();
5228 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5229 let field = self.current().value.clone();
5230 self.advance();
5231 if self.check(TokenType::Colon) {
5232 self.advance();
5233 match field.as_str() {
5234 "where" => {
5235 where_expr =
5236 self.consume(TokenType::StringLit)?.value.clone()
5237 }
5238 _ => self.skip_value(),
5239 }
5240 }
5241 }
5242 if self.check(TokenType::RBrace) {
5243 self.advance();
5244 }
5245 }
5246 Ok((
5247 Loc {
5248 line: tok.line,
5249 column: tok.column,
5250 },
5251 store,
5252 where_expr,
5253 ))
5254 }
5255
5256 fn parse_persist_step(&mut self) -> Result<FlowStep, ParseError> {
5275 let tok = self.current().clone();
5276 self.advance(); if self.current().value == "into" && !self.check(TokenType::LBrace) {
5280 self.advance();
5281 }
5282 let store = if self.at_declaration_start()
5283 || self.check(TokenType::LBrace)
5284 || self.check(TokenType::RBrace)
5285 || self.check(TokenType::Eof)
5286 {
5287 String::new()
5288 } else {
5289 self.consume_any_ident_or_kw()?.value.clone()
5290 };
5291 let mut fields: Vec<(String, String)> = Vec::new();
5292 if self.check(TokenType::LBrace) {
5293 self.advance();
5294 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5295 let col = self.current().value.clone();
5296 self.advance();
5297 if self.check(TokenType::Colon) {
5298 self.advance();
5299 let value = if self.check(TokenType::StringLit) {
5300 self.consume(TokenType::StringLit)?.value.clone()
5301 } else if self.check(TokenType::RBrace)
5302 || self.check(TokenType::Eof)
5303 || self.check(TokenType::Colon)
5304 {
5305 String::new()
5306 } else {
5307 let v = self.current().clone();
5308 self.advance();
5309 v.value.clone()
5310 };
5311 fields.push((col, value));
5312 }
5313 }
5314 if self.check(TokenType::RBrace) {
5315 self.advance();
5316 }
5317 }
5318 Ok(FlowStep::Persist(PersistStep {
5319 store_name: store,
5320 fields,
5321 loc: Loc {
5322 line: tok.line,
5323 column: tok.column,
5324 },
5325 }))
5326 }
5327
5328 fn parse_mutate_step(&mut self) -> Result<FlowStep, ParseError> {
5342 let tok = self.current().clone();
5343 self.advance(); let store = if self.at_declaration_start()
5345 || self.check(TokenType::LBrace)
5346 || self.check(TokenType::RBrace)
5347 || self.check(TokenType::Eof)
5348 {
5349 String::new()
5350 } else {
5351 self.consume_any_ident_or_kw()?.value.clone()
5352 };
5353 let mut where_expr = String::new();
5354 let mut fields: Vec<(String, String)> = Vec::new();
5355 if self.check(TokenType::LBrace) {
5356 self.advance();
5357 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5358 let key = self.current().value.clone();
5359 self.advance();
5360 if self.check(TokenType::Colon) {
5361 self.advance();
5362 if key == "where" {
5363 where_expr =
5364 self.consume(TokenType::StringLit)?.value.clone();
5365 } else {
5366 let value = if self.check(TokenType::StringLit) {
5367 self.consume(TokenType::StringLit)?.value.clone()
5368 } else if self.check(TokenType::RBrace)
5369 || self.check(TokenType::Eof)
5370 || self.check(TokenType::Colon)
5371 {
5372 String::new()
5373 } else {
5374 let v = self.current().clone();
5375 self.advance();
5376 v.value.clone()
5377 };
5378 fields.push((key, value));
5379 }
5380 }
5381 }
5382 if self.check(TokenType::RBrace) {
5383 self.advance();
5384 }
5385 }
5386 Ok(FlowStep::Mutate(MutateStep {
5387 store_name: store,
5388 where_expr,
5389 fields,
5390 loc: Loc {
5391 line: tok.line,
5392 column: tok.column,
5393 },
5394 }))
5395 }
5396
5397 fn parse_agent(&mut self) -> Result<AgentDefinition, ParseError> {
5400 let tok = self.consume(TokenType::Agent)?;
5401 let name = self.consume(TokenType::Identifier)?.value;
5402 let mut node = AgentDefinition {
5403 name,
5404 goal: String::new(),
5405 tools: Vec::new(),
5406 memory_ref: String::new(),
5407 strategy: String::new(),
5408 on_stuck: String::new(),
5409 shield_ref: String::new(),
5410 max_iterations: None,
5411 max_tokens: None,
5412 max_time: String::new(),
5413 max_cost: None,
5414 loc: Loc {
5415 line: tok.line,
5416 column: tok.column,
5417 },
5418 leading_trivia: Vec::new(),
5419 trailing_trivia: Vec::new(),
5420 };
5421 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
5423 self.advance();
5424 }
5425 self.consume(TokenType::LBrace)?;
5426 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5427 let field = self.current().clone();
5428 let field_name = field.value.clone();
5429 self.advance();
5430 if self.check(TokenType::Colon) {
5431 self.advance();
5432 match field_name.as_str() {
5433 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
5434 "tools" => node.tools = self.parse_bracketed_identifiers()?,
5435 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
5436 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
5437 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
5438 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
5439 "max_iterations" => node.max_iterations = self.parse_optional_int(),
5440 "max_tokens" => node.max_tokens = self.parse_optional_int(),
5441 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
5442 "max_cost" => node.max_cost = self.parse_optional_float(),
5443 _ => self.skip_value(),
5444 }
5445 } else if self.check(TokenType::LBrace) {
5446 self.skip_braced_block()?;
5447 }
5448 }
5449 self.consume(TokenType::RBrace)?;
5450 Ok(node)
5451 }
5452
5453 fn parse_extension(&mut self) -> Result<ExtensionDefinition, ParseError> {
5458 let tok = self.consume(TokenType::Extension)?;
5459 let name = self.consume(TokenType::Identifier)?.value;
5460 let mut node = ExtensionDefinition {
5461 name,
5462 category: String::new(),
5463 members: Vec::new(),
5464 loc: Loc {
5465 line: tok.line,
5466 column: tok.column,
5467 },
5468 leading_trivia: Vec::new(),
5469 trailing_trivia: Vec::new(),
5470 };
5471 self.consume(TokenType::LBrace)?;
5472 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5473 let field_name = self.current().value.clone();
5474 self.advance();
5475 if self.check(TokenType::Colon) {
5476 self.advance();
5477 match field_name.as_str() {
5478 "category" => {
5479 node.category = self.consume_any_ident_or_kw()?.value.clone()
5480 }
5481 "members" => node.members = self.parse_extension_members()?,
5482 _ => self.skip_value(),
5483 }
5484 } else if self.check(TokenType::LBrace) {
5485 self.skip_braced_block()?;
5486 }
5487 }
5488 self.consume(TokenType::RBrace)?;
5489 Ok(node)
5490 }
5491
5492 fn parse_extension_members(&mut self) -> Result<Vec<ExtensionMember>, ParseError> {
5496 let mut members = Vec::new();
5497 self.consume(TokenType::LBracket)?;
5498 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5499 let name_tok = self.consume(TokenType::StringLit)?;
5500 let mut member = ExtensionMember {
5501 name: name_tok.value.clone(),
5502 semantics: None,
5503 default_confidence: None,
5504 loc: Loc {
5505 line: name_tok.line,
5506 column: name_tok.column,
5507 },
5508 };
5509 if self.check(TokenType::Colon) {
5511 self.advance();
5512 self.consume(TokenType::LBrace)?;
5513 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5514 let mkey = self.current().value.clone();
5515 self.advance();
5516 if self.check(TokenType::Colon) {
5517 self.advance();
5518 match mkey.as_str() {
5519 "semantics" => {
5520 member.semantics =
5521 Some(self.consume(TokenType::StringLit)?.value.clone())
5522 }
5523 "default_confidence" => {
5524 member.default_confidence = self.parse_optional_float()
5525 }
5526 _ => self.skip_value(),
5527 }
5528 }
5529 if self.check(TokenType::Comma) {
5530 self.advance();
5531 }
5532 }
5533 self.consume(TokenType::RBrace)?;
5534 }
5535 members.push(member);
5536 if self.check(TokenType::Comma) {
5537 self.advance();
5538 }
5539 }
5540 self.consume(TokenType::RBracket)?;
5541 Ok(members)
5542 }
5543
5544 fn parse_window(&mut self) -> Result<WindowDefinition, ParseError> {
5547 let tok = self.consume(TokenType::Window)?;
5548 let name = self.consume(TokenType::Identifier)?.value;
5549 let mut node = WindowDefinition {
5550 name,
5551 timezone: String::new(),
5552 allow: Vec::new(),
5553 exclude: Vec::new(),
5554 on_outside: String::new(),
5555 loc: Loc {
5556 line: tok.line,
5557 column: tok.column,
5558 },
5559 leading_trivia: Vec::new(),
5560 trailing_trivia: Vec::new(),
5561 };
5562 self.consume(TokenType::LBrace)?;
5563 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5564 let field_name = self.consume_any_ident_or_kw()?.value;
5565 self.consume(TokenType::Colon)?;
5566 match field_name.as_str() {
5567 "timezone" => node.timezone = self.consume(TokenType::StringLit)?.value,
5568 "allow" => node.allow = self.parse_window_allow()?,
5569 "exclude" => node.exclude = self.parse_window_exclude()?,
5570 "on_outside" => node.on_outside = self.consume_any_ident_or_kw()?.value,
5571 _ => self.skip_value(),
5572 }
5573 }
5574 self.consume(TokenType::RBrace)?;
5575 Ok(node)
5576 }
5577
5578 fn parse_window_allow(&mut self) -> Result<Vec<WindowSpan>, ParseError> {
5580 self.consume(TokenType::LBracket)?;
5581 let mut spans = Vec::new();
5582 if !self.check(TokenType::RBracket) {
5583 spans.push(self.parse_window_span()?);
5584 while self.check(TokenType::Comma) {
5585 self.advance();
5586 if self.check(TokenType::RBracket) {
5587 break; }
5589 spans.push(self.parse_window_span()?);
5590 }
5591 }
5592 self.consume(TokenType::RBracket)?;
5593 Ok(spans)
5594 }
5595
5596 fn parse_window_exclude(&mut self) -> Result<Vec<String>, ParseError> {
5600 self.consume(TokenType::LBracket)?;
5601 let mut dates = Vec::new();
5602 if !self.check(TokenType::RBracket) {
5603 dates.push(self.consume(TokenType::StringLit)?.value);
5604 while self.check(TokenType::Comma) {
5605 self.advance();
5606 if self.check(TokenType::RBracket) {
5607 break; }
5609 dates.push(self.consume(TokenType::StringLit)?.value);
5610 }
5611 }
5612 self.consume(TokenType::RBracket)?;
5613 Ok(dates)
5614 }
5615
5616 fn parse_window_span(&mut self) -> Result<WindowSpan, ParseError> {
5618 let tok = self.consume(TokenType::LBrace)?;
5619 let mut span = WindowSpan {
5620 day_start: String::new(),
5621 day_end: String::new(),
5622 hour_start: 0,
5623 hour_end: 0,
5624 loc: Loc {
5625 line: tok.line,
5626 column: tok.column,
5627 },
5628 };
5629 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5630 let field = self.consume_any_ident_or_kw()?.value;
5631 self.consume(TokenType::Colon)?;
5632 match field.as_str() {
5633 "days" => {
5634 span.day_start = self.consume_any_ident_or_kw()?.value;
5635 self.consume(TokenType::DotDot)?;
5636 span.day_end = self.consume_any_ident_or_kw()?.value;
5637 }
5638 "hours" => {
5639 span.hour_start = self.consume_number()? as i64;
5640 self.consume(TokenType::DotDot)?;
5641 span.hour_end = self.consume_number()? as i64;
5642 }
5643 _ => self.skip_value(),
5644 }
5645 if self.check(TokenType::Comma) {
5646 self.advance();
5647 }
5648 }
5649 self.consume(TokenType::RBrace)?;
5650 Ok(span)
5651 }
5652
5653 fn parse_shield(&mut self) -> Result<ShieldDefinition, ParseError> {
5654 let tok = self.consume(TokenType::Shield)?;
5655 let name = self.consume(TokenType::Identifier)?.value;
5656 let mut node = ShieldDefinition {
5657 name,
5658 scan: Vec::new(),
5659 strategy: String::new(),
5660 on_breach: String::new(),
5661 severity: String::new(),
5662 quarantine: String::new(),
5663 max_retries: None,
5664 confidence_threshold: None,
5665 allow_tools: Vec::new(),
5666 deny_tools: Vec::new(),
5667 sandbox: None,
5668 redact: Vec::new(),
5669 log: String::new(),
5670 deflect_message: String::new(),
5671 taint: String::new(),
5672 compliance: Vec::new(),
5673 sign: String::new(),
5674 unknown_fields: Vec::new(),
5675 loc: Loc {
5676 line: tok.line,
5677 column: tok.column,
5678 },
5679 leading_trivia: Vec::new(),
5680 trailing_trivia: Vec::new(),
5681 };
5682 self.consume(TokenType::LBrace)?;
5683 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5684 let field_name = self.current().value.clone();
5685 let field_loc = Loc {
5686 line: self.current().line,
5687 column: self.current().column,
5688 };
5689 self.advance();
5690 if self.check(TokenType::Colon) {
5691 self.advance();
5692 match field_name.as_str() {
5693 "scan" => node.scan = self.parse_bracketed_identifiers()?,
5694 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
5695 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
5696 "severity" => node.severity = self.consume_any_ident_or_kw()?.value.clone(),
5697 "quarantine" => {
5698 node.quarantine = self.consume(TokenType::StringLit)?.value.clone()
5699 }
5700 "max_retries" => node.max_retries = self.parse_optional_int(),
5701 "confidence_threshold" => {
5702 node.confidence_threshold = self.parse_optional_float()
5703 }
5704 "allow_tools" => node.allow_tools = self.parse_bracketed_identifiers()?,
5705 "deny_tools" => node.deny_tools = self.parse_bracketed_identifiers()?,
5706 "sandbox" => {
5707 node.sandbox = Some(self.consume_any_ident_or_kw()?.value == "true")
5708 }
5709 "redact" => node.redact = self.parse_bracketed_identifiers()?,
5710 "log" => node.log = self.consume_any_ident_or_kw()?.value.clone(),
5711 "deflect_message" => {
5712 node.deflect_message = self.consume(TokenType::StringLit)?.value.clone()
5713 }
5714 "taint" => node.taint = self.consume_any_ident_or_kw()?.value.clone(),
5715 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
5717 "sign" => node.sign = self.consume_any_ident_or_kw()?.value.clone(),
5720 _ => {
5724 node.unknown_fields.push((field_name.clone(), field_loc));
5725 self.skip_value()
5726 }
5727 }
5728 } else if self.check(TokenType::LBrace) {
5729 self.skip_braced_block()?;
5730 }
5731 }
5732 self.consume(TokenType::RBrace)?;
5733 Ok(node)
5734 }
5735
5736 fn parse_pix(&mut self) -> Result<PixDefinition, ParseError> {
5737 let tok = self.consume(TokenType::Pix)?;
5738 let name = self.consume(TokenType::Identifier)?.value;
5739 let mut node = PixDefinition {
5740 name,
5741 source: String::new(),
5742 depth: None,
5743 branching: None,
5744 model: String::new(),
5745 loc: Loc {
5746 line: tok.line,
5747 column: tok.column,
5748 },
5749 leading_trivia: Vec::new(),
5750 trailing_trivia: Vec::new(),
5751 };
5752 self.consume(TokenType::LBrace)?;
5753 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5754 let field_name = self.current().value.clone();
5755 self.advance();
5756 if self.check(TokenType::Colon) {
5757 self.advance();
5758 match field_name.as_str() {
5759 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
5760 "depth" => node.depth = self.parse_optional_int(),
5761 "branching" => node.branching = self.parse_optional_int(),
5762 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
5763 _ => self.skip_value(),
5764 }
5765 } else if self.check(TokenType::LBrace) {
5766 self.skip_braced_block()?;
5767 }
5768 }
5769 self.consume(TokenType::RBrace)?;
5770 Ok(node)
5771 }
5772
5773 fn parse_ledger(&mut self) -> Result<LedgerDefinition, ParseError> {
5779 let tok = self.consume(TokenType::Ledger)?;
5780 let name = self.consume(TokenType::Identifier)?.value;
5781 let mut node = LedgerDefinition {
5782 name,
5783 source: String::new(),
5784 depth: None,
5785 branching: None,
5786 model: String::new(),
5787 loc: Loc {
5788 line: tok.line,
5789 column: tok.column,
5790 },
5791 leading_trivia: Vec::new(),
5792 trailing_trivia: Vec::new(),
5793 };
5794 self.consume(TokenType::LBrace)?;
5795 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5796 let field_name = self.current().value.clone();
5797 self.advance();
5798 if self.check(TokenType::Colon) {
5799 self.advance();
5800 match field_name.as_str() {
5801 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
5802 "depth" => node.depth = self.parse_optional_int(),
5803 "branching" => node.branching = self.parse_optional_int(),
5804 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
5805 _ => self.skip_value(),
5806 }
5807 } else if self.check(TokenType::LBrace) {
5808 self.skip_braced_block()?;
5809 }
5810 }
5811 self.consume(TokenType::RBrace)?;
5812 Ok(node)
5813 }
5814
5815 fn parse_psyche(&mut self) -> Result<PsycheDefinition, ParseError> {
5816 let tok = self.consume(TokenType::Psyche)?;
5817 let name = self.consume(TokenType::Identifier)?.value;
5818 let mut node = PsycheDefinition {
5819 name,
5820 dimensions: Vec::new(),
5821 manifold_noise: None,
5822 manifold_momentum: None,
5823 safety_constraints: Vec::new(),
5824 quantum_enabled: None,
5825 inference_mode: String::new(),
5826 loc: Loc {
5827 line: tok.line,
5828 column: tok.column,
5829 },
5830 leading_trivia: Vec::new(),
5831 trailing_trivia: Vec::new(),
5832 };
5833 self.consume(TokenType::LBrace)?;
5834 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5835 let field_name = self.current().value.clone();
5836 self.advance();
5837 if self.check(TokenType::Colon) {
5838 self.advance();
5839 match field_name.as_str() {
5840 "dimensions" => node.dimensions = self.parse_bracketed_identifiers()?,
5841 "manifold_noise" => node.manifold_noise = self.parse_optional_float(),
5842 "manifold_momentum" => node.manifold_momentum = self.parse_optional_float(),
5843 "safety_constraints" => {
5844 node.safety_constraints = self.parse_bracketed_identifiers()?
5845 }
5846 "quantum_enabled" => {
5847 node.quantum_enabled = Some(self.consume_any_ident_or_kw()?.value == "true")
5848 }
5849 "inference_mode" => {
5850 node.inference_mode = self.consume_any_ident_or_kw()?.value.clone()
5851 }
5852 _ => self.skip_value(),
5853 }
5854 } else if self.check(TokenType::LBrace) {
5855 self.skip_braced_block()?;
5856 }
5857 }
5858 self.consume(TokenType::RBrace)?;
5859 Ok(node)
5860 }
5861
5862 fn parse_corpus(&mut self) -> Result<CorpusDefinition, ParseError> {
5863 let tok = self.consume(TokenType::Corpus)?;
5864 let name = self.consume(TokenType::Identifier)?.value;
5865 let mut node = CorpusDefinition {
5866 name,
5867 documents: Vec::new(),
5868 relations: Vec::new(),
5869 adaptive: false,
5870 mcp_server: String::new(),
5871 mcp_resource_uri: String::new(),
5872 store_source: None,
5873 loc: Loc {
5874 line: tok.line,
5875 column: tok.column,
5876 },
5877 leading_trivia: Vec::new(),
5878 trailing_trivia: Vec::new(),
5879 };
5880 let mut dynamic = false;
5884 if self.check(TokenType::From) {
5885 self.advance();
5886 if self.check(TokenType::AxonStore) {
5887 self.advance();
5888 dynamic = true;
5889 } else {
5890 self.consume(TokenType::Mcp)?;
5891 self.consume(TokenType::LParen)?;
5892 node.mcp_server = self.consume(TokenType::StringLit)?.value.clone();
5893 self.consume(TokenType::Comma)?;
5894 node.mcp_resource_uri = self.consume(TokenType::StringLit)?.value.clone();
5895 self.consume(TokenType::RParen)?;
5896 return Ok(node);
5897 }
5898 }
5899 self.consume(TokenType::LBrace)?;
5900 let mut src = CorpusStoreSource {
5903 doc_store: String::new(),
5904 doc_id_col: String::new(),
5905 doc_title_col: String::new(),
5906 edge_store: String::new(),
5907 edge_from_col: String::new(),
5908 edge_to_col: String::new(),
5909 edge_type_col: String::new(),
5910 edge_weight_col: String::new(),
5911 loc: Loc {
5912 line: tok.line,
5913 column: tok.column,
5914 },
5915 };
5916 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5917 let field_name = self.current().value.clone();
5918 self.advance();
5919 if self.check(TokenType::Colon) {
5920 self.advance();
5921 match field_name.as_str() {
5922 "documents" if dynamic => {
5924 let (store, cols) = self.parse_corpus_store_mapping(2)?;
5925 src.doc_store = store;
5926 src.doc_id_col = cols[0].clone();
5927 src.doc_title_col = cols[1].clone();
5928 }
5929 "documents" => node.documents = self.parse_bracketed_identifiers()?,
5930 "relations" if dynamic => {
5932 let (store, cols) = self.parse_corpus_store_mapping(4)?;
5933 src.edge_store = store;
5934 src.edge_from_col = cols[0].clone();
5935 src.edge_to_col = cols[1].clone();
5936 src.edge_type_col = cols[2].clone();
5937 src.edge_weight_col = cols[3].clone();
5938 }
5939 "relations" => node.relations = self.parse_corpus_relations()?,
5941 "adaptive" => node.adaptive = self.consume_any_ident_or_kw()?.value == "true",
5943 _ => self.skip_value(),
5944 }
5945 } else if self.check(TokenType::LBrace) {
5946 self.skip_braced_block()?;
5947 }
5948 }
5949 self.consume(TokenType::RBrace)?;
5950 if dynamic {
5951 node.store_source = Some(src);
5952 }
5953 Ok(node)
5954 }
5955
5956 fn parse_corpus_store_mapping(&mut self, n: usize) -> Result<(String, Vec<String>), ParseError> {
5963 let store = self.consume(TokenType::Identifier)?.value.clone();
5964 self.consume(TokenType::LParen)?;
5965 let mut cols = Vec::with_capacity(n);
5966 for i in 0..n {
5967 if i > 0 {
5968 self.consume(TokenType::Comma)?;
5969 }
5970 cols.push(self.consume_any_ident_or_kw()?.value.clone());
5971 }
5972 self.consume(TokenType::RParen)?;
5973 Ok((store, cols))
5974 }
5975
5976 fn parse_corpus_relations(&mut self) -> Result<Vec<CorpusRelation>, ParseError> {
5982 let mut out = Vec::new();
5983 self.consume(TokenType::LBracket)?;
5984 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5985 if self.check(TokenType::Comma) {
5986 self.advance();
5987 continue;
5988 }
5989 let tok = self.current().clone();
5990 let etype = self.consume_any_ident_or_kw()?.value.clone();
5991 self.consume(TokenType::LParen)?;
5992 let from = self.consume_any_ident_or_kw()?.value.clone();
5993 self.consume(TokenType::Comma)?;
5994 let to = self.consume_any_ident_or_kw()?.value.clone();
5995 self.consume(TokenType::Comma)?;
5996 let weight = self.consume_number()?;
5997 self.consume(TokenType::RParen)?;
5998 out.push(CorpusRelation {
5999 etype,
6000 from,
6001 to,
6002 weight,
6003 loc: Loc { line: tok.line, column: tok.column },
6004 });
6005 }
6006 self.consume(TokenType::RBracket)?;
6007 Ok(out)
6008 }
6009
6010 fn parse_dataspace(&mut self) -> Result<DataspaceDefinition, ParseError> {
6027 let tok = self.consume(TokenType::Dataspace)?;
6028 let name = self.consume(TokenType::Identifier)?.value;
6029 let mut node = DataspaceDefinition {
6030 name,
6031 columns: Vec::new(),
6032 loc: Loc {
6033 line: tok.line,
6034 column: tok.column,
6035 },
6036 leading_trivia: Vec::new(),
6037 trailing_trivia: Vec::new(),
6038 };
6039 if self.check(TokenType::LBrace) {
6040 self.consume(TokenType::LBrace)?;
6041 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6042 let entry = self.current().clone();
6043 if entry.value != "column" {
6044 return Err(ParseError {
6045 message: format!(
6046 "Unknown entry `{}` in dataspace `{}`. A dataspace body \
6047 declares its columnar schema: `column <name>: <Type>` \
6048 (one per line, over the closed type catalog — \
6049 Text, Int, Float, Bool, Timestamp, Json).",
6050 entry.value, node.name
6051 ),
6052 line: entry.line,
6053 column: entry.column,
6054 ..Default::default()
6055 });
6056 }
6057 self.advance(); let col_tok = self.current().clone();
6059 let col_name = self.consume_any_ident_or_kw()?.value.clone();
6060 self.consume(TokenType::Colon)?;
6061 let declared_type = self.consume_any_ident_or_kw()?.value.clone();
6062 node.columns.push(crate::ast::DataspaceColumn {
6063 name: col_name,
6064 declared_type,
6065 loc: Loc {
6066 line: col_tok.line,
6067 column: col_tok.column,
6068 },
6069 });
6070 }
6071 self.consume(TokenType::RBrace)?;
6072 }
6073 Ok(node)
6074 }
6075
6076 fn parse_ots(&mut self) -> Result<OtsDefinition, ParseError> {
6077 let tok = self.consume(TokenType::Ots)?;
6078 let name = self.consume(TokenType::Identifier)?.value;
6079 let mut node = OtsDefinition {
6080 name,
6081 teleology: String::new(),
6082 homotopy_search: String::new(),
6083 loss_function: String::new(),
6084 loc: Loc {
6085 line: tok.line,
6086 column: tok.column,
6087 },
6088 leading_trivia: Vec::new(),
6089 trailing_trivia: Vec::new(),
6090 };
6091 if self.check(TokenType::Lt) {
6093 while !self.check(TokenType::Gt) && !self.check(TokenType::Eof) {
6094 self.advance();
6095 }
6096 if self.check(TokenType::Gt) {
6097 self.advance();
6098 }
6099 }
6100 self.consume(TokenType::LBrace)?;
6101 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6102 let field_name = self.current().value.clone();
6103 self.advance();
6104 if self.check(TokenType::Colon) {
6105 self.advance();
6106 match field_name.as_str() {
6107 "teleology" => {
6108 node.teleology = self.consume(TokenType::StringLit)?.value.clone()
6109 }
6110 "homotopy_search" => {
6111 node.homotopy_search = self.consume_any_ident_or_kw()?.value.clone()
6112 }
6113 "loss_function" => {
6114 node.loss_function = self.consume(TokenType::StringLit)?.value.clone()
6115 }
6116 _ => self.skip_value(),
6117 }
6118 } else if self.check(TokenType::LBrace) {
6119 self.skip_braced_block()?;
6120 }
6121 }
6122 self.consume(TokenType::RBrace)?;
6123 Ok(node)
6124 }
6125
6126 fn parse_mandate(&mut self) -> Result<MandateDefinition, ParseError> {
6127 let tok = self.consume(TokenType::Mandate)?;
6128 let name = self.consume(TokenType::Identifier)?.value;
6129 let mut node = MandateDefinition {
6130 name,
6131 constraint: String::new(),
6132 kp: None,
6133 ki: None,
6134 kd: None,
6135 tolerance: None,
6136 max_steps: None,
6137 on_violation: String::new(),
6138 loc: Loc {
6139 line: tok.line,
6140 column: tok.column,
6141 },
6142 leading_trivia: Vec::new(),
6143 trailing_trivia: Vec::new(),
6144 };
6145 self.consume(TokenType::LBrace)?;
6146 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6147 let field_name = self.current().value.clone();
6148 self.advance();
6149 if self.check(TokenType::Colon) {
6150 self.advance();
6151 match field_name.as_str() {
6152 "constraint" => {
6153 node.constraint = self.consume(TokenType::StringLit)?.value.clone()
6154 }
6155 "kp" | "Kp" => node.kp = self.parse_optional_float(),
6156 "ki" | "Ki" => node.ki = self.parse_optional_float(),
6157 "kd" | "Kd" => node.kd = self.parse_optional_float(),
6158 "tolerance" => node.tolerance = self.parse_optional_float(),
6159 "max_steps" => node.max_steps = self.parse_optional_int(),
6160 "on_violation" => {
6161 node.on_violation = self.consume_any_ident_or_kw()?.value.clone()
6162 }
6163 _ => self.skip_value(),
6164 }
6165 } else if self.check(TokenType::LBrace) {
6166 self.skip_braced_block()?;
6167 }
6168 }
6169 self.consume(TokenType::RBrace)?;
6170 Ok(node)
6171 }
6172
6173 fn parse_compute(&mut self) -> Result<ComputeDefinition, ParseError> {
6174 let tok = self.consume(TokenType::Compute)?;
6175 let name = self.consume(TokenType::Identifier)?.value;
6176 let mut node = ComputeDefinition {
6177 name,
6178 shield_ref: String::new(),
6179 loc: Loc {
6180 line: tok.line,
6181 column: tok.column,
6182 },
6183 leading_trivia: Vec::new(),
6184 trailing_trivia: Vec::new(),
6185 };
6186 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
6188 self.advance();
6189 }
6190 self.consume(TokenType::LBrace)?;
6191 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6192 let field_name = self.current().value.clone();
6193 self.advance();
6194 if self.check(TokenType::Colon) {
6195 self.advance();
6196 match field_name.as_str() {
6197 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6198 _ => self.skip_value(),
6199 }
6200 } else if self.check(TokenType::LBrace) {
6201 self.skip_braced_block()?;
6202 }
6203 }
6204 self.consume(TokenType::RBrace)?;
6205 Ok(node)
6206 }
6207
6208 fn parse_daemon(&mut self) -> Result<DaemonDefinition, ParseError> {
6209 let tok = self.consume(TokenType::Daemon)?;
6210 let name = self.consume(TokenType::Identifier)?.value;
6211 let mut node = DaemonDefinition {
6212 name,
6213 goal: String::new(),
6214 tools: Vec::new(),
6215 memory_ref: String::new(),
6216 strategy: String::new(),
6217 on_stuck: String::new(),
6218 shield_ref: String::new(),
6219 window_ref: String::new(),
6220 budget: None,
6221 max_tokens: None,
6222 max_time: String::new(),
6223 max_cost: None,
6224 listeners: Vec::new(),
6225 requires_capabilities: Vec::new(),
6226 loc: Loc {
6227 line: tok.line,
6228 column: tok.column,
6229 },
6230 leading_trivia: Vec::new(),
6231 trailing_trivia: Vec::new(),
6232 };
6233 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
6235 self.advance();
6236 }
6237 self.consume(TokenType::LBrace)?;
6238 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6239 let field = self.current().clone();
6240 let field_name = field.value.clone();
6241 self.advance();
6242 if self.check(TokenType::Colon) {
6243 self.advance();
6244 match field_name.as_str() {
6245 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
6246 "tools" => node.tools = self.parse_bracketed_identifiers()?,
6247 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
6248 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
6249 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
6250 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6251 "window" => node.window_ref = self.consume_any_ident_or_kw()?.value.clone(),
6253 "max_tokens" => node.max_tokens = self.parse_optional_int(),
6254 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
6255 "max_cost" => node.max_cost = self.parse_optional_float(),
6256 "requires" => {
6261 let bracket_tok = self.current().clone();
6262 let items = self.parse_bracketed_dot_identifiers()?;
6263 for slug in &items {
6264 if !is_valid_capability_slug(slug) {
6265 return Err(ParseError {
6266 message: format!(
6267 "Invalid capability slug '{slug}' in daemon '{}' \
6268 `requires:`. Capability slugs must match \
6269 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
6270 lowercase identifiers. Examples: `daemon.run`, \
6271 `memory.write`, `flow.execute`.",
6272 node.name
6273 ),
6274 line: bracket_tok.line,
6275 column: bracket_tok.column,
6276 ..Default::default()
6277 });
6278 }
6279 }
6280 node.requires_capabilities = items;
6281 }
6282 _ => self.skip_value(),
6283 }
6284 } else if field.ttype == TokenType::Listen {
6285 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
6291 (self.consume(TokenType::StringLit)?.value.clone(), false)
6292 } else {
6293 (self.consume_any_ident_or_kw()?.value.clone(), true)
6294 };
6295 let mut alias = String::new();
6296 if !self.at_declaration_start()
6297 && !self.check(TokenType::RBrace)
6298 && !self.check(TokenType::LBrace)
6299 {
6300 let next = self.current().clone();
6301 if next.value == "as" || next.ttype == TokenType::As {
6302 self.advance();
6303 alias = self.consume_any_ident_or_kw()?.value.clone();
6304 }
6305 }
6306 let listen_loc = Loc {
6307 line: field.line,
6308 column: field.column,
6309 };
6310 let body = self.parse_listener_body()?;
6314 node.listeners.push(ListenStep {
6315 channel,
6316 channel_is_ref,
6317 event_alias: alias,
6318 body,
6319 loc: listen_loc,
6320 });
6321 } else if field_name == "budget" && self.check(TokenType::LBrace) {
6322 node.budget = Some(self.parse_budget_block(field.line, field.column)?);
6324 } else if self.check(TokenType::LBrace) {
6325 self.skip_braced_block()?;
6326 }
6327 }
6328 self.consume(TokenType::RBrace)?;
6329 Ok(node)
6330 }
6331
6332 fn parse_budget_block(&mut self, line: u32, column: u32) -> Result<BudgetBlock, ParseError> {
6334 self.consume(TokenType::LBrace)?;
6335 let mut quotas = Vec::new();
6336 let mut on_exhausted = String::new();
6337 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6338 let field = self.current().clone();
6339 let field_name = self.consume_any_ident_or_kw()?.value;
6340 match field_name.as_str() {
6341 "rate" | "max" => {
6342 quotas.push(self.parse_budget_quota(field_name, field.line, field.column)?);
6343 }
6344 "on_exhausted" => {
6345 self.consume(TokenType::Colon)?;
6346 on_exhausted = self.consume_any_ident_or_kw()?.value;
6347 }
6348 _ => self.skip_value(),
6349 }
6350 }
6351 self.consume(TokenType::RBrace)?;
6352 Ok(BudgetBlock {
6353 quotas,
6354 on_exhausted,
6355 loc: Loc { line, column },
6356 })
6357 }
6358
6359 fn parse_budget_quota(
6362 &mut self,
6363 kind: String,
6364 line: u32,
6365 column: u32,
6366 ) -> Result<BudgetQuota, ParseError> {
6367 self.consume(TokenType::Colon)?;
6368 let limit = self.consume_number()? as i64;
6369 let _per = self.consume_any_ident_or_kw()?; let period = self.consume_any_ident_or_kw()?.value;
6372 let _on = self.consume_any_ident_or_kw()?; let _tool = self.consume_any_ident_or_kw()?; self.consume(TokenType::LParen)?;
6376 let effect = self.consume_any_ident_or_kw()?.value;
6377 self.consume(TokenType::RParen)?;
6378 Ok(BudgetQuota {
6379 kind,
6380 limit,
6381 period,
6382 effect,
6383 loc: Loc { line, column },
6384 })
6385 }
6386
6387 fn parse_axonstore(&mut self) -> Result<AxonStoreDefinition, ParseError> {
6388 let tok = self.consume(TokenType::AxonStore)?;
6389 let name = self.consume(TokenType::Identifier)?.value;
6390 let mut node = AxonStoreDefinition {
6391 name,
6392 backend: String::new(),
6393 connection: String::new(),
6394 confidence_floor: None,
6395 isolation: String::new(),
6396 on_breach: String::new(),
6397 capability: String::new(),
6398 class: String::new(),
6399 column_schema: None,
6400 loc: Loc {
6401 line: tok.line,
6402 column: tok.column,
6403 },
6404 leading_trivia: Vec::new(),
6405 trailing_trivia: Vec::new(),
6406 };
6407 self.consume(TokenType::LBrace)?;
6408 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6409 let field = self.current().clone();
6410 let field_name = field.value.clone();
6411 if field.ttype == TokenType::Schema {
6417 self.advance();
6418 let parsed = self.parse_store_schema_declaration(&node.name, field.line, field.column)?;
6419 node.column_schema = Some(parsed);
6420 continue;
6421 }
6422 self.advance();
6423 if self.check(TokenType::Colon) {
6424 self.advance();
6425 match field_name.as_str() {
6426 "backend" => node.backend = self.consume_any_ident_or_kw()?.value.clone(),
6427 "class" => node.class = self.parse_dotted_identifier()?,
6435 "connection" => {
6436 node.connection = self.consume(TokenType::StringLit)?.value.clone()
6437 }
6438 "confidence_floor" => node.confidence_floor = self.parse_optional_float(),
6439 "isolation" => node.isolation = self.consume_any_ident_or_kw()?.value.clone(),
6440 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
6441 "capability" => {
6445 let slug_tok = self.consume(TokenType::StringLit)?.clone();
6446 if !is_valid_capability_slug(&slug_tok.value) {
6447 return Err(ParseError {
6448 message: format!(
6449 "Invalid capability slug '{}' in axonstore '{}' \
6450 `capability:`. Capability slugs must match \
6451 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
6452 lowercase identifiers starting with a letter. Examples: \
6453 `admin`, `tenant.read`, `hipaa.phi.read`.",
6454 slug_tok.value, node.name
6455 ),
6456 line: slug_tok.line,
6457 column: slug_tok.column,
6458 ..Default::default()
6459 });
6460 }
6461 node.capability = slug_tok.value.clone();
6462 }
6463 _ => self.skip_value(),
6464 }
6465 } else if self.check(TokenType::LBrace) {
6466 self.skip_braced_block()?;
6467 }
6468 }
6469 self.consume(TokenType::RBrace)?;
6470 Ok(node)
6471 }
6472
6473 fn parse_store_schema_declaration(
6485 &mut self,
6486 store_name: &str,
6487 sch_line: u32,
6488 sch_col: u32,
6489 ) -> Result<crate::store_schema::StoreColumnSchema, ParseError> {
6490 use crate::store_schema::{StoreColumn, StoreColumnSchema, StoreColumnType};
6491
6492 if self.check(TokenType::LBrace) {
6494 self.consume(TokenType::LBrace)?;
6495 let mut columns: Vec<StoreColumn> = Vec::new();
6496 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6497 let col_tok = self.current().clone();
6498 let col_name = self.consume_any_ident_or_kw()?.value.clone();
6499 self.consume(TokenType::Colon)?;
6500 let type_tok = self.consume_any_ident_or_kw()?.clone();
6501 let col_type = StoreColumnType::from_token(&type_tok.value).ok_or_else(|| {
6502 let names = StoreColumnType::all_canonical_names();
6503 let suggestion =
6504 crate::smart_suggest::suggest_for(&type_tok.value, &names);
6505 let suggest_suffix = if suggestion.is_empty() {
6506 String::new()
6507 } else {
6508 format!(" {suggestion}")
6509 };
6510 let known = names.join(", ");
6511 ParseError {
6512 message: format!(
6513 "Unknown column type `{}` for column `{}` in \
6514 axonstore `{}` `schema:` block. The closed \
6515 v1.38.0 column-type catalog (Fase 38.b D1) \
6516 is {{{known}}} (plus common lowercase \
6517 aliases — `int`/`integer`/`int4` for \
6518 `Int`, `bool`/`boolean` for `Bool`, etc.).\
6519 {suggest_suffix}",
6520 type_tok.value, col_name, store_name
6521 ),
6522 line: type_tok.line,
6523 column: type_tok.column,
6524 ..Default::default()
6525 }
6526 })?;
6527
6528 let mut json_shape: Option<String> = None;
6538 if self.check(TokenType::Lt) {
6539 self.advance();
6540 let shape_tok = self.consume_any_ident_or_kw()?.clone();
6541 self.consume(TokenType::Gt)?;
6542 if matches!(col_type, StoreColumnType::Json | StoreColumnType::Jsonb) {
6543 json_shape = Some(shape_tok.value.clone());
6544 } else {
6545 return Err(ParseError {
6546 message: format!(
6547 "axon-T841 a shape lens `<{shape}>` may refine \
6548 only a `Json` / `Jsonb` column, but column \
6549 `{col}` in axonstore `{store}` is `{ty}`. Drop \
6550 the `<{shape}>` (a rigid column already has a \
6551 fixed shape), or change the column type to \
6552 `Json<{shape}>` if it carries open documents.",
6553 shape = shape_tok.value,
6554 col = col_name,
6555 store = store_name,
6556 ty = col_type.canonical_name(),
6557 ),
6558 line: shape_tok.line,
6559 column: shape_tok.column,
6560 ..Default::default()
6561 });
6562 }
6563 }
6564
6565 let mut col = StoreColumn {
6566 name: col_name,
6567 col_type,
6568 json_shape,
6569 primary_key: false,
6570 auto_increment: false,
6571 not_null: false,
6572 unique: false,
6573 indexed: false,
6574 default_value: String::new(),
6575 identity: false,
6580 line: col_tok.line,
6581 column: col_tok.column,
6582 };
6583
6584 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6587 if self.current().ttype != TokenType::Identifier {
6588 break;
6591 }
6592 let constraint = self.current().value.clone();
6593 match constraint.as_str() {
6594 "primary_key" => {
6595 col.primary_key = true;
6596 self.advance();
6597 }
6598 "auto_increment" => {
6599 col.auto_increment = true;
6600 self.advance();
6601 }
6602 "not_null" => {
6603 col.not_null = true;
6604 self.advance();
6605 }
6606 "unique" => {
6607 col.unique = true;
6608 self.advance();
6609 }
6610 "index" => {
6616 col.indexed = true;
6617 self.advance();
6618 }
6619 "identity" => {
6630 col.identity = true;
6631 self.advance();
6632 }
6633 "default" => {
6634 self.advance();
6635 let dv = self.current().clone();
6636 if matches!(
6637 dv.ttype,
6638 TokenType::StringLit
6639 | TokenType::Integer
6640 | TokenType::Float
6641 ) {
6642 col.default_value = dv.value.clone();
6643 self.advance();
6644 } else {
6645 col.default_value =
6646 self.consume_any_ident_or_kw()?.value.clone();
6647 }
6648 }
6649 _ => break,
6650 }
6651 }
6652
6653 columns.push(col);
6654 }
6655 self.consume(TokenType::RBrace)?;
6656 return Ok(StoreColumnSchema::Inline {
6657 columns,
6658 leading_trivia: Vec::new(),
6659 line: sch_line,
6660 column: sch_col,
6661 });
6662 }
6663
6664 if !self.check(TokenType::Colon) {
6666 let cur = self.current().clone();
6667 return Err(ParseError {
6668 message: format!(
6669 "axonstore `{store_name}` `schema:` declaration expects \
6670 `{{ … }}` (inline columns), `: \"manifest.ref\"` \
6671 (manifest reference), or `: env:VAR` (per-tenant schema \
6672 namespace). Got `{}` instead.",
6673 cur.value
6674 ),
6675 line: cur.line,
6676 column: cur.column,
6677 ..Default::default()
6678 });
6679 }
6680 self.consume(TokenType::Colon)?;
6681
6682 if self.check(TokenType::StringLit) {
6684 let lit = self.consume(TokenType::StringLit)?.clone();
6685 let value = lit.value.clone();
6686 if let Some(var) = value.strip_prefix("env:") {
6687 let var = var.trim();
6688 if var.is_empty() {
6689 return Err(ParseError {
6690 message: format!(
6691 "axonstore `{store_name}` `schema: \"env:\"` is \
6692 missing the variable name after the `env:` \
6693 prefix."
6694 ),
6695 line: lit.line,
6696 column: lit.column,
6697 ..Default::default()
6698 });
6699 }
6700 return Ok(StoreColumnSchema::EnvVar {
6701 var_name: var.to_string(),
6702 line: sch_line,
6703 column: sch_col,
6704 });
6705 }
6706 if value.trim().is_empty() {
6708 return Err(ParseError {
6709 message: format!(
6710 "axonstore `{store_name}` `schema:` manifest reference \
6711 is empty. Expected `\"qualified.name\"` — e.g. \
6712 `\"public.tenants\"`."
6713 ),
6714 line: lit.line,
6715 column: lit.column,
6716 ..Default::default()
6717 });
6718 }
6719 return Ok(StoreColumnSchema::ManifestRef {
6720 qualified_name: value,
6721 line: sch_line,
6722 column: sch_col,
6723 });
6724 }
6725
6726 let env_tok = self.current().clone();
6729 if env_tok.value == "env" {
6730 self.advance();
6731 if !self.check(TokenType::Colon) {
6732 return Err(ParseError {
6733 message: format!(
6734 "axonstore `{store_name}` `schema: env` is missing the \
6735 `:` separator. Expected `schema: env:VAR`."
6736 ),
6737 line: env_tok.line,
6738 column: env_tok.column,
6739 ..Default::default()
6740 });
6741 }
6742 self.advance(); let var_tok = self.consume_any_ident_or_kw()?.clone();
6744 if var_tok.value.trim().is_empty() {
6745 return Err(ParseError {
6746 message: format!(
6747 "axonstore `{store_name}` `schema: env:` is missing \
6748 the variable name."
6749 ),
6750 line: var_tok.line,
6751 column: var_tok.column,
6752 ..Default::default()
6753 });
6754 }
6755 return Ok(StoreColumnSchema::EnvVar {
6756 var_name: var_tok.value.clone(),
6757 line: sch_line,
6758 column: sch_col,
6759 });
6760 }
6761
6762 Err(ParseError {
6763 message: format!(
6764 "axonstore `{store_name}` `schema:` declaration expects \
6765 `{{ … }}` (inline columns), `\"manifest.ref\"` (manifest \
6766 reference), or `env:VAR` (per-tenant schema namespace). \
6767 Got `{}` instead.",
6768 env_tok.value
6769 ),
6770 line: env_tok.line,
6771 column: env_tok.column,
6772 ..Default::default()
6773 })
6774 }
6775
6776 fn parse_resource(&mut self) -> Result<ResourceDefinition, ParseError> {
6783 let tok = self.consume(TokenType::Resource)?;
6784 let name = self.consume(TokenType::Identifier)?.value;
6785 let mut node = ResourceDefinition {
6786 name,
6787 kind: String::new(),
6788 endpoint: String::new(),
6789 capacity: None,
6790 lifetime: "affine".to_string(),
6791 certainty_floor: None,
6792 shield_ref: String::new(),
6793 loc: Loc {
6794 line: tok.line,
6795 column: tok.column,
6796 },
6797 leading_trivia: Vec::new(),
6798 trailing_trivia: Vec::new(),
6799 };
6800 self.consume(TokenType::LBrace)?;
6801 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6802 let field_tok = self.current().clone();
6803 let field_name = field_tok.value.clone();
6804 self.advance();
6805 if !self.check(TokenType::Colon) {
6806 if self.check(TokenType::LBrace) {
6808 self.skip_braced_block()?;
6809 }
6810 continue;
6811 }
6812 self.advance(); match field_name.as_str() {
6814 "kind" => node.kind = self.consume_any_ident_or_kw()?.value,
6815 "endpoint" => node.endpoint = self.consume(TokenType::StringLit)?.value,
6816 "capacity" => {
6817 node.capacity = self.parse_optional_int();
6818 }
6819 "lifetime" => {
6820 let lt_tok = self.consume_any_ident_or_kw()?;
6821 let lt = lt_tok.value;
6822 if !matches!(lt.as_str(), "linear" | "affine" | "persistent") {
6823 return Err(ParseError {
6824 message: format!(
6825 "Invalid lifetime '{lt}' in resource '{}' — \
6826 expected linear | affine | persistent",
6827 node.name
6828 ),
6829 line: lt_tok.line,
6830 column: lt_tok.column,
6831 ..Default::default()
6832 });
6833 }
6834 node.lifetime = lt;
6835 }
6836 "certainty_floor" => {
6837 node.certainty_floor = self.parse_optional_float();
6838 }
6839 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
6840 _ => self.skip_value(),
6841 }
6842 }
6843 self.consume(TokenType::RBrace)?;
6844 Ok(node)
6845 }
6846
6847 fn parse_fabric(&mut self) -> Result<FabricDefinition, ParseError> {
6849 let tok = self.consume(TokenType::Fabric)?;
6850 let name = self.consume(TokenType::Identifier)?.value;
6851 let mut node = FabricDefinition {
6852 name,
6853 provider: String::new(),
6854 region: String::new(),
6855 zones: None,
6856 ephemeral: None,
6857 shield_ref: String::new(),
6858 loc: Loc {
6859 line: tok.line,
6860 column: tok.column,
6861 },
6862 leading_trivia: Vec::new(),
6863 trailing_trivia: Vec::new(),
6864 };
6865 self.consume(TokenType::LBrace)?;
6866 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6867 let field_name = self.current().value.clone();
6868 self.advance();
6869 if !self.check(TokenType::Colon) {
6870 if self.check(TokenType::LBrace) {
6871 self.skip_braced_block()?;
6872 }
6873 continue;
6874 }
6875 self.advance(); match field_name.as_str() {
6877 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
6878 "region" => node.region = self.consume(TokenType::StringLit)?.value,
6879 "zones" => node.zones = self.parse_optional_int(),
6880 "ephemeral" => {
6881 let b = self.parse_bool()?;
6882 node.ephemeral = Some(b);
6883 }
6884 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
6885 _ => self.skip_value(),
6886 }
6887 }
6888 self.consume(TokenType::RBrace)?;
6889 Ok(node)
6890 }
6891
6892 fn parse_manifest(&mut self) -> Result<ManifestDefinition, ParseError> {
6894 let tok = self.consume(TokenType::Manifest)?;
6895 let name = self.consume(TokenType::Identifier)?.value;
6896 let mut node = ManifestDefinition {
6897 name,
6898 resources: Vec::new(),
6899 fabric_ref: String::new(),
6900 region: String::new(),
6901 zones: None,
6902 compliance: Vec::new(),
6903 loc: Loc {
6904 line: tok.line,
6905 column: tok.column,
6906 },
6907 leading_trivia: Vec::new(),
6908 trailing_trivia: Vec::new(),
6909 };
6910 self.consume(TokenType::LBrace)?;
6911 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6912 let field_name = self.current().value.clone();
6913 self.advance();
6914 if !self.check(TokenType::Colon) {
6915 if self.check(TokenType::LBrace) {
6916 self.skip_braced_block()?;
6917 }
6918 continue;
6919 }
6920 self.advance();
6921 match field_name.as_str() {
6922 "resources" => node.resources = self.parse_bracketed_identifiers()?,
6923 "fabric" => node.fabric_ref = self.consume_any_ident_or_kw()?.value,
6924 "region" => node.region = self.consume(TokenType::StringLit)?.value,
6925 "zones" => node.zones = self.parse_optional_int(),
6926 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
6927 _ => self.skip_value(),
6928 }
6929 }
6930 self.consume(TokenType::RBrace)?;
6931 Ok(node)
6932 }
6933
6934 fn parse_observe(&mut self) -> Result<ObserveDefinition, ParseError> {
6936 let tok = self.consume(TokenType::Observe)?;
6937 let name = self.consume(TokenType::Identifier)?.value;
6938 self.consume(TokenType::From)?;
6940 let target = self.consume(TokenType::Identifier)?.value;
6941 let mut node = ObserveDefinition {
6942 name,
6943 target,
6944 sources: Vec::new(),
6945 quorum: None,
6946 timeout: String::new(),
6947 on_partition: "fail".to_string(),
6948 certainty_floor: None,
6949 loc: Loc {
6950 line: tok.line,
6951 column: tok.column,
6952 },
6953 leading_trivia: Vec::new(),
6954 trailing_trivia: Vec::new(),
6955 };
6956 self.consume(TokenType::LBrace)?;
6957 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6958 let field_name = self.current().value.clone();
6959 self.advance();
6960 if !self.check(TokenType::Colon) {
6961 if self.check(TokenType::LBrace) {
6962 self.skip_braced_block()?;
6963 }
6964 continue;
6965 }
6966 self.advance();
6967 match field_name.as_str() {
6968 "sources" => node.sources = self.parse_bracketed_identifiers()?,
6969 "quorum" => node.quorum = self.parse_optional_int(),
6970 "timeout" => {
6971 let t = self.current().clone();
6972 match t.ttype {
6973 TokenType::Duration | TokenType::StringLit => {
6974 self.advance();
6975 node.timeout = t.value;
6976 }
6977 _ => node.timeout = self.consume_any_ident_or_kw()?.value,
6978 }
6979 }
6980 "on_partition" => {
6981 let p_tok = self.consume_any_ident_or_kw()?;
6982 let p = p_tok.value;
6983 if !matches!(p.as_str(), "fail" | "shield_quarantine") {
6984 return Err(ParseError {
6985 message: format!(
6986 "Invalid on_partition '{p}' in observe '{}' — \
6987 expected fail | shield_quarantine",
6988 node.name
6989 ),
6990 line: p_tok.line,
6991 column: p_tok.column,
6992 ..Default::default()
6993 });
6994 }
6995 node.on_partition = p;
6996 }
6997 "certainty_floor" => node.certainty_floor = self.parse_optional_float(),
6998 _ => self.skip_value(),
6999 }
7000 }
7001 self.consume(TokenType::RBrace)?;
7002 Ok(node)
7003 }
7004
7005 fn parse_reconcile(&mut self) -> Result<ReconcileDefinition, ParseError> {
7009 let tok = self.consume(TokenType::Reconcile)?;
7010 let name = self.consume(TokenType::Identifier)?.value;
7011 let mut node = ReconcileDefinition {
7012 name,
7013 observe_ref: String::new(),
7014 threshold: None,
7015 tolerance: None,
7016 on_drift: "provision".to_string(),
7017 shield_ref: String::new(),
7018 mandate_ref: String::new(),
7019 max_retries: 3,
7020 loc: Loc {
7021 line: tok.line,
7022 column: tok.column,
7023 },
7024 leading_trivia: Vec::new(),
7025 trailing_trivia: Vec::new(),
7026 };
7027 self.consume(TokenType::LBrace)?;
7028 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7029 let field_name = self.current().value.clone();
7030 self.advance();
7031 if !self.check(TokenType::Colon) {
7032 if self.check(TokenType::LBrace) {
7033 self.skip_braced_block()?;
7034 }
7035 continue;
7036 }
7037 self.advance();
7038 match field_name.as_str() {
7039 "observe" => node.observe_ref = self.consume_any_ident_or_kw()?.value,
7040 "threshold" => node.threshold = self.parse_optional_float(),
7041 "tolerance" => node.tolerance = self.parse_optional_float(),
7042 "on_drift" => {
7043 let d_tok = self.consume_any_ident_or_kw()?;
7044 let d = d_tok.value;
7045 if !matches!(d.as_str(), "provision" | "alert" | "refine") {
7046 return Err(ParseError {
7047 message: format!(
7048 "Invalid on_drift '{d}' in reconcile '{}' — \
7049 expected provision | alert | refine",
7050 node.name
7051 ),
7052 line: d_tok.line,
7053 column: d_tok.column,
7054 ..Default::default()
7055 });
7056 }
7057 node.on_drift = d;
7058 }
7059 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7060 "mandate" => node.mandate_ref = self.consume_any_ident_or_kw()?.value,
7061 "max_retries" => {
7062 if let Some(v) = self.parse_optional_int() {
7063 node.max_retries = v;
7064 }
7065 }
7066 _ => self.skip_value(),
7067 }
7068 }
7069 self.consume(TokenType::RBrace)?;
7070 Ok(node)
7071 }
7072
7073 fn parse_lease(&mut self) -> Result<LeaseDefinition, ParseError> {
7075 let tok = self.consume(TokenType::Lease)?;
7076 let name = self.consume(TokenType::Identifier)?.value;
7077 let mut node = LeaseDefinition {
7078 name,
7079 resource_ref: String::new(),
7080 duration: String::new(),
7081 acquire: "on_start".to_string(),
7082 on_expire: "anchor_breach".to_string(),
7083 loc: Loc {
7084 line: tok.line,
7085 column: tok.column,
7086 },
7087 leading_trivia: Vec::new(),
7088 trailing_trivia: Vec::new(),
7089 };
7090 self.consume(TokenType::LBrace)?;
7091 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7092 let field_name = self.current().value.clone();
7093 self.advance();
7094 if !self.check(TokenType::Colon) {
7095 if self.check(TokenType::LBrace) {
7096 self.skip_braced_block()?;
7097 }
7098 continue;
7099 }
7100 self.advance();
7101 match field_name.as_str() {
7102 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
7103 "duration" => {
7104 let t = self.current().clone();
7105 match t.ttype {
7106 TokenType::Duration | TokenType::StringLit => {
7107 self.advance();
7108 node.duration = t.value;
7109 }
7110 _ => node.duration = self.consume_any_ident_or_kw()?.value,
7111 }
7112 }
7113 "acquire" => {
7114 let a_tok = self.consume_any_ident_or_kw()?;
7115 let a = a_tok.value;
7116 if !matches!(a.as_str(), "on_start" | "on_demand") {
7117 return Err(ParseError {
7118 message: format!(
7119 "Invalid acquire '{a}' in lease '{}' — \
7120 expected on_start | on_demand",
7121 node.name
7122 ),
7123 line: a_tok.line,
7124 column: a_tok.column,
7125 ..Default::default()
7126 });
7127 }
7128 node.acquire = a;
7129 }
7130 "on_expire" => {
7131 let e_tok = self.consume_any_ident_or_kw()?;
7132 let e = e_tok.value;
7133 if !matches!(e.as_str(), "anchor_breach" | "release" | "extend") {
7134 return Err(ParseError {
7135 message: format!(
7136 "Invalid on_expire '{e}' in lease '{}' — \
7137 expected anchor_breach | release | extend",
7138 node.name
7139 ),
7140 line: e_tok.line,
7141 column: e_tok.column,
7142 ..Default::default()
7143 });
7144 }
7145 node.on_expire = e;
7146 }
7147 _ => self.skip_value(),
7148 }
7149 }
7150 self.consume(TokenType::RBrace)?;
7151 Ok(node)
7152 }
7153
7154 fn parse_ensemble(&mut self) -> Result<EnsembleDefinition, ParseError> {
7156 let tok = self.consume(TokenType::Ensemble)?;
7157 let name = self.consume(TokenType::Identifier)?.value;
7158 let mut node = EnsembleDefinition {
7159 name,
7160 observations: Vec::new(),
7161 quorum: None,
7162 aggregation: "majority".to_string(),
7163 certainty_mode: "min".to_string(),
7164 loc: Loc {
7165 line: tok.line,
7166 column: tok.column,
7167 },
7168 leading_trivia: Vec::new(),
7169 trailing_trivia: Vec::new(),
7170 };
7171 self.consume(TokenType::LBrace)?;
7172 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7173 let field_name = self.current().value.clone();
7174 self.advance();
7175 if !self.check(TokenType::Colon) {
7176 if self.check(TokenType::LBrace) {
7177 self.skip_braced_block()?;
7178 }
7179 continue;
7180 }
7181 self.advance();
7182 match field_name.as_str() {
7183 "observations" => node.observations = self.parse_bracketed_identifiers()?,
7184 "quorum" => node.quorum = self.parse_optional_int(),
7185 "aggregation" => {
7186 let a_tok = self.consume_any_ident_or_kw()?;
7187 let a = a_tok.value;
7188 if !matches!(a.as_str(), "majority" | "weighted" | "byzantine") {
7189 return Err(ParseError {
7190 message: format!(
7191 "Invalid aggregation '{a}' in ensemble '{}' — \
7192 expected majority | weighted | byzantine",
7193 node.name
7194 ),
7195 line: a_tok.line,
7196 column: a_tok.column,
7197 ..Default::default()
7198 });
7199 }
7200 node.aggregation = a;
7201 }
7202 "certainty_mode" => {
7203 let c_tok = self.consume_any_ident_or_kw()?;
7204 let c = c_tok.value;
7205 if !matches!(c.as_str(), "min" | "weighted" | "harmonic") {
7206 return Err(ParseError {
7207 message: format!(
7208 "Invalid certainty_mode '{c}' in ensemble '{}' — \
7209 expected min | weighted | harmonic",
7210 node.name
7211 ),
7212 line: c_tok.line,
7213 column: c_tok.column,
7214 ..Default::default()
7215 });
7216 }
7217 node.certainty_mode = c;
7218 }
7219 _ => self.skip_value(),
7220 }
7221 }
7222 self.consume(TokenType::RBrace)?;
7223 Ok(node)
7224 }
7225
7226 fn parse_session_definition(&mut self) -> Result<SessionDefinition, ParseError> {
7234 let tok = self.consume(TokenType::Session)?;
7235 let name = self.consume(TokenType::Identifier)?.value;
7236 let mut node = SessionDefinition {
7237 name,
7238 roles: Vec::new(),
7239 loc: Loc {
7240 line: tok.line,
7241 column: tok.column,
7242 },
7243 leading_trivia: Vec::new(),
7244 trailing_trivia: Vec::new(),
7245 };
7246 self.consume(TokenType::LBrace)?;
7247 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7248 let role_tok = self.consume_any_ident_or_kw()?;
7249 self.consume(TokenType::Colon)?;
7250 let steps = self.parse_session_steps()?;
7251 node.roles.push(SessionRole {
7252 name: role_tok.value,
7253 steps,
7254 loc: Loc {
7255 line: role_tok.line,
7256 column: role_tok.column,
7257 },
7258 });
7259 }
7260 self.consume(TokenType::RBrace)?;
7261 Ok(node)
7262 }
7263
7264 fn parse_observable(&mut self) -> Result<ObservableDefinition, ParseError> {
7277 let tok = self.consume(TokenType::Observable)?;
7278 let name = self.consume(TokenType::Identifier)?.value;
7279 let mut node = ObservableDefinition {
7280 name,
7281 qubits: None,
7282 terms: Vec::new(),
7283 loc: Loc {
7284 line: tok.line,
7285 column: tok.column,
7286 },
7287 leading_trivia: Vec::new(),
7288 trailing_trivia: Vec::new(),
7289 };
7290 self.consume(TokenType::LBrace)?;
7291 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7292 let key_tok = self.consume_any_ident_or_kw()?;
7293 self.consume(TokenType::Colon)?;
7294 match key_tok.value.as_str() {
7295 "qubits" => node.qubits = Some(self.consume_number()? as i64),
7296 "term" => {
7297 let term_loc = Loc {
7298 line: key_tok.line,
7299 column: key_tok.column,
7300 };
7301 let mut negative = false;
7303 if self.check(TokenType::Minus) {
7304 self.advance();
7305 negative = true;
7306 } else if self.check(TokenType::Plus) {
7307 self.advance();
7308 }
7309 let mag = self.consume_number()?;
7310 let coefficient = if negative { -mag } else { mag };
7311 self.consume(TokenType::Star)?;
7313 let pauli = self.consume(TokenType::StringLit)?.value;
7314 node.terms.push(PauliTerm {
7315 coefficient,
7316 pauli,
7317 loc: term_loc,
7318 });
7319 }
7320 _ => self.skip_value(),
7321 }
7322 }
7323 self.consume(TokenType::RBrace)?;
7324 Ok(node)
7325 }
7326
7327 fn parse_witness(&mut self) -> Result<WitnessDefinition, ParseError> {
7335 let tok = self.consume(TokenType::Witness)?;
7336 let name = self.consume(TokenType::Identifier)?.value;
7337 let mut node = WitnessDefinition {
7338 name,
7339 claim: String::new(),
7340 baseline: String::new(),
7341 metric: String::new(),
7342 threshold: 0.0,
7343 data: String::new(),
7344 loc: Loc {
7345 line: tok.line,
7346 column: tok.column,
7347 },
7348 leading_trivia: Vec::new(),
7349 trailing_trivia: Vec::new(),
7350 };
7351 self.consume(TokenType::LBrace)?;
7352 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7353 let key_tok = self.consume_any_ident_or_kw()?;
7354 self.consume(TokenType::Colon)?;
7355 match key_tok.value.as_str() {
7356 "claim" => node.claim = self.consume_any_ident_or_kw()?.value,
7357 "against" => node.baseline = self.consume_any_ident_or_kw()?.value,
7360 "metric" => node.metric = self.consume_any_ident_or_kw()?.value,
7361 "threshold" => node.threshold = self.consume_number()?,
7362 "data" => node.data = self.consume_any_ident_or_kw()?.value,
7363 _ => self.skip_value(),
7364 }
7365 }
7366 self.consume(TokenType::RBrace)?;
7367 Ok(node)
7368 }
7369
7370 fn parse_socket(&mut self) -> Result<SocketDefinition, ParseError> {
7375 let tok = self.consume(TokenType::Socket)?;
7376 let name = self.consume(TokenType::Identifier)?.value;
7377 let mut node = SocketDefinition {
7378 name,
7379 loc: Loc { line: tok.line, column: tok.column },
7380 ..Default::default()
7381 };
7382 self.consume(TokenType::LBrace)?;
7383 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7384 let key = self.consume_any_ident_or_kw()?.value;
7385 self.consume(TokenType::Colon)?;
7386 match key.as_str() {
7387 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
7388 "backpressure" => {
7389 let kind = self.consume_any_ident_or_kw()?.value;
7391 if kind != "credit" {
7392 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
7393 }
7394 self.consume(TokenType::LParen)?;
7395 let n = self
7396 .consume(TokenType::Integer)?
7397 .value
7398 .parse::<i64>()
7399 .map_err(|_| self.error("backpressure credit must be an integer"))?;
7400 self.consume(TokenType::RParen)?;
7401 node.backpressure_credit = Some(n);
7402 }
7403 "reconnect" => {
7404 let mode = self.consume_any_ident_or_kw()?.value;
7405 node.reconnect = mode == "cognitive_state";
7406 }
7407 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
7408 other => return Err(self.error(&format!("unknown socket field `{other}`"))),
7409 }
7410 if self.check(TokenType::Comma) {
7412 self.consume(TokenType::Comma)?;
7413 }
7414 }
7415 self.consume(TokenType::RBrace)?;
7416 Ok(node)
7417 }
7418
7419 fn parse_upstream(&mut self) -> Result<UpstreamDefinition, ParseError> {
7427 let tok = self.consume(TokenType::Upstream)?;
7428 let name = self.consume(TokenType::Identifier)?.value;
7429 let mut node = UpstreamDefinition {
7430 name,
7431 loc: Loc { line: tok.line, column: tok.column },
7432 ..Default::default()
7433 };
7434 if self.check(TokenType::From) {
7436 self.advance();
7437 let base = self.consume(TokenType::Identifier)?.value;
7438 self.consume(TokenType::At)?;
7439 let version = self.consume_any_ident_or_kw()?.value;
7440 node.preset = Some(format!("{base}@{version}"));
7441 }
7442 self.consume(TokenType::LBrace)?;
7443 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7444 let key = self.consume_any_ident_or_kw()?.value;
7445 self.consume(TokenType::Colon)?;
7446 match key.as_str() {
7447 "transport" => node.transport = self.consume_any_ident_or_kw()?.value,
7448 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
7449 "role" => node.role = self.consume_any_ident_or_kw()?.value,
7450 "resolve" => node.resolve = self.parse_dotted_identifier()?,
7451 "secret" => node.secret = self.parse_dotted_identifier()?,
7452 "auth" => {
7453 node.auth_kind = self.consume_any_ident_or_kw()?.value;
7456 if self.check(TokenType::LParen) {
7457 self.consume(TokenType::LParen)?;
7458 node.auth_name = Some(self.consume(TokenType::StringLit)?.value);
7459 if self.check(TokenType::Comma) {
7460 self.consume(TokenType::Comma)?;
7461 node.auth_prefix = Some(self.consume(TokenType::StringLit)?.value);
7462 }
7463 self.consume(TokenType::RParen)?;
7464 }
7465 }
7466 "map" => node.map = self.parse_upstream_map()?,
7467 "reconnect" => node.reconnect = Some(self.parse_upstream_reconnect()?),
7468 "overflow" => node.overflow = Some(self.consume_any_ident_or_kw()?.value),
7469 "backpressure" => {
7470 let kind = self.consume_any_ident_or_kw()?.value;
7472 if kind != "credit" {
7473 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
7474 }
7475 self.consume(TokenType::LParen)?;
7476 let n = self
7477 .consume(TokenType::Integer)?
7478 .value
7479 .parse::<i64>()
7480 .map_err(|_| self.error("backpressure credit must be an integer"))?;
7481 self.consume(TokenType::RParen)?;
7482 node.backpressure_credit = Some(n);
7483 }
7484 other => return Err(self.error(&format!("unknown upstream field `{other}`"))),
7485 }
7486 if self.check(TokenType::Comma) {
7488 self.consume(TokenType::Comma)?;
7489 }
7490 }
7491 self.consume(TokenType::RBrace)?;
7492 Ok(node)
7493 }
7494
7495 fn parse_cors(&mut self) -> Result<CorsDefinition, ParseError> {
7504 let tok = self.consume(TokenType::Cors)?;
7505 let name = self.consume(TokenType::Identifier)?.value;
7506 let mut node = CorsDefinition {
7507 name,
7508 loc: Loc { line: tok.line, column: tok.column },
7509 ..Default::default()
7510 };
7511 self.consume(TokenType::LBrace)?;
7512 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7513 let key = self.consume_any_ident_or_kw()?.value;
7514 self.consume(TokenType::Colon)?;
7515 match key.as_str() {
7516 "allow_origins" => node.allow_origins = self.parse_bracketed_strings()?,
7517 "allow_methods" => node.allow_methods = self.parse_bracketed_identifiers()?,
7518 "allow_headers" => node.allow_headers = self.parse_bracketed_strings()?,
7519 "allow_credentials" => {
7520 node.allow_credentials = self.consume_any_ident_or_kw()?.value == "true"
7521 }
7522 "max_age" => node.max_age = Some(self.consume(TokenType::Duration)?.value),
7523 "expose_headers" => node.expose_headers = self.parse_bracketed_strings()?,
7524 other => return Err(self.error(&format!("unknown cors field `{other}`"))),
7525 }
7526 if self.check(TokenType::Comma) {
7528 self.consume(TokenType::Comma)?;
7529 }
7530 }
7531 self.consume(TokenType::RBrace)?;
7532 Ok(node)
7533 }
7534
7535 fn parse_credential(&mut self) -> Result<CredentialDefinition, ParseError> {
7543 let tok = self.consume(TokenType::Credential)?;
7544 let name = self.consume(TokenType::Identifier)?.value;
7545 let mut node = CredentialDefinition {
7546 name,
7547 loc: Loc { line: tok.line, column: tok.column },
7548 ..Default::default()
7549 };
7550 self.consume(TokenType::LBrace)?;
7551 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7552 let key = self.consume_any_ident_or_kw()?.value;
7553 self.consume(TokenType::Colon)?;
7554 match key.as_str() {
7555 "ttl" => node.ttl = self.consume(TokenType::Duration)?.value,
7556 "grants" => {
7557 let bracket_tok = self.current().clone();
7558 let items = self.parse_bracketed_dot_identifiers()?;
7559 for slug in &items {
7560 if !is_valid_capability_slug(slug) {
7561 return Err(ParseError {
7562 message: format!(
7563 "Invalid capability slug '{slug}' in credential '{}' \
7564 `grants:`. Capability slugs must match \
7565 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
7566 lowercase identifiers starting with a letter. Examples: \
7567 `chat.invoke`, `flow.execute`.",
7568 node.name
7569 ),
7570 line: bracket_tok.line,
7571 column: bracket_tok.column,
7572 ..Default::default()
7573 });
7574 }
7575 }
7576 node.grants = items;
7577 }
7578 other => return Err(self.error(&format!("unknown credential field `{other}`"))),
7579 }
7580 if self.check(TokenType::Comma) {
7582 self.consume(TokenType::Comma)?;
7583 }
7584 }
7585 self.consume(TokenType::RBrace)?;
7586 Ok(node)
7587 }
7588
7589 fn parse_cache(&mut self) -> Result<CacheDefinition, ParseError> {
7596 let tok = self.consume(TokenType::Cache)?;
7597 let name = self.consume(TokenType::Identifier)?.value;
7598 let mut node = CacheDefinition {
7599 name,
7600 loc: Loc { line: tok.line, column: tok.column },
7601 ..Default::default()
7602 };
7603 self.consume(TokenType::LBrace)?;
7604 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7605 let key = self.consume_any_ident_or_kw()?.value;
7606 self.consume(TokenType::Colon)?;
7607 match key.as_str() {
7608 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
7609 "ttl" => node.ttl = Some(self.consume(TokenType::Duration)?.value),
7610 "key" => node.key_params = self.parse_bracketed_identifiers()?,
7611 "default" => {
7612 node.default_policy = self.consume_any_ident_or_kw()?.value == "true"
7613 }
7614 "apply_to_effects" => {
7615 node.apply_to_effects = self.parse_bracketed_identifiers()?
7616 }
7617 "invalidate_on" => node.invalidate_on = self.parse_bracketed_identifiers()?,
7618 other => return Err(self.error(&format!("unknown cache field `{other}`"))),
7619 }
7620 if self.check(TokenType::Comma) {
7621 self.consume(TokenType::Comma)?;
7622 }
7623 }
7624 self.consume(TokenType::RBrace)?;
7625 Ok(node)
7626 }
7627
7628 fn parse_document(&mut self) -> Result<crate::ast::DocumentDefinition, ParseError> {
7636 let tok = self.consume(TokenType::Document)?;
7637 let name = self.consume(TokenType::Identifier)?.value;
7638 let mut node = crate::ast::DocumentDefinition {
7639 name,
7640 loc: Loc {
7641 line: tok.line,
7642 column: tok.column,
7643 },
7644 ..Default::default()
7645 };
7646 self.consume(TokenType::LBrace)?;
7647 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7648 let field = self.current().clone();
7649 let field_name = field.value.clone();
7650 self.advance();
7651 if self.check(TokenType::Colon) {
7652 self.advance();
7653 match field_name.as_str() {
7654 "target" => node.target = self.consume_any_ident_or_kw()?.value,
7655 "template" => node.template = self.parse_dotted_identifier()?,
7656 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
7657 "effects" => node.effects = Some(self.parse_effect_row()?),
7658 other => {
7659 return Err(self.error(&format!(
7660 "unknown document field `{other}` in document `{}` — expected \
7661 `target:` / `template:` / `provenance:` / `effects:`, or a body \
7662 block (`section {{ … }}` / `slide {{ … }}` / `sheet {{ … }}`)",
7663 node.name
7664 )))
7665 }
7666 }
7667 } else if self.check(TokenType::LBrace) {
7668 node.blocks
7669 .push(self.parse_doc_block_body(field_name, field.line, field.column)?);
7670 } else {
7671 return Err(self.error(&format!(
7672 "unexpected `{field_name}` in document `{}` body — expected a `field:` or a \
7673 body block `{field_name} {{ … }}`",
7674 node.name
7675 )));
7676 }
7677 if self.check(TokenType::Comma) {
7678 self.advance();
7679 }
7680 }
7681 self.consume(TokenType::RBrace)?;
7682 Ok(node)
7683 }
7684
7685 fn parse_doc_block_body(
7691 &mut self,
7692 kind: String,
7693 line: u32,
7694 column: u32,
7695 ) -> Result<crate::ast::DocBlock, ParseError> {
7696 let mut block = crate::ast::DocBlock {
7697 kind,
7698 loc: Loc { line, column },
7699 ..Default::default()
7700 };
7701 self.consume(TokenType::LBrace)?;
7702 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7703 let name_tok = self.current().clone();
7704 let name = self.consume_any_ident_or_kw()?.value;
7705 if self.check(TokenType::Colon) {
7706 self.advance();
7707 let value = self.parse_doc_scalar()?;
7708 block.fields.push((name, value));
7709 } else if self.check(TokenType::LBrace) {
7710 let child = self.parse_doc_block_body(name, name_tok.line, name_tok.column)?;
7711 block.children.push(child);
7712 } else {
7713 return Err(self.error(&format!(
7714 "in document block `{}`: `{name}` must be a `field:` value or open a nested \
7715 block `{name} {{ … }}`",
7716 block.kind
7717 )));
7718 }
7719 if self.check(TokenType::Comma) {
7720 self.advance();
7721 }
7722 }
7723 self.consume(TokenType::RBrace)?;
7724 Ok(block)
7725 }
7726
7727 fn parse_doc_scalar(&mut self) -> Result<crate::ast::DocScalar, ParseError> {
7732 let tok = self.current().clone();
7733 match tok.ttype {
7734 TokenType::StringLit => {
7735 self.advance();
7736 Ok(crate::ast::DocScalar::Text(tok.value))
7737 }
7738 TokenType::Integer => {
7739 self.advance();
7740 Ok(crate::ast::DocScalar::Int(tok.value.parse::<i64>().unwrap_or(0)))
7741 }
7742 TokenType::Bool => {
7743 self.advance();
7744 Ok(crate::ast::DocScalar::Bool(tok.value == "true"))
7745 }
7746 TokenType::LBracket => {
7747 let items = self.parse_bracketed_strings()?;
7748 Ok(crate::ast::DocScalar::List(items))
7749 }
7750 _ => {
7751 let name = self.consume_any_ident_or_kw()?.value;
7752 Ok(crate::ast::DocScalar::Ref(name))
7753 }
7754 }
7755 }
7756
7757 fn parse_deliver(&mut self) -> Result<crate::ast::DeliverDefinition, ParseError> {
7765 let tok = self.consume(TokenType::Deliver)?;
7766 let name = self.consume(TokenType::Identifier)?.value;
7767 let mut node = crate::ast::DeliverDefinition {
7768 name,
7769 loc: Loc {
7770 line: tok.line,
7771 column: tok.column,
7772 },
7773 ..Default::default()
7774 };
7775 self.consume(TokenType::LBrace)?;
7776 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7777 let field = self.current().clone();
7778 let field_name = field.value.clone();
7779 self.advance();
7780 if self.check(TokenType::Colon) {
7781 self.advance();
7782 match field_name.as_str() {
7783 "target" => node.target = self.consume_any_ident_or_kw()?.value,
7784 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
7785 "secret" => node.secret = self.consume_any_ident_or_kw()?.value,
7786 "effects" => node.effects = Some(self.parse_effect_row()?),
7787 other => {
7788 return Err(self.error(&format!(
7789 "unknown deliver field `{other}` in deliver `{}` — expected \
7790 `target:` / `provenance:` / `secret:` / `effects:`, or an operation \
7791 block (`upsert_contact {{ … }}` / `create_deal {{ … }}` / \
7792 `add_note {{ … }}`)",
7793 node.name
7794 )))
7795 }
7796 }
7797 } else if self.check(TokenType::LBrace) {
7798 node.ops
7799 .push(self.parse_deliver_op(field_name, field.line, field.column)?);
7800 } else {
7801 return Err(self.error(&format!(
7802 "unexpected `{field_name}` in deliver `{}` body — expected a `field:` or an \
7803 operation block `{field_name} {{ … }}`",
7804 node.name
7805 )));
7806 }
7807 if self.check(TokenType::Comma) {
7808 self.advance();
7809 }
7810 }
7811 self.consume(TokenType::RBrace)?;
7812 Ok(node)
7813 }
7814
7815 fn parse_deliver_op(
7819 &mut self,
7820 kind: String,
7821 line: u32,
7822 column: u32,
7823 ) -> Result<crate::ast::DeliverOp, ParseError> {
7824 let mut op = crate::ast::DeliverOp {
7825 kind,
7826 loc: Loc { line, column },
7827 ..Default::default()
7828 };
7829 self.consume(TokenType::LBrace)?;
7830 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7831 let name = self.consume_any_ident_or_kw()?.value;
7832 self.consume(TokenType::Colon).map_err(|_| {
7833 self.error(&format!(
7834 "in deliver operation `{}`: `{name}` must be a `field: value` pair — an \
7835 operation binds scalar fields, it takes no nested blocks",
7836 op.kind
7837 ))
7838 })?;
7839 let value = self.parse_doc_scalar()?;
7840 op.fields.push((name, value));
7841 if self.check(TokenType::Comma) {
7842 self.advance();
7843 }
7844 }
7845 self.consume(TokenType::RBrace)?;
7846 Ok(op)
7847 }
7848
7849 fn parse_savant(&mut self) -> Result<SavantDefinition, ParseError> {
7855 let tok = self.consume(TokenType::Savant)?;
7856 let name = self.consume(TokenType::Identifier)?.value;
7857 let mut node = SavantDefinition {
7858 name,
7859 loc: Loc {
7860 line: tok.line,
7861 column: tok.column,
7862 },
7863 ..Default::default()
7864 };
7865 self.consume(TokenType::LBrace)?;
7866 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7867 let field = self.current().clone();
7868 let field_name = field.value.clone();
7869 self.advance();
7870 if self.check(TokenType::Colon) {
7871 self.advance();
7872 match field_name.as_str() {
7873 "domain" => node.domain = self.consume(TokenType::StringLit)?.value,
7874 other => {
7875 return Err(self.error(&format!(
7876 "unknown savant field `{other}` in savant `{}` — expected \
7877 `domain:` or a `cognition` / `memory` / `budget` / `mandate` block",
7878 node.name
7879 )))
7880 }
7881 }
7882 } else if field_name == "cognition" {
7883 node.cognition = Some(self.parse_savant_cognition(field.line, field.column)?);
7884 } else if field_name == "memory" {
7885 node.memory = Some(self.parse_savant_memory(field.line, field.column)?);
7886 } else if field_name == "budget" {
7887 node.budget = Some(self.parse_savant_budget(field.line, field.column)?);
7888 } else if field_name == "mandate" {
7889 node.mandates
7890 .push(self.parse_savant_mandate(field.line, field.column)?);
7891 } else {
7892 return Err(self.error(&format!(
7893 "unexpected `{field_name}` in savant `{}` body — expected `domain:` or a \
7894 `cognition` / `memory` / `budget` / `mandate` block",
7895 node.name
7896 )));
7897 }
7898 if self.check(TokenType::Comma) {
7899 self.advance();
7900 }
7901 }
7902 self.consume(TokenType::RBrace)?;
7903 Ok(node)
7904 }
7905
7906 fn parse_savant_cognition(
7909 &mut self,
7910 line: u32,
7911 column: u32,
7912 ) -> Result<SavantCognition, ParseError> {
7913 self.consume(TokenType::LBrace)?;
7914 let mut node = SavantCognition {
7915 loc: Loc { line, column },
7916 ..Default::default()
7917 };
7918 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7919 let key = self.consume_any_ident_or_kw()?.value;
7920 self.consume(TokenType::Colon)?;
7921 match key.as_str() {
7922 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
7923 "entropic_threshold" => node.entropic_threshold = self.parse_optional_float(),
7924 "divergence" => node.divergence = self.consume_any_ident_or_kw()?.value,
7925 other => {
7926 return Err(self.error(&format!(
7927 "unknown savant `cognition` field `{other}` — expected \
7928 `depth` / `entropic_threshold` / `divergence`"
7929 )))
7930 }
7931 }
7932 if self.check(TokenType::Comma) {
7933 self.advance();
7934 }
7935 }
7936 self.consume(TokenType::RBrace)?;
7937 Ok(node)
7938 }
7939
7940 fn parse_savant_memory(
7943 &mut self,
7944 line: u32,
7945 column: u32,
7946 ) -> Result<SavantMemory, ParseError> {
7947 self.consume(TokenType::LBrace)?;
7948 let mut node = SavantMemory {
7949 loc: Loc { line, column },
7950 ..Default::default()
7951 };
7952 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7953 let key = self.consume_any_ident_or_kw()?.value;
7954 self.consume(TokenType::Colon)?;
7955 match key.as_str() {
7956 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
7957 "corpus_graph" => {
7958 node.corpus_graph = self.consume_any_ident_or_kw()?.value == "true"
7959 }
7960 "isolation_level" => node.isolation_level = self.consume_any_ident_or_kw()?.value,
7961 other => {
7962 return Err(self.error(&format!(
7963 "unknown savant `memory` field `{other}` — expected \
7964 `backend` / `corpus_graph` / `isolation_level`"
7965 )))
7966 }
7967 }
7968 if self.check(TokenType::Comma) {
7969 self.advance();
7970 }
7971 }
7972 self.consume(TokenType::RBrace)?;
7973 Ok(node)
7974 }
7975
7976 fn parse_savant_budget(
7979 &mut self,
7980 line: u32,
7981 column: u32,
7982 ) -> Result<SavantBudget, ParseError> {
7983 self.consume(TokenType::LBrace)?;
7984 let mut node = SavantBudget {
7985 loc: Loc { line, column },
7986 ..Default::default()
7987 };
7988 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7989 let key = self.consume_any_ident_or_kw()?.value;
7990 self.consume(TokenType::Colon)?;
7991 match key.as_str() {
7992 "max_iterations" => node.max_iterations = self.parse_optional_int(),
7993 "max_tool_synth" => node.max_tool_synth = self.parse_optional_int(),
7994 other => {
7995 return Err(self.error(&format!(
7996 "unknown savant `budget` field `{other}` — expected \
7997 `max_iterations` / `max_tool_synth`"
7998 )))
7999 }
8000 }
8001 if self.check(TokenType::Comma) {
8002 self.advance();
8003 }
8004 }
8005 self.consume(TokenType::RBrace)?;
8006 Ok(node)
8007 }
8008
8009 fn parse_savant_mandate(
8012 &mut self,
8013 line: u32,
8014 column: u32,
8015 ) -> Result<SavantMandate, ParseError> {
8016 let name = self.consume(TokenType::Identifier)?.value;
8017 let mut node = SavantMandate {
8018 name,
8019 loc: Loc { line, column },
8020 ..Default::default()
8021 };
8022 self.consume(TokenType::LBrace)?;
8023 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8024 let key = self.consume_any_ident_or_kw()?.value;
8025 self.consume(TokenType::Colon)?;
8026 match key.as_str() {
8027 "objective" => node.objective = self.consume(TokenType::StringLit)?.value,
8028 "output" => node.output_type = self.consume_any_ident_or_kw()?.value,
8029 other => {
8030 return Err(self.error(&format!(
8031 "unknown savant `mandate` field `{other}` — expected `objective` / `output`"
8032 )))
8033 }
8034 }
8035 if self.check(TokenType::Comma) {
8036 self.advance();
8037 }
8038 }
8039 self.consume(TokenType::RBrace)?;
8040 Ok(node)
8041 }
8042
8043 fn parse_synth(&mut self) -> Result<SynthDefinition, ParseError> {
8048 let tok = self.consume(TokenType::Synth)?;
8049 let name = self.consume(TokenType::Identifier)?.value;
8050 let mut node = SynthDefinition {
8051 name,
8052 loc: Loc {
8053 line: tok.line,
8054 column: tok.column,
8055 },
8056 ..Default::default()
8057 };
8058 self.consume(TokenType::LBrace)?;
8059 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8060 let key = self.consume_any_ident_or_kw()?.value;
8061 self.consume(TokenType::Colon)?;
8062 match key.as_str() {
8063 "target" => node.target = self.consume(TokenType::StringLit)?.value,
8064 "risk" => node.risk = self.consume_any_ident_or_kw()?.value,
8065 "language" => node.language = self.consume_any_ident_or_kw()?.value,
8066 "sandbox" => node.sandbox = self.consume_any_ident_or_kw()?.value,
8067 "review" => node.review = self.consume_any_ident_or_kw()?.value,
8068 "max_lines" => node.max_lines = self.parse_optional_int(),
8069 other => {
8070 return Err(self.error(&format!(
8071 "unknown synth field `{other}` in synth `{}` — expected `target` / `risk` \
8072 / `language` / `sandbox` / `review` / `max_lines`",
8073 node.name
8074 )))
8075 }
8076 }
8077 if self.check(TokenType::Comma) {
8078 self.consume(TokenType::Comma)?;
8079 }
8080 }
8081 self.consume(TokenType::RBrace)?;
8082 Ok(node)
8083 }
8084
8085 fn parse_voice(&mut self) -> Result<VoiceDefinition, ParseError> {
8090 let tok = self.consume(TokenType::Voice)?;
8091 let name = self.consume(TokenType::Identifier)?.value;
8092 let mut node = VoiceDefinition {
8093 name,
8094 loc: Loc { line: tok.line, column: tok.column },
8095 ..Default::default()
8096 };
8097 self.consume(TokenType::LBrace)?;
8098 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8099 let key = self.consume_any_ident_or_kw()?.value;
8100 self.consume(TokenType::Colon)?;
8101 match key.as_str() {
8102 "stt" => node.stt = Some(self.parse_upstream_ref()?),
8104 "tts" => node.tts = Some(self.parse_upstream_ref()?),
8105 "realtime" => node.realtime = Some(self.parse_upstream_ref()?),
8106 "carrier" => node.carrier = self.consume_any_ident_or_kw()?.value,
8107 "interruptible" => {
8108 let v = self.consume_any_ident_or_kw()?.value;
8109 node.interruptible = v == "true";
8110 }
8111 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
8112 "persona" => node.persona = Some(self.consume(TokenType::Identifier)?.value),
8113 "context" => node.context = Some(self.consume(TokenType::Identifier)?.value),
8114 other => return Err(self.error(&format!("unknown voice field `{other}`"))),
8115 }
8116 if self.check(TokenType::Comma) {
8117 self.consume(TokenType::Comma)?;
8118 }
8119 }
8120 self.consume(TokenType::RBrace)?;
8121 Ok(node)
8122 }
8123
8124 fn parse_upstream_ref(&mut self) -> Result<String, ParseError> {
8127 let base = self.consume(TokenType::Identifier)?.value;
8128 if self.check(TokenType::At) {
8129 self.advance();
8130 let version = self.consume_any_ident_or_kw()?.value;
8131 Ok(format!("{base}@{version}"))
8132 } else {
8133 Ok(base)
8134 }
8135 }
8136
8137 fn parse_upstream_map(&mut self) -> Result<Vec<UpstreamMapRule>, ParseError> {
8143 self.consume(TokenType::LBracket)?;
8144 let mut rules = Vec::new();
8145 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8146 let dir_tok = self.current().clone();
8147 let direction = match dir_tok.ttype {
8148 TokenType::Send => "send",
8149 TokenType::Receive => "receive",
8150 _ => {
8151 return Err(self.error(&format!(
8152 "upstream map rule must start with `send` or `receive`, got `{}`",
8153 dir_tok.value
8154 )))
8155 }
8156 };
8157 self.advance();
8158 let message = self.consume(TokenType::Identifier)?.value;
8159 self.consume(TokenType::As)?;
8160 let framing = self.consume_any_ident_or_kw()?.value;
8161 let mut rule = UpstreamMapRule {
8162 direction: direction.to_string(),
8163 message,
8164 framing,
8165 loc: Loc { line: dir_tok.line, column: dir_tok.column },
8166 ..Default::default()
8167 };
8168 if self.current().value == "tag" {
8170 self.advance();
8171 rule.tag = Some(self.consume(TokenType::StringLit)?.value);
8172 } else if self.current().value == "when" {
8173 self.advance();
8178 rule.when_field = Some(self.consume(TokenType::StringLit)?.value);
8179 if self.check(TokenType::Assign) {
8180 self.advance();
8181 rule.when_value = Some(self.consume(TokenType::StringLit)?.value);
8182 }
8183 }
8184 rules.push(rule);
8185 if self.check(TokenType::Comma) {
8186 self.advance();
8187 }
8188 }
8189 self.consume(TokenType::RBracket)?;
8190 Ok(rules)
8191 }
8192
8193 fn parse_upstream_reconnect(&mut self) -> Result<UpstreamReconnect, ParseError> {
8197 self.consume(TokenType::LBrace)?;
8198 let mut backoff_ms: Option<i64> = None;
8199 let mut max_attempts: Option<i64> = None;
8200 let mut on_exhausted: Option<String> = None;
8201 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8202 let key = self.consume_any_ident_or_kw()?.value;
8203 self.consume(TokenType::Colon)?;
8204 match key.as_str() {
8205 "backoff_ms" => {
8206 backoff_ms = Some(
8207 self.consume(TokenType::Integer)?
8208 .value
8209 .parse::<i64>()
8210 .map_err(|_| self.error("backoff_ms must be an integer"))?,
8211 )
8212 }
8213 "max_attempts" => {
8214 max_attempts = Some(
8215 self.consume(TokenType::Integer)?
8216 .value
8217 .parse::<i64>()
8218 .map_err(|_| self.error("max_attempts must be an integer"))?,
8219 )
8220 }
8221 "on_exhausted" => on_exhausted = Some(self.consume_any_ident_or_kw()?.value),
8222 other => return Err(self.error(&format!("unknown reconnect field `{other}`"))),
8223 }
8224 if self.check(TokenType::Comma) {
8225 self.consume(TokenType::Comma)?;
8226 }
8227 }
8228 self.consume(TokenType::RBrace)?;
8229 match (backoff_ms, max_attempts, on_exhausted) {
8230 (Some(b), Some(m), Some(o)) => Ok(UpstreamReconnect { backoff_ms: b, max_attempts: m, on_exhausted: o }),
8231 _ => Err(self.error(
8232 "reconnect requires all of `backoff_ms:`, `max_attempts:`, `on_exhausted:` — a reconnection policy with a hole is not a policy",
8233 )),
8234 }
8235 }
8236
8237 fn parse_session_steps(&mut self) -> Result<Vec<SessionStep>, ParseError> {
8239 self.consume(TokenType::LBracket)?;
8240 let mut steps = Vec::new();
8241 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8242 steps.push(self.parse_session_step()?);
8243 if self.check(TokenType::Comma) {
8244 self.advance();
8245 }
8246 }
8247 self.consume(TokenType::RBracket)?;
8248 Ok(steps)
8249 }
8250
8251 fn parse_session_step_block(&mut self) -> Result<Vec<SessionStep>, ParseError> {
8255 self.consume(TokenType::LBrace)?;
8256 let mut steps = Vec::new();
8257 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8258 steps.push(self.parse_session_step()?);
8259 if self.check(TokenType::Comma) {
8260 self.advance();
8261 }
8262 }
8263 self.consume(TokenType::RBrace)?;
8264 Ok(steps)
8265 }
8266
8267 fn parse_session_step(&mut self) -> Result<SessionStep, ParseError> {
8268 let tok = self.current().clone();
8269 let loc = Loc { line: tok.line, column: tok.column };
8270 match tok.ttype {
8271 TokenType::Send => {
8272 self.advance();
8273 let msg = self.consume_any_ident_or_kw()?;
8274 Ok(SessionStep { op: "send".into(), message_type: msg.value, loc, ..Default::default() })
8275 }
8276 TokenType::Receive => {
8277 self.advance();
8278 let msg = self.consume_any_ident_or_kw()?;
8279 Ok(SessionStep { op: "receive".into(), message_type: msg.value, loc, ..Default::default() })
8280 }
8281 TokenType::Loop => {
8282 self.advance();
8283 Ok(SessionStep { op: "loop".into(), loc, ..Default::default() })
8284 }
8285 TokenType::End => {
8286 self.advance();
8287 Ok(SessionStep { op: "end".into(), loc, ..Default::default() })
8288 }
8289 TokenType::Identifier if tok.value == "select" || tok.value == "branch" => {
8292 self.parse_session_choice(&tok.value, loc)
8293 }
8294 TokenType::Identifier if tok.value == "interrupt" => {
8297 self.parse_session_interrupt(loc)
8298 }
8299 TokenType::Identifier if tok.value == "resume" => {
8303 self.advance();
8304 Ok(SessionStep { op: "resume".into(), loc, ..Default::default() })
8305 }
8306 _ => Err(ParseError {
8307 message: format!(
8308 "Invalid session step '{}' — expected send | receive | loop | end | select | branch | interrupt | resume",
8309 tok.value
8310 ),
8311 line: tok.line,
8312 column: tok.column,
8313 ..Default::default()
8314 }),
8315 }
8316 }
8317
8318 fn consume_contextual(&mut self, kw: &str) -> Result<(), ParseError> {
8323 let t = self.current().clone();
8324 if t.value != kw {
8325 return Err(ParseError {
8326 message: format!("expected `{kw}` in interrupt step, got `{}`", t.value),
8327 line: t.line,
8328 column: t.column,
8329 ..Default::default()
8330 });
8331 }
8332 self.advance();
8333 Ok(())
8334 }
8335
8336 fn parse_session_interrupt(&mut self, loc: Loc) -> Result<SessionStep, ParseError> {
8344 self.advance(); let body = self.parse_session_step_block()?;
8348 self.consume_contextual("on")?;
8350 let signal = self.consume_any_ident_or_kw()?;
8351 self.consume_contextual("as")?;
8353 let binder = self.consume_any_ident_or_kw()?;
8354 self.consume_contextual("resumable")?;
8356 let handler = self.parse_session_step_block()?;
8357 Ok(SessionStep {
8358 op: "interrupt".into(),
8359 message_type: signal.value,
8360 branches: vec![
8361 SessionBranch { label: "body".into(), steps: body, loc: loc.clone() },
8362 SessionBranch { label: "handler".into(), steps: handler, loc: loc.clone() },
8363 ],
8364 binder: binder.value,
8365 resumable: true,
8366 loc,
8367 })
8368 }
8369
8370 fn parse_session_choice(&mut self, op: &str, loc: Loc) -> Result<SessionStep, ParseError> {
8373 self.advance(); self.consume(TokenType::LBrace)?;
8375 let mut branches = Vec::new();
8376 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8377 let label_tok = self.consume_any_ident_or_kw()?;
8378 self.consume(TokenType::Colon)?;
8379 let steps = self.parse_session_steps()?;
8380 branches.push(SessionBranch {
8381 label: label_tok.value,
8382 steps,
8383 loc: Loc { line: label_tok.line, column: label_tok.column },
8384 });
8385 if self.check(TokenType::Comma) {
8386 self.advance();
8387 }
8388 }
8389 self.consume(TokenType::RBrace)?;
8390 Ok(SessionStep { op: op.to_string(), branches, loc, ..Default::default() })
8391 }
8392
8393 fn parse_topology(&mut self) -> Result<TopologyDefinition, ParseError> {
8395 let tok = self.consume(TokenType::Topology)?;
8396 let name = self.consume(TokenType::Identifier)?.value;
8397 let mut node = TopologyDefinition {
8398 name,
8399 nodes: Vec::new(),
8400 edges: Vec::new(),
8401 loc: Loc {
8402 line: tok.line,
8403 column: tok.column,
8404 },
8405 leading_trivia: Vec::new(),
8406 trailing_trivia: Vec::new(),
8407 };
8408 self.consume(TokenType::LBrace)?;
8409 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8410 let field_name = self.current().value.clone();
8411 self.advance();
8412 if !self.check(TokenType::Colon) {
8413 if self.check(TokenType::LBrace) {
8414 self.skip_braced_block()?;
8415 }
8416 continue;
8417 }
8418 self.advance();
8419 match field_name.as_str() {
8420 "nodes" => node.nodes = self.parse_bracketed_identifiers()?,
8421 "edges" => node.edges = self.parse_topology_edges()?,
8422 _ => self.skip_value(),
8423 }
8424 }
8425 self.consume(TokenType::RBrace)?;
8426 Ok(node)
8427 }
8428
8429 fn parse_topology_edges(&mut self) -> Result<Vec<TopologyEdge>, ParseError> {
8430 self.consume(TokenType::LBracket)?;
8431 let mut edges = Vec::new();
8432 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8433 edges.push(self.parse_topology_edge()?);
8434 if self.check(TokenType::Comma) {
8435 self.advance();
8436 }
8437 }
8438 self.consume(TokenType::RBracket)?;
8439 Ok(edges)
8440 }
8441
8442 fn parse_topology_edge(&mut self) -> Result<TopologyEdge, ParseError> {
8443 let src_tok = self.consume_any_ident_or_kw()?;
8444 self.consume(TokenType::Arrow)?;
8445 let tgt_tok = self.consume_any_ident_or_kw()?;
8446 self.consume(TokenType::Colon)?;
8447 let sess_tok = self.consume_any_ident_or_kw()?;
8448 Ok(TopologyEdge {
8449 source: src_tok.value,
8450 target: tgt_tok.value,
8451 session_ref: sess_tok.value,
8452 loc: Loc {
8453 line: src_tok.line,
8454 column: src_tok.column,
8455 },
8456 })
8457 }
8458
8459 fn parse_immune(&mut self) -> Result<ImmuneDefinition, ParseError> {
8463 let tok = self.consume(TokenType::Immune)?;
8464 let name = self.consume(TokenType::Identifier)?.value;
8465 let mut node = ImmuneDefinition {
8466 name,
8467 watch: Vec::new(),
8468 sensitivity: None,
8469 baseline: "learned".to_string(),
8470 window: 100,
8471 scope: String::new(),
8472 tau: String::new(),
8473 decay: "exponential".to_string(),
8474 loc: Loc {
8475 line: tok.line,
8476 column: tok.column,
8477 },
8478 leading_trivia: Vec::new(),
8479 trailing_trivia: Vec::new(),
8480 };
8481 self.consume(TokenType::LBrace)?;
8482 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8483 let field_name = self.current().value.clone();
8484 self.advance();
8485 if !self.check(TokenType::Colon) {
8486 if self.check(TokenType::LBrace) {
8487 self.skip_braced_block()?;
8488 }
8489 continue;
8490 }
8491 self.advance();
8492 match field_name.as_str() {
8493 "watch" => node.watch = self.parse_bracketed_identifiers()?,
8494 "sensitivity" => node.sensitivity = self.parse_optional_float(),
8495 "baseline" => node.baseline = self.consume_any_ident_or_kw()?.value,
8496 "window" => {
8497 if let Some(v) = self.parse_optional_int() {
8498 node.window = v;
8499 }
8500 }
8501 "scope" => {
8502 let s_tok = self.consume_any_ident_or_kw()?;
8503 let s = s_tok.value;
8504 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
8505 return Err(ParseError {
8506 message: format!(
8507 "Invalid scope '{s}' in immune '{}' — \
8508 expected tenant | flow | global",
8509 node.name
8510 ),
8511 line: s_tok.line,
8512 column: s_tok.column,
8513 ..Default::default()
8514 });
8515 }
8516 node.scope = s;
8517 }
8518 "tau" => {
8519 let t = self.current().clone();
8520 match t.ttype {
8521 TokenType::Duration | TokenType::StringLit => {
8522 self.advance();
8523 node.tau = t.value;
8524 }
8525 _ => node.tau = self.consume_any_ident_or_kw()?.value,
8526 }
8527 }
8528 "decay" => {
8529 let d_tok = self.consume_any_ident_or_kw()?;
8530 let d = d_tok.value;
8531 if !matches!(d.as_str(), "exponential" | "linear" | "none") {
8532 return Err(ParseError {
8533 message: format!(
8534 "Invalid decay '{d}' in immune '{}' — \
8535 expected exponential | linear | none",
8536 node.name
8537 ),
8538 line: d_tok.line,
8539 column: d_tok.column,
8540 ..Default::default()
8541 });
8542 }
8543 node.decay = d;
8544 }
8545 _ => self.skip_value(),
8546 }
8547 }
8548 self.consume(TokenType::RBrace)?;
8549 Ok(node)
8550 }
8551
8552 fn parse_reflex(&mut self) -> Result<ReflexDefinition, ParseError> {
8554 let tok = self.consume(TokenType::Reflex)?;
8555 let name = self.consume(TokenType::Identifier)?.value;
8556 let mut node = ReflexDefinition {
8557 name,
8558 trigger: String::new(),
8559 on_level: "doubt".to_string(),
8560 action: String::new(),
8561 scope: String::new(),
8562 sla: String::new(),
8563 loc: Loc {
8564 line: tok.line,
8565 column: tok.column,
8566 },
8567 leading_trivia: Vec::new(),
8568 trailing_trivia: Vec::new(),
8569 };
8570 self.consume(TokenType::LBrace)?;
8571 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8572 let field_name = self.current().value.clone();
8573 self.advance();
8574 if !self.check(TokenType::Colon) {
8575 if self.check(TokenType::LBrace) {
8576 self.skip_braced_block()?;
8577 }
8578 continue;
8579 }
8580 self.advance();
8581 match field_name.as_str() {
8582 "trigger" => node.trigger = self.consume_any_ident_or_kw()?.value,
8583 "on_level" => {
8584 let l_tok = self.consume_any_ident_or_kw()?;
8585 let l = l_tok.value;
8586 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
8587 return Err(ParseError {
8588 message: format!(
8589 "Invalid on_level '{l}' in reflex '{}' — \
8590 expected know | believe | speculate | doubt",
8591 node.name
8592 ),
8593 line: l_tok.line,
8594 column: l_tok.column,
8595 ..Default::default()
8596 });
8597 }
8598 node.on_level = l;
8599 }
8600 "action" => {
8601 let a_tok = self.consume_any_ident_or_kw()?;
8602 let a = a_tok.value;
8603 if !matches!(
8604 a.as_str(),
8605 "drop"
8606 | "revoke"
8607 | "emit"
8608 | "redact"
8609 | "quarantine"
8610 | "terminate"
8611 | "alert"
8612 ) {
8613 return Err(ParseError {
8614 message: format!(
8615 "Invalid action '{a}' in reflex '{}' — \
8616 expected drop | revoke | emit | redact | \
8617 quarantine | terminate | alert",
8618 node.name
8619 ),
8620 line: a_tok.line,
8621 column: a_tok.column,
8622 ..Default::default()
8623 });
8624 }
8625 node.action = a;
8626 }
8627 "scope" => {
8628 let s_tok = self.consume_any_ident_or_kw()?;
8629 let s = s_tok.value;
8630 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
8631 return Err(ParseError {
8632 message: format!(
8633 "Invalid scope '{s}' in reflex '{}' — \
8634 expected tenant | flow | global",
8635 node.name
8636 ),
8637 line: s_tok.line,
8638 column: s_tok.column,
8639 ..Default::default()
8640 });
8641 }
8642 node.scope = s;
8643 }
8644 "sla" => {
8645 let t = self.current().clone();
8646 match t.ttype {
8647 TokenType::Duration | TokenType::StringLit => {
8648 self.advance();
8649 node.sla = t.value;
8650 }
8651 _ => node.sla = self.consume_any_ident_or_kw()?.value,
8652 }
8653 }
8654 _ => self.skip_value(),
8655 }
8656 }
8657 self.consume(TokenType::RBrace)?;
8658 Ok(node)
8659 }
8660
8661 fn parse_heal(&mut self) -> Result<HealDefinition, ParseError> {
8663 let tok = self.consume(TokenType::Heal)?;
8664 let name = self.consume(TokenType::Identifier)?.value;
8665 let mut node = HealDefinition {
8666 name,
8667 source: String::new(),
8668 on_level: "doubt".to_string(),
8669 mode: "human_in_loop".to_string(),
8670 scope: String::new(),
8671 review_sla: String::new(),
8672 shield_ref: String::new(),
8673 max_patches: 3,
8674 loc: Loc {
8675 line: tok.line,
8676 column: tok.column,
8677 },
8678 leading_trivia: Vec::new(),
8679 trailing_trivia: Vec::new(),
8680 };
8681 self.consume(TokenType::LBrace)?;
8682 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8683 let field_name = self.current().value.clone();
8684 self.advance();
8685 if !self.check(TokenType::Colon) {
8686 if self.check(TokenType::LBrace) {
8687 self.skip_braced_block()?;
8688 }
8689 continue;
8690 }
8691 self.advance();
8692 match field_name.as_str() {
8693 "source" => node.source = self.consume_any_ident_or_kw()?.value,
8694 "on_level" => {
8695 let l_tok = self.consume_any_ident_or_kw()?;
8696 let l = l_tok.value;
8697 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
8698 return Err(ParseError {
8699 message: format!(
8700 "Invalid on_level '{l}' in heal '{}' — \
8701 expected know | believe | speculate | doubt",
8702 node.name
8703 ),
8704 line: l_tok.line,
8705 column: l_tok.column,
8706 ..Default::default()
8707 });
8708 }
8709 node.on_level = l;
8710 }
8711 "mode" => {
8712 let m_tok = self.consume_any_ident_or_kw()?;
8713 let m = m_tok.value;
8714 if !matches!(m.as_str(), "audit_only" | "human_in_loop" | "adversarial") {
8715 return Err(ParseError {
8716 message: format!(
8717 "Invalid mode '{m}' in heal '{}' — \
8718 expected audit_only | human_in_loop | adversarial",
8719 node.name
8720 ),
8721 line: m_tok.line,
8722 column: m_tok.column,
8723 ..Default::default()
8724 });
8725 }
8726 node.mode = m;
8727 }
8728 "scope" => {
8729 let s_tok = self.consume_any_ident_or_kw()?;
8730 let s = s_tok.value;
8731 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
8732 return Err(ParseError {
8733 message: format!(
8734 "Invalid scope '{s}' in heal '{}' — \
8735 expected tenant | flow | global",
8736 node.name
8737 ),
8738 line: s_tok.line,
8739 column: s_tok.column,
8740 ..Default::default()
8741 });
8742 }
8743 node.scope = s;
8744 }
8745 "review_sla" => {
8746 let t = self.current().clone();
8747 match t.ttype {
8748 TokenType::Duration | TokenType::StringLit => {
8749 self.advance();
8750 node.review_sla = t.value;
8751 }
8752 _ => node.review_sla = self.consume_any_ident_or_kw()?.value,
8753 }
8754 }
8755 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
8756 "max_patches" => {
8757 if let Some(v) = self.parse_optional_int() {
8758 node.max_patches = v;
8759 }
8760 }
8761 _ => self.skip_value(),
8762 }
8763 }
8764 self.consume(TokenType::RBrace)?;
8765 Ok(node)
8766 }
8767
8768 fn parse_component(&mut self) -> Result<ComponentDefinition, ParseError> {
8772 let tok = self.consume(TokenType::Component)?;
8773 let name = self.consume(TokenType::Identifier)?.value;
8774 let mut node = ComponentDefinition {
8775 name,
8776 renders: String::new(),
8777 via_shield: String::new(),
8778 on_interact: String::new(),
8779 render_hint: "custom".to_string(),
8780 loc: Loc {
8781 line: tok.line,
8782 column: tok.column,
8783 },
8784 leading_trivia: Vec::new(),
8785 trailing_trivia: Vec::new(),
8786 };
8787 self.consume(TokenType::LBrace)?;
8788 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8789 let field_name = self.current().value.clone();
8790 self.advance();
8791 if !self.check(TokenType::Colon) {
8792 if self.check(TokenType::LBrace) {
8793 self.skip_braced_block()?;
8794 }
8795 continue;
8796 }
8797 self.advance();
8798 match field_name.as_str() {
8799 "renders" => node.renders = self.consume_any_ident_or_kw()?.value,
8800 "via_shield" => node.via_shield = self.consume_any_ident_or_kw()?.value,
8801 "on_interact" => node.on_interact = self.consume_any_ident_or_kw()?.value,
8802 "render_hint" => {
8803 let h_tok = self.consume_any_ident_or_kw()?;
8804 let h = h_tok.value;
8805 if !matches!(h.as_str(), "card" | "list" | "form" | "chart" | "custom") {
8806 return Err(ParseError {
8807 message: format!(
8808 "Invalid render_hint '{h}' in component '{}' — \
8809 expected card | list | form | chart | custom",
8810 node.name
8811 ),
8812 line: h_tok.line,
8813 column: h_tok.column,
8814 ..Default::default()
8815 });
8816 }
8817 node.render_hint = h;
8818 }
8819 _ => self.skip_value(),
8820 }
8821 }
8822 self.consume(TokenType::RBrace)?;
8823 Ok(node)
8824 }
8825
8826 fn parse_view(&mut self) -> Result<ViewDefinition, ParseError> {
8828 let tok = self.consume(TokenType::View)?;
8829 let name = self.consume(TokenType::Identifier)?.value;
8830 let mut node = ViewDefinition {
8831 name,
8832 title: String::new(),
8833 components: Vec::new(),
8834 route: String::new(),
8835 loc: Loc {
8836 line: tok.line,
8837 column: tok.column,
8838 },
8839 leading_trivia: Vec::new(),
8840 trailing_trivia: Vec::new(),
8841 };
8842 self.consume(TokenType::LBrace)?;
8843 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8844 let field_name = self.current().value.clone();
8845 self.advance();
8846 if !self.check(TokenType::Colon) {
8847 if self.check(TokenType::LBrace) {
8848 self.skip_braced_block()?;
8849 }
8850 continue;
8851 }
8852 self.advance();
8853 match field_name.as_str() {
8854 "title" => node.title = self.consume(TokenType::StringLit)?.value,
8855 "components" => node.components = self.parse_bracketed_identifiers()?,
8856 "route" => node.route = self.consume(TokenType::StringLit)?.value,
8857 _ => self.skip_value(),
8858 }
8859 }
8860 self.consume(TokenType::RBrace)?;
8861 Ok(node)
8862 }
8863
8864 fn parse_axonendpoint(&mut self) -> Result<AxonEndpointDefinition, ParseError> {
8865 let tok = self.consume(TokenType::AxonEndpoint)?;
8866 let name = self.consume(TokenType::Identifier)?.value;
8867 let mut node = AxonEndpointDefinition {
8868 name,
8869 method: String::new(),
8870 path: String::new(),
8871 body_type: String::new(),
8872 execute_flow: String::new(),
8873 output_type: String::new(),
8874 shield_ref: String::new(),
8875 cors_ref: String::new(),
8878 retries: None,
8879 timeout: String::new(),
8880 compliance: Vec::new(),
8881 transport: "json".to_string(),
8883 keepalive: String::new(),
8884 transport_explicit: false,
8890 implicit_transport: String::new(),
8891 requires_capabilities: Vec::new(),
8893 public: false,
8896 replay_explicit: false,
8900 replay: false,
8901 transport_dialect: String::new(),
8906 has_algebraic_stream_effect: false,
8912 backend: String::new(),
8917 path_params: Vec::new(),
8922 query_params: Vec::new(),
8927 loc: Loc {
8928 line: tok.line,
8929 column: tok.column,
8930 },
8931 leading_trivia: Vec::new(),
8932 trailing_trivia: Vec::new(),
8933 };
8934 self.consume(TokenType::LBrace)?;
8935 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8936 let field_name = self.current().value.clone();
8937 self.advance();
8938 if self.check(TokenType::Colon) {
8939 self.advance();
8940 match field_name.as_str() {
8941 "method" => {
8942 let value_tok = self.consume_any_ident_or_kw()?;
8949 let value_upper = value_tok.value.to_uppercase();
8950 if !axonendpoint_is_valid_method(&value_upper) {
8951 let hint = crate::smart_suggest::suggest_for(
8952 &value_upper,
8953 AXONENDPOINT_METHOD_VALUES,
8954 );
8955 let base = format!(
8956 "Invalid method '{}' in axonendpoint '{}'.",
8957 value_tok.value, node.name
8958 );
8959 let message = if hint.is_empty() {
8960 format!(
8961 "{base} expected GET | POST | PUT | DELETE | PATCH, found {}",
8962 value_tok.value
8963 )
8964 } else {
8965 format!(
8966 "{base} {hint} (expected GET | POST | PUT | DELETE | PATCH, found {})",
8967 value_tok.value
8968 )
8969 };
8970 return Err(ParseError {
8971 message,
8972 line: value_tok.line,
8973 column: value_tok.column,
8974 ..Default::default()
8975 });
8976 }
8977 node.method = value_upper;
8978 }
8979 "path" => {
8980 node.path = self.consume(TokenType::StringLit)?.value.clone();
8981 match extract_path_param_names(&node.path) {
8989 Ok(names) => node.path_params = names,
8990 Err(dup) => {
8991 let cur = self.current().clone();
8992 return Err(ParseError {
8993 message: format!(
8994 "axonendpoint '{}' declares path '{}' \
8995 containing duplicate placeholder '{{{}}}'. \
8996 Each `{{name}}` in a `path:` must be \
8997 unique — the runtime cannot bind two \
8998 path segments to the same name (Fase 37.y D1).",
8999 node.name, node.path, dup,
9000 ),
9001 line: cur.line,
9002 column: cur.column,
9003 ..Default::default()
9004 });
9005 }
9006 }
9007 },
9008 "body" => node.body_type = self.consume_any_ident_or_kw()?.value.clone(),
9009 "query" => {
9010 let lbrace_tok = self.consume(TokenType::LBrace)?;
9029 let block_line = lbrace_tok.line;
9030 if !node.query_params.is_empty() {
9031 return Err(ParseError {
9032 message: format!(
9033 "axonendpoint '{}' declares `query: {{ … }}` \
9034 more than once. The query-parameter block \
9035 is unique per endpoint; combine all params \
9036 into a single block (Fase 37.y D2).",
9037 node.name,
9038 ),
9039 line: lbrace_tok.line,
9040 column: lbrace_tok.column,
9041 ..Default::default()
9042 });
9043 }
9044 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9045 let name_tok = self.consume(TokenType::Identifier)?;
9046 let field_name = name_tok.value.clone();
9047 if node
9049 .query_params
9050 .iter()
9051 .any(|f| f.name == field_name)
9052 {
9053 return Err(ParseError {
9054 message: format!(
9055 "axonendpoint '{}' declares duplicate \
9056 query param '{}' inside `query: {{ … }}`. \
9057 Each name must appear at most once \
9058 (Fase 37.y D2).",
9059 node.name, field_name,
9060 ),
9061 line: name_tok.line,
9062 column: name_tok.column,
9063 ..Default::default()
9064 });
9065 }
9066 self.consume(TokenType::Colon)?;
9067 let type_expr = self.parse_type_expr()?;
9068 if !type_expr.generic_param.is_empty() {
9080 let canonical_hint = if type_expr.name == "Optional" {
9081 format!(
9082 " Use `{}?` (the `?` suffix) for an \
9083 optional query param instead of \
9084 `Optional<{}>`.",
9085 type_expr.generic_param,
9086 type_expr.generic_param,
9087 )
9088 } else if type_expr.name == "List" {
9089 " Multi-value query params (e.g. `?tag=a&tag=b`) \
9090 are honest-deferred from v1.38.5; bind a \
9091 single-value `Text` query param and parse \
9092 the value inside the flow."
9093 .to_string()
9094 } else {
9095 String::new()
9096 };
9097 return Err(ParseError {
9098 message: format!(
9099 "axonendpoint '{}' query param '{}' uses \
9100 a generic type `{}<{}>`. Query params \
9101 take a primitive type from the closed \
9102 catalog ({}); the `?` suffix marks \
9103 optional.{} (Fase 37.y D2).",
9104 node.name,
9105 field_name,
9106 type_expr.name,
9107 type_expr.generic_param,
9108 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | "),
9109 canonical_hint,
9110 ),
9111 line: type_expr.loc.line,
9112 column: type_expr.loc.column,
9113 ..Default::default()
9114 });
9115 }
9116 if !axonendpoint_is_valid_query_param_type(&type_expr.name) {
9120 let hint = crate::smart_suggest::suggest_for(
9127 &type_expr.name,
9128 AXONENDPOINT_QUERY_PARAM_TYPES,
9129 );
9130 let hint_text = if hint.is_empty() {
9131 format!(
9132 " Expected one of: {}.",
9133 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
9134 )
9135 } else {
9136 format!(
9137 " {} Expected one of: {}.",
9138 hint,
9139 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
9140 )
9141 };
9142 return Err(ParseError {
9143 message: format!(
9144 "axonendpoint '{}' query param '{}' has \
9145 unsupported type '{}'.{} (Fase 37.y D2).",
9146 node.name, field_name, type_expr.name,
9147 hint_text,
9148 ),
9149 line: type_expr.loc.line,
9150 column: type_expr.loc.column,
9151 ..Default::default()
9152 });
9153 }
9154 node.query_params.push(TypeField {
9155 name: field_name,
9156 type_expr,
9157 loc: Loc {
9158 line: name_tok.line,
9159 column: name_tok.column,
9160 },
9161 });
9162 if self.check(TokenType::Comma) {
9168 self.advance();
9169 }
9170 let _ = block_line; }
9172 self.consume(TokenType::RBrace)?;
9173 },
9174 "execute" => node.execute_flow = self.consume_any_ident_or_kw()?.value.clone(),
9175 "output" => {
9176 node.output_type = self.parse_output_type_string()?;
9194 }
9195 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
9196 "cors" => node.cors_ref = self.consume_any_ident_or_kw()?.value.clone(),
9198 "retries" => node.retries = self.parse_optional_int(),
9199 "timeout" => {
9200 let t = self.current().clone();
9201 self.advance();
9202 node.timeout = t.value.clone();
9203 }
9204 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
9205 "replay" => {
9206 let value_tok = self.consume(TokenType::Bool)?;
9213 node.replay = value_tok.value.eq_ignore_ascii_case("true");
9214 node.replay_explicit = true;
9215 }
9216 "public" => {
9222 let value_tok = self.consume(TokenType::Bool)?;
9223 node.public = value_tok.value.eq_ignore_ascii_case("true");
9224 }
9225 "requires" => {
9226 let bracket_tok = self.current().clone();
9233 let items = self.parse_bracketed_dot_identifiers()?;
9234 for slug in &items {
9235 if !is_valid_capability_slug(slug) {
9236 return Err(ParseError {
9237 message: format!(
9238 "Invalid capability slug '{slug}' in axonendpoint '{}' \
9239 `requires:`. Capability slugs must match \
9240 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
9241 lowercase identifiers starting with a letter. Examples: \
9242 `admin`, `legal.read`, `hipaa.phi.read`.",
9243 node.name
9244 ),
9245 line: bracket_tok.line,
9246 column: bracket_tok.column,
9247 ..Default::default()
9248 });
9249 }
9250 }
9251 node.requires_capabilities = items;
9252 }
9253 "transport" => {
9257 let value_tok = self.consume_any_ident_or_kw()?;
9258 let value = &value_tok.value;
9259 if !axonendpoint_is_valid_transport(value) {
9260 let hint = crate::smart_suggest::suggest_for(
9261 value,
9262 AXONENDPOINT_TRANSPORT_VALUES,
9263 );
9264 let base = format!(
9265 "Invalid transport '{}' in axonendpoint '{}'.",
9266 value, node.name
9267 );
9268 let message = if hint.is_empty() {
9269 format!("{base} expected json | sse | ndjson, found {value}")
9270 } else {
9271 format!(
9272 "{base} {hint} (expected json | sse | ndjson, found {value})"
9273 )
9274 };
9275 return Err(ParseError {
9276 message,
9277 line: value_tok.line,
9278 column: value_tok.column,
9279 ..Default::default()
9280 });
9281 }
9282 node.transport = value.clone();
9283 node.transport_explicit = true;
9288 if self.check(TokenType::LParen) {
9295 if value != "sse" {
9296 let tok = self.current().clone();
9297 return Err(ParseError {
9298 message: format!(
9299 "Dialect parametrization \
9300 `transport: {value}(<dialect>)` is \
9301 only valid for `sse`; got \
9302 `{value}` in axonendpoint '{}'.",
9303 node.name
9304 ),
9305 line: tok.line,
9306 column: tok.column,
9307 ..Default::default()
9308 });
9309 }
9310 self.advance(); let dialect_tok = self.consume_any_ident_or_kw()?;
9312 let dialect = dialect_tok.value.clone();
9313 if !AXONENDPOINT_TRANSPORT_DIALECTS
9314 .iter()
9315 .any(|&d| d == dialect)
9316 {
9317 let hint = crate::smart_suggest::suggest_for(
9318 &dialect,
9319 AXONENDPOINT_TRANSPORT_DIALECTS,
9320 );
9321 let base = format!(
9322 "Invalid SSE dialect '{dialect}' in axonendpoint '{}'.",
9323 node.name
9324 );
9325 let message = if hint.is_empty() {
9326 format!(
9327 "{base} expected axon | openai | kimi | glm | anthropic, found {dialect}"
9328 )
9329 } else {
9330 format!(
9331 "{base} {hint} (expected axon | openai | kimi | glm | anthropic, found {dialect})"
9332 )
9333 };
9334 return Err(ParseError {
9335 message,
9336 line: dialect_tok.line,
9337 column: dialect_tok.column,
9338 ..Default::default()
9339 });
9340 }
9341 let rparen_tok = self.current().clone();
9343 if !self.check(TokenType::RParen) {
9344 return Err(ParseError {
9345 message: format!(
9346 "Expected `)` after dialect name \
9347 in axonendpoint '{}' \
9348 (transport: sse(<dialect>) grammar).",
9349 node.name
9350 ),
9351 line: rparen_tok.line,
9352 column: rparen_tok.column,
9353 ..Default::default()
9354 });
9355 }
9356 self.advance(); node.transport_dialect = dialect;
9358 }
9359 }
9360 "keepalive" => {
9361 let value_tok = self.current().clone();
9365 self.advance();
9366 let value = &value_tok.value;
9367 if !axonendpoint_is_valid_keepalive(value) {
9368 let hint = crate::smart_suggest::suggest_for(
9369 value,
9370 AXONENDPOINT_KEEPALIVE_VALUES,
9371 );
9372 let base = format!(
9373 "Invalid keepalive '{}' in axonendpoint '{}'.",
9374 value, node.name
9375 );
9376 let message = if hint.is_empty() {
9377 format!("{base} expected 5s | 15s | 30s | 60s, found {value}")
9378 } else {
9379 format!(
9380 "{base} {hint} (expected 5s | 15s | 30s | 60s, found {value})"
9381 )
9382 };
9383 return Err(ParseError {
9384 message,
9385 line: value_tok.line,
9386 column: value_tok.column,
9387 ..Default::default()
9388 });
9389 }
9390 node.keepalive = value.clone();
9391 }
9392 "backend" => {
9393 let value_tok = self.consume_any_ident_or_kw()?;
9401 let value = &value_tok.value;
9402 if !axonendpoint_is_valid_backend(value) {
9403 let hint = crate::smart_suggest::suggest_for(
9404 value,
9405 AXONENDPOINT_BACKEND_VALUES,
9406 );
9407 let expected = AXONENDPOINT_BACKEND_VALUES.join(" | ");
9408 let base = format!(
9409 "Invalid backend '{}' in axonendpoint '{}'.",
9410 value, node.name
9411 );
9412 let message = if hint.is_empty() {
9413 format!("{base} expected {expected}, found {value}")
9414 } else {
9415 format!(
9416 "{base} {hint} (expected {expected}, found {value})"
9417 )
9418 };
9419 return Err(ParseError {
9420 message,
9421 line: value_tok.line,
9422 column: value_tok.column,
9423 ..Default::default()
9424 });
9425 }
9426 node.backend = value.clone();
9427 }
9428 _ => self.skip_value(),
9429 }
9430 } else if self.check(TokenType::LBrace) {
9431 self.skip_braced_block()?;
9432 }
9433 }
9434 self.consume(TokenType::RBrace)?;
9435 Ok(node)
9436 }
9437
9438 fn parse_optional_int(&mut self) -> Option<i64> {
9441 let tok = self.current().clone();
9442 match tok.ttype {
9443 TokenType::Integer => {
9444 self.advance();
9445 tok.value.parse::<i64>().ok()
9446 }
9447 _ => {
9448 self.advance();
9449 None
9450 }
9451 }
9452 }
9453
9454 fn parse_optional_float(&mut self) -> Option<f64> {
9455 let tok = self.current().clone();
9456 match tok.ttype {
9457 TokenType::Float | TokenType::Integer => {
9458 self.advance();
9459 tok.value.parse::<f64>().ok()
9460 }
9461 _ => {
9462 self.advance();
9463 None
9464 }
9465 }
9466 }
9467
9468 fn parse_lambda_data(&mut self) -> Result<LambdaDataDefinition, ParseError> {
9471 let tok = self.consume(TokenType::Lambda)?;
9472 let name = self.consume(TokenType::Identifier)?;
9473 self.consume(TokenType::LBrace)?;
9474
9475 let mut node = LambdaDataDefinition {
9476 name: name.value.clone(),
9477 ontology: String::new(),
9478 certainty: 1.0,
9479 temporal_frame_start: String::new(),
9480 temporal_frame_end: String::new(),
9481 provenance: String::new(),
9482 derivation: String::new(),
9483 loc: Loc {
9484 line: tok.line,
9485 column: tok.column,
9486 },
9487 leading_trivia: Vec::new(),
9488 trailing_trivia: Vec::new(),
9489 };
9490
9491 while !self.check(TokenType::RBrace) {
9492 let field = self.current().clone();
9493 match field.ttype {
9494 TokenType::Ontology => {
9495 self.advance();
9496 self.consume(TokenType::Colon)?;
9497 node.ontology = self.consume(TokenType::StringLit)?.value.clone();
9498 }
9499 TokenType::Certainty => {
9500 self.advance();
9501 self.consume(TokenType::Colon)?;
9502 let val = self.current().clone();
9503 match val.ttype {
9504 TokenType::Float => {
9505 self.advance();
9506 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
9507 }
9508 TokenType::Integer => {
9509 self.advance();
9510 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
9511 }
9512 _ => {
9513 return Err(ParseError {
9514 message: format!(
9515 "Expected number for certainty, got '{}'",
9516 val.value
9517 ),
9518 line: val.line,
9519 column: val.column,
9520 ..Default::default()
9521 });
9522 }
9523 }
9524 }
9525 TokenType::TemporalFrame => {
9526 self.advance();
9527 self.consume(TokenType::Colon)?;
9528 node.temporal_frame_start = self.consume(TokenType::StringLit)?.value.clone();
9529 if self.check(TokenType::StringLit) {
9531 node.temporal_frame_end = self.consume(TokenType::StringLit)?.value.clone();
9532 }
9533 }
9534 TokenType::Provenance => {
9535 self.advance();
9536 self.consume(TokenType::Colon)?;
9537 node.provenance = self.consume(TokenType::StringLit)?.value.clone();
9538 }
9539 TokenType::Derivation => {
9540 self.advance();
9541 self.consume(TokenType::Colon)?;
9542 let d = self.current().clone();
9543 self.advance();
9544 node.derivation = d.value.clone();
9545 }
9546 _ => {
9547 self.advance();
9549 if self.check(TokenType::Colon) {
9550 self.advance();
9551 self.skip_value();
9552 }
9553 }
9554 }
9555 }
9556
9557 self.consume(TokenType::RBrace)?;
9558 Ok(node)
9559 }
9560
9561 fn parse_lambda_data_apply(&mut self) -> Result<LambdaDataApplyNode, ParseError> {
9562 let tok = self.consume(TokenType::Lambda)?;
9563 let lambda_name = self.consume(TokenType::Identifier)?;
9564
9565 let on_tok = self.current().clone();
9567 self.advance();
9568 if on_tok.value != "on" {
9569 return Err(ParseError {
9570 message: format!(
9571 "Expected 'on' after lambda data name in flow step, got '{}'",
9572 on_tok.value
9573 ),
9574 line: on_tok.line,
9575 column: on_tok.column,
9576 ..Default::default()
9577 });
9578 }
9579
9580 let target = self.current().clone();
9581 self.advance();
9582
9583 let mut output_type = String::new();
9584 if self.check(TokenType::Arrow) {
9585 self.advance();
9586 output_type = self.consume(TokenType::Identifier)?.value.clone();
9587 }
9588
9589 Ok(LambdaDataApplyNode {
9590 lambda_data_name: lambda_name.value.clone(),
9591 target: target.value.clone(),
9592 output_type,
9593 loc: Loc {
9594 line: tok.line,
9595 column: tok.column,
9596 },
9597 })
9598 }
9599
9600 fn parse_generic_declaration(&mut self) -> Result<Declaration, ParseError> {
9603 let kw_tok = self.current().clone();
9604 self.advance(); let name = if self.current().ttype == TokenType::Identifier {
9608 let n = self.current().value.clone();
9609 self.advance();
9610 n
9611 } else if !self.check(TokenType::LBrace)
9612 && !self.check(TokenType::LParen)
9613 && !self.check(TokenType::Eof)
9614 && self
9615 .current()
9616 .value
9617 .chars()
9618 .all(|c| c.is_alphanumeric() || c == '_')
9619 {
9620 let n = self.current().value.clone();
9621 self.advance();
9622 n
9623 } else {
9624 String::new()
9625 };
9626
9627 if self.check(TokenType::LParen) {
9629 self.advance();
9630 let mut depth = 1u32;
9631 while depth > 0 && !self.check(TokenType::Eof) {
9632 if self.check(TokenType::LParen) {
9633 depth += 1;
9634 } else if self.check(TokenType::RParen) {
9635 depth -= 1;
9636 }
9637 self.advance();
9638 }
9639 }
9640
9641 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
9643 if self.check(TokenType::Eof) {
9644 break;
9645 }
9646 self.advance();
9647 }
9648
9649 if self.check(TokenType::LBrace) {
9651 self.skip_braced_block()?;
9652 }
9653
9654 Ok(Declaration::Generic(GenericDeclaration {
9655 keyword: kw_tok.value,
9656 name,
9657 loc: Loc {
9658 line: kw_tok.line,
9659 column: kw_tok.column,
9660 },
9661 leading_trivia: Vec::new(),
9662 trailing_trivia: Vec::new(),
9663 }))
9664 }
9665
9666 fn parse_channel(&mut self) -> Result<ChannelDefinition, ParseError> {
9674 let tok = self.consume(TokenType::Channel)?;
9675 let name = self.consume(TokenType::Identifier)?.value;
9676 let mut node = ChannelDefinition {
9677 name: name.clone(),
9678 message: String::new(),
9679 qos: "at_least_once".to_string(),
9680 lifetime: "affine".to_string(),
9681 persistence: "ephemeral".to_string(),
9682 shield_ref: String::new(),
9683 loc: Loc {
9684 line: tok.line,
9685 column: tok.column,
9686 },
9687 leading_trivia: Vec::new(),
9688 trailing_trivia: Vec::new(),
9689 };
9690 self.consume(TokenType::LBrace)?;
9691 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9692 let field_tok = self.current().clone();
9693 let field_name = field_tok.value.clone();
9694 self.advance();
9695 if !self.check(TokenType::Colon) {
9696 if self.check(TokenType::LBrace) {
9697 self.skip_braced_block()?;
9698 }
9699 continue;
9700 }
9701 self.advance();
9702 match field_name.as_str() {
9703 "message" => node.message = self.parse_channel_message_type()?,
9704 "qos" => {
9705 let q_tok = self.consume_any_ident_or_kw()?;
9706 if !matches!(
9707 q_tok.value.as_str(),
9708 "at_most_once" | "at_least_once" | "exactly_once" | "broadcast" | "queue"
9709 ) {
9710 return Err(ParseError {
9711 message: format!(
9712 "Invalid qos '{}' in channel '{}' — \
9713 expected at_most_once | at_least_once | \
9714 exactly_once | broadcast | queue",
9715 q_tok.value, name
9716 ),
9717 line: q_tok.line,
9718 column: q_tok.column,
9719 ..Default::default()
9720 });
9721 }
9722 node.qos = q_tok.value;
9723 }
9724 "lifetime" => {
9725 let lt_tok = self.consume_any_ident_or_kw()?;
9726 if !matches!(lt_tok.value.as_str(), "linear" | "affine" | "persistent") {
9727 return Err(ParseError {
9728 message: format!(
9729 "Invalid lifetime '{}' in channel '{}' — \
9730 expected linear | affine | persistent",
9731 lt_tok.value, name
9732 ),
9733 line: lt_tok.line,
9734 column: lt_tok.column,
9735 ..Default::default()
9736 });
9737 }
9738 node.lifetime = lt_tok.value;
9739 }
9740 "persistence" => {
9741 let p_tok = self.consume_any_ident_or_kw()?;
9742 if !matches!(p_tok.value.as_str(), "ephemeral" | "persistent_axonstore") {
9743 return Err(ParseError {
9744 message: format!(
9745 "Invalid persistence '{}' in channel '{}' — \
9746 expected ephemeral | persistent_axonstore",
9747 p_tok.value, name
9748 ),
9749 line: p_tok.line,
9750 column: p_tok.column,
9751 ..Default::default()
9752 });
9753 }
9754 node.persistence = p_tok.value;
9755 }
9756 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
9757 _ => self.skip_value(),
9758 }
9759 }
9760 self.consume(TokenType::RBrace)?;
9761 Ok(node)
9762 }
9763
9764 fn parse_channel_message_type(&mut self) -> Result<String, ParseError> {
9767 let head = self.consume(TokenType::Identifier)?;
9768 let mut spelling = head.value;
9769 if self.check(TokenType::Lt) {
9770 self.advance();
9771 let inner = self.parse_channel_message_type()?;
9772 self.consume(TokenType::Gt)?;
9773 spelling = format!("{}<{}>", spelling, inner);
9774 }
9775 Ok(spelling)
9776 }
9777
9778 fn parse_emit_step(&mut self) -> Result<FlowStep, ParseError> {
9784 let tok = self.consume(TokenType::Emit)?;
9785 let channel = self.consume(TokenType::Identifier)?.value;
9786 self.consume(TokenType::LParen)?;
9787 let value = self.parse_emit_value_ref()?;
9788 self.consume(TokenType::RParen)?;
9789 Ok(FlowStep::Emit(EmitStatement {
9790 channel_ref: channel,
9791 value_ref: value,
9792 loc: Loc {
9793 line: tok.line,
9794 column: tok.column,
9795 },
9796 }))
9797 }
9798
9799 fn parse_mint_step(&mut self) -> Result<FlowStep, ParseError> {
9805 let tok = self.consume(TokenType::Mint)?;
9806 let credential_ref = self.consume(TokenType::Identifier)?.value;
9807 self.consume(TokenType::As)?;
9808 let binding = self.consume(TokenType::Identifier)?.value;
9809 Ok(FlowStep::Mint(MintStep {
9810 credential_ref,
9811 binding,
9812 loc: Loc {
9813 line: tok.line,
9814 column: tok.column,
9815 },
9816 }))
9817 }
9818
9819 fn parse_rotate_step(&mut self) -> Result<FlowStep, ParseError> {
9834 let tok = self.consume(TokenType::Rotate)?;
9835 let store_ref = self.consume(TokenType::Identifier)?.value;
9836 let mut where_expr = String::new();
9837 if self.check(TokenType::Where) {
9838 self.advance();
9839 where_expr = self.consume(TokenType::StringLit)?.value.clone();
9840 }
9841 let with_tok = self.current().clone();
9842 if with_tok.value != "with" {
9843 return Err(ParseError {
9844 message: format!(
9845 "Expected `with <Tool>` after `rotate {store_ref}{}`, found '{}'. \
9846 A rotation names the tool that performs the renewal exchange: \
9847 `rotate {store_ref} [where \"<filter>\"] with <Tool> as <binding>`.",
9848 if where_expr.is_empty() { "" } else { " where …" },
9849 with_tok.value
9850 ),
9851 line: with_tok.line,
9852 column: with_tok.column,
9853 ..Default::default()
9854 });
9855 }
9856 self.advance();
9857 let tool_ref = self.consume(TokenType::Identifier)?.value;
9858 self.consume(TokenType::As)?;
9859 let binding = self.consume(TokenType::Identifier)?.value;
9860 Ok(FlowStep::Rotate(RotateStep {
9861 store_ref,
9862 where_expr,
9863 tool_ref,
9864 binding,
9865 loc: Loc {
9866 line: tok.line,
9867 column: tok.column,
9868 },
9869 }))
9870 }
9871
9872 fn parse_emit_value_ref(&mut self) -> Result<String, ParseError> {
9888 let head = self.consume(TokenType::Identifier)?.value;
9889 let mut parts = vec![head];
9890 while self.check(TokenType::Dot) {
9891 self.advance(); let next_tok = self.current().clone();
9893 let valid = !next_tok.value.is_empty()
9894 && next_tok.value.as_bytes()[0].is_ascii_alphabetic()
9895 || next_tok.value.starts_with('_');
9896 if !valid {
9897 return Err(ParseError {
9898 message: format!(
9899 "Expected identifier or keyword after '.' in dotted \
9900 access, found {:?}",
9901 next_tok.value
9902 ),
9903 line: next_tok.line,
9904 column: next_tok.column,
9905 ..Default::default()
9906 });
9907 }
9908 self.advance();
9909 parts.push(next_tok.value);
9910 }
9911 Ok(parts.join("."))
9912 }
9913
9914 fn parse_publish_step(&mut self) -> Result<FlowStep, ParseError> {
9916 let tok = self.consume(TokenType::Publish)?;
9917 let channel = self.consume(TokenType::Identifier)?.value;
9918 self.consume(TokenType::Within)?;
9919 let shield = self.consume(TokenType::Identifier)?.value;
9920 Ok(FlowStep::Publish(PublishStatement {
9921 channel_ref: channel,
9922 shield_ref: shield,
9923 loc: Loc {
9924 line: tok.line,
9925 column: tok.column,
9926 },
9927 }))
9928 }
9929
9930 fn parse_discover_step(&mut self) -> Result<FlowStep, ParseError> {
9932 let tok = self.consume(TokenType::Discover)?;
9933 let cap = self.consume(TokenType::Identifier)?.value;
9934 self.consume(TokenType::As)?;
9935 let alias = self.consume(TokenType::Identifier)?.value;
9936 Ok(FlowStep::Discover(DiscoverStatement {
9937 capability_ref: cap,
9938 alias,
9939 loc: Loc {
9940 line: tok.line,
9941 column: tok.column,
9942 },
9943 }))
9944 }
9945}
9946
9947#[cfg(test)]
9950mod fase13_parser_tests {
9951 use super::*;
9952 use crate::lexer::Lexer;
9953
9954 fn parse(src: &str) -> Result<Program, ParseError> {
9955 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
9956 Parser::new(tokens).parse()
9957 }
9958
9959 #[test]
9960 fn channel_full_parses() {
9961 let src = r#"channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }"#;
9962 let prog = parse(src).expect("parse");
9963 match &prog.declarations[0] {
9964 Declaration::Channel(c) => {
9965 assert_eq!(c.name, "C");
9966 assert_eq!(c.message, "Order");
9967 assert_eq!(c.qos, "at_least_once");
9968 assert_eq!(c.lifetime, "affine");
9969 assert_eq!(c.persistence, "ephemeral");
9970 assert_eq!(c.shield_ref, "Gate");
9971 }
9972 _ => panic!("expected ChannelDefinition"),
9973 }
9974 }
9975
9976 #[test]
9977 fn channel_defaults_match_paper_d1() {
9978 let prog = parse("channel C { message: Order }").expect("parse");
9979 if let Declaration::Channel(c) = &prog.declarations[0] {
9980 assert_eq!(c.qos, "at_least_once"); assert_eq!(c.lifetime, "affine"); assert_eq!(c.persistence, "ephemeral");
9983 assert_eq!(c.shield_ref, "");
9984 } else {
9985 panic!("expected ChannelDefinition");
9986 }
9987 }
9988
9989 #[test]
9990 fn channel_second_order_message_type_parses() {
9991 let prog = parse("channel C { message: Channel<Order> }").expect("parse");
9992 if let Declaration::Channel(c) = &prog.declarations[0] {
9993 assert_eq!(c.message, "Channel<Order>");
9994 } else {
9995 panic!("expected ChannelDefinition");
9996 }
9997 }
9998
9999 #[test]
10000 fn channel_nested_channel_message_type_parses() {
10001 let prog = parse("channel C { message: Channel<Channel<Order>> }").expect("parse");
10002 if let Declaration::Channel(c) = &prog.declarations[0] {
10003 assert_eq!(c.message, "Channel<Channel<Order>>");
10004 } else {
10005 panic!("expected ChannelDefinition");
10006 }
10007 }
10008
10009 #[test]
10010 fn channel_invalid_qos_rejected() {
10011 let err = parse("channel C { message: T qos: bogus }").unwrap_err();
10012 assert!(err.message.contains("Invalid qos"), "got {}", err.message);
10013 }
10014
10015 #[test]
10016 fn channel_invalid_lifetime_rejected() {
10017 let err = parse("channel C { message: T lifetime: eternal }").unwrap_err();
10018 assert!(
10019 err.message.contains("Invalid lifetime"),
10020 "got {}",
10021 err.message
10022 );
10023 }
10024
10025 #[test]
10026 fn channel_invalid_persistence_rejected() {
10027 let err = parse("channel C { message: T persistence: forever }").unwrap_err();
10028 assert!(
10029 err.message.contains("Invalid persistence"),
10030 "got {}",
10031 err.message
10032 );
10033 }
10034
10035 #[test]
10036 fn emit_value_parses() {
10037 let src = "flow f() -> Out { emit C(payload) }";
10038 let prog = parse(src).expect("parse");
10039 if let Declaration::Flow(f) = &prog.declarations[0] {
10040 match &f.body[0] {
10041 FlowStep::Emit(e) => {
10042 assert_eq!(e.channel_ref, "C");
10043 assert_eq!(e.value_ref, "payload");
10044 }
10045 other => panic!("expected Emit, got {:?}", other),
10046 }
10047 } else {
10048 panic!("expected Flow");
10049 }
10050 }
10051
10052 #[test]
10053 fn publish_within_shield_parses() {
10054 let src = "flow f() -> Cap { publish C within Gate }";
10055 let prog = parse(src).expect("parse");
10056 if let Declaration::Flow(f) = &prog.declarations[0] {
10057 match &f.body[0] {
10058 FlowStep::Publish(p) => {
10059 assert_eq!(p.channel_ref, "C");
10060 assert_eq!(p.shield_ref, "Gate");
10061 }
10062 other => panic!("expected Publish, got {:?}", other),
10063 }
10064 } else {
10065 panic!("expected Flow");
10066 }
10067 }
10068
10069 #[test]
10070 fn discover_with_alias_parses() {
10071 let src = "flow f() -> Out { discover C as ch }";
10072 let prog = parse(src).expect("parse");
10073 if let Declaration::Flow(f) = &prog.declarations[0] {
10074 match &f.body[0] {
10075 FlowStep::Discover(d) => {
10076 assert_eq!(d.capability_ref, "C");
10077 assert_eq!(d.alias, "ch");
10078 }
10079 other => panic!("expected Discover, got {:?}", other),
10080 }
10081 } else {
10082 panic!("expected Flow");
10083 }
10084 }
10085
10086 #[test]
10087 fn listen_typed_ref_sets_flag_true() {
10088 let src = "daemon D() { goal: \"x\" listen C as ev { } }";
10089 let prog = parse(src).expect("parse");
10090 if let Declaration::Daemon(d) = &prog.declarations[0] {
10091 assert_eq!(d.listeners.len(), 1);
10092 assert_eq!(d.listeners[0].channel, "C");
10093 assert!(d.listeners[0].channel_is_ref, "typed ref ⇒ true");
10094 } else {
10095 panic!("expected Daemon");
10096 }
10097 }
10098
10099 #[test]
10100 fn listen_string_topic_legacy_flag_false() {
10101 let src = "daemon D() { goal: \"x\" listen \"orders\" as ev { } }";
10102 let prog = parse(src).expect("parse");
10103 if let Declaration::Daemon(d) = &prog.declarations[0] {
10104 assert_eq!(d.listeners.len(), 1);
10105 assert_eq!(d.listeners[0].channel, "orders");
10106 assert!(!d.listeners[0].channel_is_ref, "string topic ⇒ false");
10107 } else {
10108 panic!("expected Daemon");
10109 }
10110 }
10111
10112 fn extract_first_emit(prog: &Program) -> &EmitStatement {
10115 if let Declaration::Flow(f) = &prog.declarations[0] {
10116 if let FlowStep::Emit(e) = &f.body[0] {
10117 return e;
10118 }
10119 }
10120 panic!("expected emit statement at flow body[0]");
10121 }
10122
10123 #[test]
10124 fn emit_accepts_bare_identifier_value_ref() {
10125 let prog = parse("flow f() -> Out { emit Hello(payload) }").expect("parse");
10127 let emit = extract_first_emit(&prog);
10128 assert_eq!(emit.channel_ref, "Hello");
10129 assert_eq!(emit.value_ref, "payload");
10130 }
10131
10132 #[test]
10133 fn emit_accepts_two_segment_dotted_value_ref() {
10134 let prog = parse("flow f() -> Out { emit Hello(Build.output) }").expect("parse");
10136 let emit = extract_first_emit(&prog);
10137 assert_eq!(emit.value_ref, "Build.output");
10138 }
10139
10140 #[test]
10141 fn emit_accepts_three_segment_nested_dotted_value_ref() {
10142 let prog = parse("flow f() -> Out { emit Score(Analyze.result.score) }").expect("parse");
10143 let emit = extract_first_emit(&prog);
10144 assert_eq!(emit.value_ref, "Analyze.result.score");
10145 }
10146
10147 #[test]
10148 fn emit_dotted_with_trailing_dot_fails() {
10149 let result = parse("flow f() -> Out { emit Hello(Build.) }");
10151 assert!(result.is_err(), "expected parse error for trailing dot");
10152 }
10153}
10154
10155#[cfg(test)]
10158mod fase14a_declaration_trivia_tests {
10159 use super::*;
10160 use crate::lexer::Lexer;
10161 use crate::tokens::TriviaKind;
10162
10163 fn parse(src: &str) -> Program {
10164 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10165 Parser::new(toks).parse().expect("parse")
10166 }
10167
10168 #[test]
10169 fn no_comments_means_empty_trivia_per_decl() {
10170 let prog = parse("flow F() -> Out { }");
10171 assert_eq!(prog.declarations.len(), 1);
10172 assert_eq!(prog.declaration_trivia.len(), 1);
10173 assert!(prog.declaration_trivia[0].leading.is_empty());
10174 assert!(prog.declaration_trivia[0].trailing.is_empty());
10175 }
10176
10177 #[test]
10178 fn doc_line_comment_attaches_as_leading() {
10179 let prog = parse("/// Documents F\nflow F() -> Out { }");
10180 let triv = &prog.declaration_trivia[0];
10181 assert_eq!(triv.leading.len(), 1);
10182 assert_eq!(triv.leading[0].kind, TriviaKind::DocLine);
10183 assert!(triv.leading[0].is_doc());
10184 assert_eq!(triv.leading[0].text, "/// Documents F");
10185 }
10186
10187 #[test]
10188 fn regular_line_comment_attaches_as_leading() {
10189 let prog = parse("// header\nflow F() -> Out { }");
10190 let triv = &prog.declaration_trivia[0];
10191 assert_eq!(triv.leading.len(), 1);
10192 assert_eq!(triv.leading[0].kind, TriviaKind::Line);
10193 assert!(!triv.leading[0].is_doc());
10194 }
10195
10196 #[test]
10197 fn block_doc_comment_attaches_as_leading() {
10198 let prog = parse("/** Doc block */\nflow F() -> Out { }");
10199 let triv = &prog.declaration_trivia[0];
10200 assert_eq!(triv.leading[0].kind, TriviaKind::DocBlock);
10201 assert!(triv.leading[0].is_doc());
10202 }
10203
10204 #[test]
10205 fn multiple_comments_collected_in_source_order() {
10206 let src = "/// First\n/// Second\nflow F() -> Out { }";
10207 let prog = parse(src);
10208 let triv = &prog.declaration_trivia[0];
10209 assert_eq!(triv.leading.len(), 2);
10210 assert_eq!(triv.leading[0].text, "/// First");
10211 assert_eq!(triv.leading[1].text, "/// Second");
10212 }
10213
10214 #[test]
10215 fn three_decls_each_get_own_leading() {
10216 let src = "/// for A\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }\n/// for C\nflow C() -> Out { }";
10217 let prog = parse(src);
10218 assert_eq!(prog.declarations.len(), 3);
10219 assert_eq!(prog.declaration_trivia.len(), 3);
10220 for (idx, name) in ["A", "B", "C"].iter().enumerate() {
10221 let triv = &prog.declaration_trivia[idx];
10222 assert_eq!(triv.leading.len(), 1);
10223 assert_eq!(triv.leading[0].text, format!("/// for {name}"));
10224 }
10225 }
10226
10227 #[test]
10228 fn trailing_comment_attaches_to_last_token_of_decl() {
10229 let prog = parse("flow F() -> Out { } // tail");
10231 let triv = &prog.declaration_trivia[0];
10232 assert_eq!(triv.trailing.len(), 1);
10233 assert_eq!(triv.trailing[0].text, "// tail");
10234 }
10235
10236 #[test]
10237 fn mixed_doc_and_regular_preserve_order_between_decls() {
10238 let src = "/// doc for A\nflow A() -> Out { }\n\n// header line\n/// doc for B\nflow B() -> Out { }";
10239 let prog = parse(src);
10240 assert_eq!(prog.declarations.len(), 2);
10241 assert_eq!(prog.declaration_trivia[0].leading.len(), 1);
10243 assert_eq!(prog.declaration_trivia[1].leading.len(), 2);
10245 assert_eq!(prog.declaration_trivia[1].leading[0].text, "// header line");
10246 assert_eq!(prog.declaration_trivia[1].leading[1].text, "/// doc for B");
10247 }
10248
10249 #[test]
10250 fn parser_unaffected_by_comments_in_grammar_path() {
10251 let src =
10256 "// before flow\nflow /* between flow and name */ F() -> Out {\n // body comment\n}";
10257 let prog = parse(src);
10258 assert_eq!(prog.declarations.len(), 1);
10259 if let Declaration::Flow(f) = &prog.declarations[0] {
10260 assert_eq!(f.name, "F");
10261 } else {
10262 panic!("expected Flow declaration");
10263 }
10264 }
10265}
10266
10267#[cfg(test)]
10276mod fase14b_per_struct_trivia_tests {
10277 use super::*;
10278 use crate::lexer::Lexer;
10279 use crate::tokens::TriviaKind;
10280
10281 fn parse(src: &str) -> Program {
10282 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10283 Parser::new(toks).parse().expect("parse")
10284 }
10285
10286 #[test]
10287 fn flow_definition_carries_leading_trivia_directly() {
10288 let prog = parse("/// documents F\nflow F() -> Out { }");
10289 if let Declaration::Flow(f) = &prog.declarations[0] {
10290 assert_eq!(f.leading_trivia.len(), 1);
10291 assert_eq!(f.leading_trivia[0].kind, TriviaKind::DocLine);
10292 assert_eq!(f.leading_trivia[0].text, "/// documents F");
10293 assert!(f.trailing_trivia.is_empty());
10294 } else {
10295 panic!("expected Flow declaration");
10296 }
10297 }
10298
10299 #[test]
10300 fn flow_definition_carries_trailing_trivia_directly() {
10301 let prog = parse("flow F() -> Out { } // tail comment");
10302 if let Declaration::Flow(f) = &prog.declarations[0] {
10303 assert_eq!(f.trailing_trivia.len(), 1);
10304 assert_eq!(f.trailing_trivia[0].text, "// tail comment");
10305 } else {
10306 panic!("expected Flow declaration");
10307 }
10308 }
10309
10310 #[test]
10311 fn channel_definition_carries_trivia_directly() {
10312 let src = concat!(
10315 "/// inbound order events\n",
10316 "channel Orders {\n",
10317 " message: Order\n",
10318 " qos: at_least_once\n",
10319 " lifetime: affine\n",
10320 " persistence: ephemeral\n",
10321 " shield: Broker\n",
10322 "}",
10323 );
10324 let prog = parse(src);
10325 if let Declaration::Channel(ch) = &prog.declarations[0] {
10326 assert_eq!(ch.leading_trivia.len(), 1);
10327 assert!(ch.leading_trivia[0].is_doc());
10328 assert_eq!(ch.leading_trivia[0].text, "/// inbound order events");
10329 } else {
10330 panic!("expected Channel declaration");
10331 }
10332 }
10333
10334 #[test]
10335 fn per_struct_fields_match_side_channel() {
10336 let src = "/// for A\n// header for B\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }";
10339 let prog = parse(src);
10340 for (idx, decl) in prog.declarations.iter().enumerate() {
10341 let side = &prog.declaration_trivia[idx];
10342 let (per_lead, per_trail) = match decl {
10343 Declaration::Flow(f) => (&f.leading_trivia, &f.trailing_trivia),
10344 _ => panic!("unexpected variant"),
10345 };
10346 assert_eq!(per_lead.len(), side.leading.len());
10347 assert_eq!(per_trail.len(), side.trailing.len());
10348 for (a, b) in per_lead.iter().zip(side.leading.iter()) {
10349 assert_eq!(a.text, b.text);
10350 assert_eq!(a.kind, b.kind);
10351 }
10352 }
10353 }
10354
10355 #[test]
10356 fn comment_free_program_yields_empty_per_struct_fields() {
10357 let prog = parse("flow F() -> Out { }");
10358 if let Declaration::Flow(f) = &prog.declarations[0] {
10359 assert!(f.leading_trivia.is_empty());
10360 assert!(f.trailing_trivia.is_empty());
10361 } else {
10362 panic!("expected Flow declaration");
10363 }
10364 }
10365}
10366
10367#[cfg(test)]
10376mod fase14c_inner_doc_tests {
10377 use super::*;
10378 use crate::lexer::Lexer;
10379 use crate::tokens::TriviaKind;
10380
10381 fn parse(src: &str) -> Program {
10382 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10383 Parser::new(toks).parse().expect("parse")
10384 }
10385
10386 #[test]
10387 fn inner_doc_line_reaches_leading_trivia() {
10388 let src = "//! file-level docs\nflow F() -> Out { }";
10389 let prog = parse(src);
10390 let triv = &prog.declaration_trivia[0];
10391 assert_eq!(triv.leading.len(), 1);
10392 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocLine);
10393 assert!(triv.leading[0].is_doc());
10394 assert!(triv.leading[0].is_inner_doc());
10395 assert_eq!(triv.leading[0].text, "//! file-level docs");
10396 assert_eq!(triv.leading[0].stripped_text(), " file-level docs");
10397 }
10398
10399 #[test]
10400 fn inner_doc_block_reaches_leading_trivia() {
10401 let src = "/*! module-level docs */\nflow F() -> Out { }";
10402 let prog = parse(src);
10403 let triv = &prog.declaration_trivia[0];
10404 assert_eq!(triv.leading.len(), 1);
10405 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocBlock);
10406 assert!(triv.leading[0].is_inner_doc());
10407 assert_eq!(triv.leading[0].stripped_text(), " module-level docs ");
10408 }
10409
10410 #[test]
10411 fn outer_and_inner_doc_can_coexist() {
10412 let src = "//! file docs\n/// docs F\nflow F() -> Out { }";
10416 let prog = parse(src);
10417 let triv = &prog.declaration_trivia[0];
10418 assert_eq!(triv.leading.len(), 2);
10419 assert!(triv.leading[0].is_inner_doc());
10420 assert!(triv.leading[1].is_doc());
10421 assert!(!triv.leading[1].is_inner_doc());
10422 }
10423
10424 #[test]
10425 fn inner_doc_reaches_per_struct_fields() {
10426 let src = "//! intro\nflow F() -> Out { }";
10428 let prog = parse(src);
10429 if let Declaration::Flow(f) = &prog.declarations[0] {
10430 assert_eq!(f.leading_trivia.len(), 1);
10431 assert!(f.leading_trivia[0].is_inner_doc());
10432 } else {
10433 panic!("expected Flow declaration");
10434 }
10435 }
10436}
10437
10438#[cfg(test)]
10456mod fase28_recovery_tests {
10457 use super::*;
10458 use crate::lexer::Lexer;
10459
10460 fn lex(src: &str) -> Vec<Token> {
10463 Lexer::new(src, "<test>").tokenize().expect("lex")
10464 }
10465
10466 fn recover(src: &str) -> ParseResult {
10469 Parser::new(lex(src)).parse_with_recovery()
10470 }
10471
10472 fn strict(src: &str) -> Result<Program, ParseError> {
10474 Parser::new(lex(src)).parse()
10475 }
10476
10477 #[test]
10480 fn strict_parse_unchanged_for_clean_source() {
10481 let src = "intent I {}";
10484 let prog = strict(src).expect("clean parse");
10485 assert_eq!(prog.declarations.len(), 1);
10486 }
10487
10488 #[test]
10489 fn strict_parse_still_raises_on_first_error() {
10490 let src = "flow F() { } not_a_keyword flow G() { }";
10495 let _ = strict(src).expect_err("must error fast in strict mode");
10496 }
10497
10498 #[test]
10499 fn recovery_clean_source_yields_no_errors() {
10500 let src = "flow F() { } flow G() { }";
10501 let pr = recover(src);
10502 assert!(pr.is_clean(), "errors: {:?}", pr.errors);
10503 assert_eq!(pr.program.declarations.len(), 2);
10504 }
10505
10506 #[test]
10509 fn single_unknown_top_level_token_recovers() {
10510 let src = "garbage_token flow F() { } flow G() { }";
10512 let pr = recover(src);
10513 assert_eq!(pr.errors.len(), 1, "errors: {:?}", pr.errors);
10514 assert_eq!(pr.program.declarations.len(), 2);
10515 }
10516
10517 #[test]
10518 fn error_in_first_decl_does_not_block_second() {
10519 let src = "flow F() { not_a_step nope } flow G() { }";
10522 let pr = recover(src);
10523 assert!(pr.has_errors(), "expected at least one error");
10524 let names: Vec<&str> = pr
10526 .program
10527 .declarations
10528 .iter()
10529 .filter_map(|d| match d {
10530 Declaration::Flow(f) => Some(f.name.as_str()),
10531 _ => None,
10532 })
10533 .collect();
10534 assert!(names.contains(&"G"), "G not found among {names:?}");
10535 }
10536
10537 #[test]
10538 fn malformed_declaration_then_clean_intent_recovers() {
10539 let src = "flow @ () { } intent I {}";
10540 let pr = recover(src);
10541 assert!(pr.has_errors());
10542 let kinds: Vec<&str> = pr
10543 .program
10544 .declarations
10545 .iter()
10546 .map(|d| match d {
10547 Declaration::Intent(_) => "intent",
10548 Declaration::Flow(_) => "flow",
10549 _ => "other",
10550 })
10551 .collect();
10552 assert!(kinds.contains(&"intent"), "kinds: {kinds:?}");
10553 }
10554
10555 #[test]
10556 fn recovery_does_not_double_count_a_single_error() {
10557 let src = "flow F() { not_a_step }";
10565 let pr = recover(src);
10566 let outer_ghosts = pr
10567 .errors
10568 .iter()
10569 .filter(|e| e.message.contains("at top level"))
10570 .count();
10571 assert_eq!(outer_ghosts, 0, "ghost errors: {:?}", pr.errors);
10572 }
10573
10574 #[test]
10577 fn three_independent_errors_yield_three_entries() {
10578 let src =
10579 "garbage1 flow F() { } garbage2 flow G() { } garbage3 flow H() { }";
10580 let pr = recover(src);
10581 assert_eq!(pr.errors.len(), 3, "errors: {:?}", pr.errors);
10582 assert_eq!(pr.program.declarations.len(), 3);
10583 }
10584
10585 #[test]
10586 fn all_errors_no_valid_declarations() {
10587 let src = "foo bar baz qux";
10588 let pr = recover(src);
10589 assert!(pr.has_errors());
10590 assert!(pr.program.declarations.is_empty());
10591 }
10592
10593 #[test]
10594 fn errors_recorded_in_source_order() {
10595 let src = "x flow A() { } y flow B() { } z flow C() { }";
10596 let pr = recover(src);
10597 assert_eq!(pr.errors.len(), 3);
10598 let lines: Vec<u32> = pr.errors.iter().map(|e| e.line).collect();
10599 assert!(
10602 lines.windows(2).all(|w| w[0] <= w[1]),
10603 "errors out of order: {lines:?}"
10604 );
10605 }
10606
10607 #[test]
10610 fn sync_to_flow_keyword() {
10611 let src = "garbage flow F() { }";
10612 let pr = recover(src);
10613 assert_eq!(pr.program.declarations.len(), 1);
10614 }
10615
10616 #[test]
10617 fn sync_to_intent_keyword() {
10618 let src = "garbage intent I {}";
10619 let pr = recover(src);
10620 assert_eq!(pr.program.declarations.len(), 1);
10621 }
10622
10623 #[test]
10624 fn sync_to_persona_keyword() {
10625 let src = "garbage persona P { name: \"P\" role: \"R\" }";
10626 let pr = recover(src);
10627 assert!(
10628 pr.program
10629 .declarations
10630 .iter()
10631 .any(|d| matches!(d, Declaration::Persona(_))),
10632 "persona not recovered: decls = {:?}",
10633 pr.program.declarations.len()
10634 );
10635 }
10636
10637 #[test]
10638 fn sync_to_run_keyword() {
10639 let src = "garbage run R { input: { user_message: \"hi\" } }";
10640 let pr = recover(src);
10641 assert!(pr.has_errors());
10643 }
10644
10645 #[test]
10648 fn parse_result_has_errors_and_is_clean_invert() {
10649 let pr_clean = recover("flow F() { }");
10650 assert!(pr_clean.is_clean());
10651 assert!(!pr_clean.has_errors());
10652
10653 let pr_err = recover("garbage");
10654 assert!(!pr_err.is_clean());
10655 assert!(pr_err.has_errors());
10656 }
10657
10658 #[test]
10659 fn parse_result_program_field_holds_partial_program() {
10660 let pr = recover("garbage flow F() { }");
10661 assert!(!pr.program.declarations.is_empty());
10662 }
10663
10664 #[test]
10665 fn parse_result_errors_carry_line_and_column() {
10666 let pr = recover("garbage");
10667 assert!(!pr.errors.is_empty());
10668 let e = &pr.errors[0];
10669 assert!(e.line >= 1);
10670 let _ = e.column;
10673 assert!(!e.message.is_empty());
10674 }
10675
10676 #[test]
10677 fn parse_result_debug_renders() {
10678 let pr = recover("flow F() { }");
10679 let s = format!("{pr:?}");
10680 assert!(s.contains("ParseResult"));
10681 }
10682
10683 #[test]
10686 fn empty_source_is_clean() {
10687 let pr = recover("");
10688 assert!(pr.is_clean());
10689 assert!(pr.program.declarations.is_empty());
10690 }
10691
10692 #[test]
10693 fn whitespace_only_source_is_clean() {
10694 let pr = recover(" \n\n\t \n");
10695 assert!(pr.is_clean());
10696 assert!(pr.program.declarations.is_empty());
10697 }
10698
10699 #[test]
10700 fn only_garbage_does_not_crash() {
10701 let pr = recover("foo bar baz { qux quux } corge { grault }");
10703 assert!(pr.has_errors());
10704 }
10705
10706 #[test]
10707 fn unbalanced_close_brace_does_not_crash() {
10708 let pr = recover("} flow F() { }");
10709 let names: Vec<&str> = pr
10711 .program
10712 .declarations
10713 .iter()
10714 .filter_map(|d| match d {
10715 Declaration::Flow(f) => Some(f.name.as_str()),
10716 _ => None,
10717 })
10718 .collect();
10719 assert!(names.contains(&"F"), "F not recovered: {names:?}");
10720 }
10721
10722 #[test]
10723 fn error_at_eof_does_not_loop() {
10724 let pr = recover("flow F() { ");
10726 let _ = pr.errors.len();
10728 }
10729
10730 #[test]
10731 fn nested_braces_inside_error_still_balance() {
10732 let src = "flow F() { not_a_step { inner } } flow G() { }";
10735 let pr = recover(src);
10736 let names: Vec<&str> = pr
10737 .program
10738 .declarations
10739 .iter()
10740 .filter_map(|d| match d {
10741 Declaration::Flow(f) => Some(f.name.as_str()),
10742 _ => None,
10743 })
10744 .collect();
10745 assert!(names.contains(&"G"), "G not recovered: {names:?}");
10746 }
10747
10748 #[derive(Clone, Copy)]
10757 struct Xorshift(u64);
10758 impl Xorshift {
10759 fn next(&mut self) -> u64 {
10760 let mut x = self.0;
10761 x ^= x << 13;
10762 x ^= x >> 7;
10763 x ^= x << 17;
10764 self.0 = x;
10765 x
10766 }
10767 fn pick<T: Copy>(&mut self, slice: &[T]) -> T {
10768 slice[(self.next() as usize) % slice.len()]
10769 }
10770 }
10771
10772 fn mutate(src: &str, rng: &mut Xorshift) -> String {
10773 let mut bytes: Vec<u8> = src.bytes().collect();
10774 if bytes.is_empty() {
10775 return src.to_string();
10776 }
10777 let op = rng.next() % 4;
10778 let pos = (rng.next() as usize) % bytes.len();
10779 let safe: &[u8] = b"abcdefghijklmnopqrstuvwxyz {}();:,_0123456789";
10783 match op {
10784 0 => {
10785 bytes.remove(pos);
10786 }
10787 1 => {
10788 let b = rng.pick(safe);
10789 bytes.insert(pos, b);
10790 }
10791 2 if pos + 1 < bytes.len() => {
10792 bytes.swap(pos, pos + 1);
10793 }
10794 _ => {
10795 let b = rng.pick(safe);
10796 bytes[pos] = b;
10797 }
10798 }
10799 bytes.retain(|b| b.is_ascii());
10802 String::from_utf8_lossy(&bytes).into_owned()
10803 }
10804
10805 #[test]
10806 fn fuzz_recovery_never_crashes() {
10807 let seed_bases = [
10808 "flow F() { }",
10809 "intent I { }",
10810 "persona P { name: \"P\" role: \"R\" }",
10811 "intent J { ask: \"a\" }",
10812 "type T = String",
10813 ];
10814 for (bucket, base) in (0..100u64).zip(seed_bases.iter().cycle()) {
10816 let mut rng = Xorshift(0x1234_5678_9abc_def0_u64.wrapping_add(bucket));
10817 let mut current = (*base).to_string();
10818 for _ in 0..10 {
10819 current = mutate(¤t, &mut rng);
10820 let toks = match Lexer::new(¤t, "<fuzz>").tokenize() {
10823 Ok(t) => t,
10824 Err(_) => continue,
10825 };
10826 let _pr = Parser::new(toks).parse_with_recovery();
10828 }
10829 }
10830 }
10831
10832 #[test]
10835 fn missing_colon_hint_preserved_under_recovery() {
10836 let src = "flow F() { run R { input { user_message: \"hi\" } } }";
10840 let pr = recover(src);
10841 if !pr.errors.is_empty() {
10845 let any_msg = pr.errors.iter().any(|e| !e.message.is_empty());
10846 assert!(any_msg);
10847 }
10848 }
10849
10850 #[test]
10853 fn recovered_declarations_appear_in_source_order() {
10854 let src = "flow A() { } garbage flow B() { } garbage flow C() { }";
10855 let pr = recover(src);
10856 let names: Vec<&str> = pr
10857 .program
10858 .declarations
10859 .iter()
10860 .filter_map(|d| match d {
10861 Declaration::Flow(f) => Some(f.name.as_str()),
10862 _ => None,
10863 })
10864 .collect();
10865 assert_eq!(names, vec!["A", "B", "C"]);
10866 }
10867}
10868
10869#[cfg(test)]
10877mod fase28_source_context_tests {
10878 use super::*;
10879 use crate::lexer::Lexer;
10880
10881 fn snippet(source: &str, line: u32, column: u32, filename: &str) -> String {
10882 SourceSnippet::new(
10883 source.to_string(),
10884 line,
10885 column,
10886 filename.to_string(),
10887 )
10888 .render()
10889 }
10890
10891 #[test]
10894 fn rustc_style_block_for_middle_line() {
10895 let src = "line one\nline two\nline three\nline four\nline five";
10896 let out = snippet(src, 3, 6, "x.axon");
10897 assert!(out.contains("--> x.axon:3:6"));
10898 assert!(out.contains("1 | line one"));
10899 assert!(out.contains("2 | line two"));
10900 assert!(out.contains("3 | line three"));
10901 assert!(out.contains("4 | line four"));
10902 assert!(out.contains("5 | line five"));
10903 assert!(out.contains("\n | ^"), "out:\n{out}");
10905 }
10906
10907 #[test]
10908 fn caret_column_one_renders_correctly() {
10909 let out = snippet("abc\n", 1, 1, "<source>");
10910 assert!(out.contains("\n | ^"));
10911 }
10912
10913 #[test]
10914 fn first_line_clamps_context_before_to_zero() {
10915 let src = "first\nsecond\nthird\nfourth\nfifth";
10916 let out = snippet(src, 1, 1, "<source>");
10917 assert!(out.contains("1 | first"));
10918 assert!(out.contains("2 | second"));
10919 assert!(out.contains("3 | third"));
10920 assert!(!out.contains("4 | fourth"));
10921 }
10922
10923 #[test]
10924 fn last_line_clamps_context_after_to_eof() {
10925 let src = "first\nsecond\nthird\nfourth\nfifth";
10926 let out = snippet(src, 5, 2, "<source>");
10927 assert!(out.contains("5 | fifth"));
10928 assert!(out.contains("3 | third"));
10929 assert!(out.contains("4 | fourth"));
10930 assert!(!out.contains("2 | second"));
10931 }
10932
10933 #[test]
10934 fn gutter_width_grows_with_line_count() {
10935 let src: String = (1..=12).map(|i| format!("line{i}")).collect::<Vec<_>>().join("\n");
10936 let out = snippet(&src, 12, 1, "<source>");
10937 assert!(out.contains("12 | line12"));
10938 assert!(out.contains("10 | line10"));
10939 }
10940
10941 #[test]
10944 fn empty_source_returns_empty() {
10945 assert_eq!(snippet("", 1, 1, "<source>"), "");
10946 }
10947
10948 #[test]
10949 fn zero_line_returns_empty() {
10950 assert_eq!(snippet("hi", 0, 1, "<source>"), "");
10951 }
10952
10953 #[test]
10954 fn out_of_range_line_returns_empty() {
10955 assert_eq!(snippet("hi", 99, 1, "<source>"), "");
10956 }
10957
10958 #[test]
10959 fn caret_clamps_past_eol() {
10960 let out = snippet("hello", 1, 50, "<source>");
10961 assert!(out.contains("\n | ^"), "out:\n{out}");
10962 }
10963
10964 #[test]
10965 fn unicode_codepoint_count_for_caret_clamp() {
10966 let out = snippet("héllo", 1, 99, "<source>");
10968 assert!(out.contains("\n | ^"), "out:\n{out}");
10969 }
10970
10971 #[test]
10972 fn trailing_newline_does_not_create_phantom_last_line() {
10973 let out = snippet("first\nsecond\n", 2, 1, "<source>");
10974 assert!(!out.contains("3 |"));
10975 assert!(out.contains("2 | second"));
10976 }
10977
10978 fn lex(src: &str) -> Vec<Token> {
10981 Lexer::new(src, "<test>").tokenize().expect("lex")
10982 }
10983
10984 #[test]
10985 fn strict_parse_attaches_snippet_when_source_given() {
10986 let src = "garbage_token\nflow F() { }";
10987 let err = Parser::new(lex(src))
10988 .with_source(src, "x.axon")
10989 .parse()
10990 .expect_err("must error");
10991 assert!(err.source_snippet.is_some());
10992 let display = format!("{err}");
10993 assert!(display.contains("--> x.axon:"), "display: {display}");
10994 }
10995
10996 #[test]
10997 fn strict_parse_no_snippet_when_no_source() {
10998 let src = "garbage_token";
10999 let err = Parser::new(lex(src)).parse().expect_err("must error");
11000 assert!(err.source_snippet.is_none());
11001 let display = format!("{err}");
11002 assert!(!display.contains("\n -->"));
11003 }
11004
11005 #[test]
11006 fn every_recovered_error_has_snippet() {
11007 let src = "garbage1\nflow F() { }\ngarbage2\nflow G() { }";
11008 let result = Parser::new(lex(src))
11009 .with_source(src, "multi.axon")
11010 .parse_with_recovery();
11011 assert!(!result.errors.is_empty());
11012 for err in &result.errors {
11013 assert!(err.source_snippet.is_some());
11014 let display = format!("{err}");
11015 assert!(
11016 display.contains("--> multi.axon:"),
11017 "display: {display}"
11018 );
11019 }
11020 }
11021
11022 #[test]
11023 fn recovery_no_snippet_when_no_source() {
11024 let src = "garbage1 garbage2";
11025 let result = Parser::new(lex(src)).parse_with_recovery();
11026 for err in &result.errors {
11027 assert!(err.source_snippet.is_none());
11028 }
11029 }
11030
11031 #[test]
11032 fn snippet_points_at_correct_line_for_each_error() {
11033 let src = "garbage_a\nflow F() { }\ngarbage_b\nflow G() { }";
11034 let result = Parser::new(lex(src))
11035 .with_source(src, "x")
11036 .parse_with_recovery();
11037 for err in &result.errors {
11038 let sn = err.source_snippet.as_ref().expect("snippet");
11039 assert_eq!(sn.line, err.line);
11040 }
11041 }
11042
11043 #[test]
11046 fn legacy_constructor_still_works() {
11047 let src = "flow F() { }";
11048 let prog = Parser::new(lex(src)).parse().expect("clean");
11049 assert_eq!(prog.declarations.len(), 1);
11050 }
11051
11052 #[test]
11053 fn attach_source_idempotent() {
11054 let err = ParseError {
11055 message: "bad".to_string(),
11056 line: 2,
11057 column: 3,
11058 ..Default::default()
11059 };
11060 let err2 = err.clone().attach_source("a\nb\nc\n", "f.axon");
11061 let first = format!("{err2}");
11062 let err3 = err.attach_source("a\nb\nc\n", "f.axon");
11063 let second = format!("{err3}");
11064 assert_eq!(first, second);
11065 }
11066
11067 #[test]
11068 fn attach_source_noop_when_line_zero() {
11069 let err = ParseError {
11070 message: "bad".to_string(),
11071 line: 0,
11072 column: 0,
11073 ..Default::default()
11074 };
11075 let err = err.attach_source("a\nb\nc\n", "f.axon");
11076 assert!(err.source_snippet.is_none());
11077 }
11078
11079 #[test]
11085 fn golden_simple_three_line_block() {
11086 let src = "alpha\nbeta\ngamma";
11087 let out = snippet(src, 2, 3, "g.axon");
11088 let expected = concat!(
11092 " --> g.axon:2:3\n",
11093 " |\n",
11094 "1 | alpha\n",
11095 "2 | beta\n",
11096 " | ^\n",
11097 "3 | gamma",
11098 );
11099 assert_eq!(out, expected);
11100 }
11101
11102 #[test]
11103 fn golden_first_line_caret() {
11104 let src = "abc\ndef\n";
11105 let out = snippet(src, 1, 1, "x");
11106 let expected = concat!(
11107 " --> x:1:1\n",
11108 " |\n",
11109 "1 | abc\n",
11110 " | ^\n",
11111 "2 | def",
11112 );
11113 assert_eq!(out, expected);
11114 }
11115
11116 #[test]
11117 fn golden_two_digit_gutter() {
11118 let src: String = (1..=11)
11119 .map(|i| format!("L{i}"))
11120 .collect::<Vec<_>>()
11121 .join("\n");
11122 let out = snippet(&src, 10, 2, "big");
11123 let expected = concat!(
11124 " --> big:10:2\n",
11125 " |\n",
11126 " 8 | L8\n",
11127 " 9 | L9\n",
11128 "10 | L10\n",
11129 " | ^\n",
11130 "11 | L11",
11131 );
11132 assert_eq!(out, expected);
11133 }
11134}
11135
11136#[cfg(test)]
11143mod fase28_smart_suggest_parser_tests {
11144 use super::*;
11145 use crate::lexer::Lexer;
11146
11147 fn lex(src: &str) -> Vec<Token> {
11148 Lexer::new(src, "<test>").tokenize().expect("lex")
11149 }
11150
11151 #[test]
11152 fn top_level_typo_suggests_flow() {
11153 let src = "flwo F() { }";
11154 let err = Parser::new(lex(src)).parse().expect_err("must error");
11155 assert!(
11156 err.message.contains("Did you mean `flow`?"),
11157 "msg: {}",
11158 err.message
11159 );
11160 }
11161
11162 #[test]
11163 fn top_level_unknown_far_no_suggestion() {
11164 let src = "qwerty F() { }";
11165 let err = Parser::new(lex(src)).parse().expect_err("must error");
11166 assert!(
11167 !err.message.contains("Did you mean"),
11168 "msg: {}",
11169 err.message
11170 );
11171 }
11172
11173 #[test]
11174 fn flow_body_typo_suggests_step() {
11175 let src = "flow F() { stepp S {} }";
11176 let err = Parser::new(lex(src)).parse().expect_err("must error");
11177 assert!(
11178 err.message.contains("Did you mean `step`"),
11179 "msg: {}",
11180 err.message
11181 );
11182 }
11183
11184 #[test]
11185 fn flow_body_typo_suggests_reason() {
11186 let src = "flow F() { reasn R {} }";
11187 let err = Parser::new(lex(src)).parse().expect_err("must error");
11188 assert!(
11189 err.message.contains("Did you mean `reason`?"),
11190 "msg: {}",
11191 err.message
11192 );
11193 }
11194
11195 #[test]
11196 fn recovery_mode_carries_hint() {
11197 let src = "flwo F() { }";
11198 let result = Parser::new(lex(src)).parse_with_recovery();
11199 assert!(
11200 result
11201 .errors
11202 .iter()
11203 .any(|e| e.message.contains("Did you mean `flow`?")),
11204 "errors: {:?}",
11205 result.errors
11206 );
11207 }
11208}
11209
11210#[cfg(test)]
11213mod fase35m_mutate_purge_where_tests {
11214 use super::*;
11215
11216 fn parse(src: &str) -> Program {
11217 let tokens = crate::lexer::Lexer::new(src, "<test>")
11218 .tokenize()
11219 .expect("lex");
11220 Parser::new(tokens).parse().expect("parse")
11221 }
11222
11223 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11224 for d in &prog.declarations {
11225 if let Declaration::Flow(f) = d {
11226 if f.name == flow {
11227 return f.body.first().expect("flow has at least one step");
11228 }
11229 }
11230 }
11231 panic!("flow `{flow}` not found");
11232 }
11233
11234 #[test]
11235 fn mutate_captures_its_where_clause() {
11236 let prog =
11239 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
11240 match first_step(&prog, "F") {
11241 FlowStep::Mutate(m) => {
11242 assert_eq!(m.store_name, "accounts");
11243 assert_eq!(m.where_expr, "id = 1");
11244 }
11245 other => panic!("expected Mutate, got {other:?}"),
11246 }
11247 }
11248
11249 #[test]
11250 fn purge_captures_its_where_clause() {
11251 let prog =
11252 parse("flow F() -> Unit { purge logs { where: \"ts < 100\" } }");
11253 match first_step(&prog, "F") {
11254 FlowStep::Purge(p) => {
11255 assert_eq!(p.store_name, "logs");
11256 assert_eq!(p.where_expr, "ts < 100");
11257 }
11258 other => panic!("expected Purge, got {other:?}"),
11259 }
11260 }
11261
11262 #[test]
11263 fn mutate_without_a_where_block_is_a_whole_store_op() {
11264 let prog = parse("flow F() -> Unit { mutate accounts }");
11267 match first_step(&prog, "F") {
11268 FlowStep::Mutate(m) => {
11269 assert_eq!(m.store_name, "accounts");
11270 assert_eq!(m.where_expr, "");
11271 }
11272 other => panic!("expected Mutate, got {other:?}"),
11273 }
11274 }
11275}
11276
11277#[cfg(test)]
11280mod fase35o_persist_fields_tests {
11281 use super::*;
11282
11283 fn parse(src: &str) -> Program {
11284 let tokens = crate::lexer::Lexer::new(src, "<test>")
11285 .tokenize()
11286 .expect("lex");
11287 Parser::new(tokens).parse().expect("parse")
11288 }
11289
11290 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11291 for d in &prog.declarations {
11292 if let Declaration::Flow(f) = d {
11293 if f.name == flow {
11294 return f.body.first().expect("flow has at least one step");
11295 }
11296 }
11297 }
11298 panic!("flow `{flow}` not found");
11299 }
11300
11301 #[test]
11302 fn persist_captures_its_field_block() {
11303 let prog = parse(
11307 "flow F() -> Unit { persist into chat_history { \
11308 session_id: \"${session_id}\" sender: \"user\" \
11309 content: \"${message}\" } }",
11310 );
11311 match first_step(&prog, "F") {
11312 FlowStep::Persist(p) => {
11313 assert_eq!(p.store_name, "chat_history");
11314 assert_eq!(
11315 p.fields,
11316 vec![
11317 ("session_id".to_string(), "${session_id}".to_string()),
11318 ("sender".to_string(), "user".to_string()),
11319 ("content".to_string(), "${message}".to_string()),
11320 ]
11321 );
11322 }
11323 other => panic!("expected Persist, got {other:?}"),
11324 }
11325 }
11326
11327 #[test]
11328 fn persist_without_a_block_keeps_the_user_bindings_fallback() {
11329 let prog = parse("flow F() -> Unit { persist events }");
11332 match first_step(&prog, "F") {
11333 FlowStep::Persist(p) => {
11334 assert_eq!(p.store_name, "events");
11335 assert!(p.fields.is_empty());
11336 }
11337 other => panic!("expected Persist, got {other:?}"),
11338 }
11339 }
11340
11341 #[test]
11342 fn persist_accepts_the_optional_into_connector() {
11343 let with =
11346 parse("flow F() -> Unit { persist into accounts { id: \"1\" } }");
11347 let without =
11348 parse("flow F() -> Unit { persist accounts { id: \"1\" } }");
11349 for prog in [&with, &without] {
11350 match first_step(prog, "F") {
11351 FlowStep::Persist(p) => assert_eq!(p.store_name, "accounts"),
11352 other => panic!("expected Persist, got {other:?}"),
11353 }
11354 }
11355 }
11356
11357 #[test]
11358 fn persist_into_without_a_block_resolves_the_store_name() {
11359 let prog = parse("flow F() -> Unit { persist into events }");
11362 match first_step(&prog, "F") {
11363 FlowStep::Persist(p) => {
11364 assert_eq!(p.store_name, "events");
11365 assert!(p.fields.is_empty());
11366 }
11367 other => panic!("expected Persist, got {other:?}"),
11368 }
11369 }
11370
11371 #[test]
11372 fn persist_fields_lower_into_the_ir() {
11373 let prog = parse(
11376 "flow F() -> Unit { persist into chat { content: \"${msg}\" } }",
11377 );
11378 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
11379 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
11380 match flow.steps.first().expect("one step") {
11381 crate::ir_nodes::IRFlowNode::Persist(p) => {
11382 assert_eq!(p.store_name, "chat");
11383 assert_eq!(
11384 p.fields,
11385 vec![("content".to_string(), "${msg}".to_string())]
11386 );
11387 }
11388 other => panic!("expected IRFlowNode::Persist, got {other:?}"),
11389 }
11390 }
11391}
11392
11393#[cfg(test)]
11396mod fase35p_mutate_fields_tests {
11397 use super::*;
11398
11399 fn parse(src: &str) -> Program {
11400 let tokens = crate::lexer::Lexer::new(src, "<test>")
11401 .tokenize()
11402 .expect("lex");
11403 Parser::new(tokens).parse().expect("parse")
11404 }
11405
11406 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11407 for d in &prog.declarations {
11408 if let Declaration::Flow(f) = d {
11409 if f.name == flow {
11410 return f.body.first().expect("flow has at least one step");
11411 }
11412 }
11413 }
11414 panic!("flow `{flow}` not found");
11415 }
11416
11417 #[test]
11418 fn mutate_captures_its_set_field_block() {
11419 let prog = parse(
11423 "flow F() -> Unit { mutate accounts { where: \"id = ${id}\" \
11424 balance: \"${new_balance}\" status: \"active\" } }",
11425 );
11426 match first_step(&prog, "F") {
11427 FlowStep::Mutate(m) => {
11428 assert_eq!(m.store_name, "accounts");
11429 assert_eq!(m.where_expr, "id = ${id}");
11430 assert_eq!(
11431 m.fields,
11432 vec![
11433 ("balance".to_string(), "${new_balance}".to_string()),
11434 ("status".to_string(), "active".to_string()),
11435 ]
11436 );
11437 }
11438 other => panic!("expected Mutate, got {other:?}"),
11439 }
11440 }
11441
11442 #[test]
11443 fn mutate_where_only_block_has_no_set_fields() {
11444 let prog =
11447 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
11448 match first_step(&prog, "F") {
11449 FlowStep::Mutate(m) => {
11450 assert_eq!(m.where_expr, "id = 1");
11451 assert!(m.fields.is_empty());
11452 }
11453 other => panic!("expected Mutate, got {other:?}"),
11454 }
11455 }
11456
11457 #[test]
11458 fn mutate_with_no_block_is_a_whole_store_op() {
11459 let prog = parse("flow F() -> Unit { mutate accounts }");
11462 match first_step(&prog, "F") {
11463 FlowStep::Mutate(m) => {
11464 assert_eq!(m.store_name, "accounts");
11465 assert_eq!(m.where_expr, "");
11466 assert!(m.fields.is_empty());
11467 }
11468 other => panic!("expected Mutate, got {other:?}"),
11469 }
11470 }
11471
11472 #[test]
11473 fn mutate_fields_lower_into_the_ir() {
11474 let prog = parse(
11475 "flow F() -> Unit { mutate t { where: \"id = 1\" v: \"${x}\" } }",
11476 );
11477 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
11478 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
11479 match flow.steps.first().expect("one step") {
11480 crate::ir_nodes::IRFlowNode::Mutate(m) => {
11481 assert_eq!(m.where_expr, "id = 1");
11482 assert_eq!(
11483 m.fields,
11484 vec![("v".to_string(), "${x}".to_string())]
11485 );
11486 }
11487 other => panic!("expected IRFlowNode::Mutate, got {other:?}"),
11488 }
11489 }
11490}
11491