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::Budget(n) => {
56 n.leading_trivia = leading;
57 n.trailing_trivia = trailing;
58 }
59 Declaration::Import(n) => {
60 n.leading_trivia = leading;
61 n.trailing_trivia = trailing;
62 }
63 Declaration::Persona(n) => {
64 n.leading_trivia = leading;
65 n.trailing_trivia = trailing;
66 }
67 Declaration::Context(n) => {
68 n.leading_trivia = leading;
69 n.trailing_trivia = trailing;
70 }
71 Declaration::Anchor(n) => {
72 n.leading_trivia = leading;
73 n.trailing_trivia = trailing;
74 }
75 Declaration::Memory(n) => {
76 n.leading_trivia = leading;
77 n.trailing_trivia = trailing;
78 }
79 Declaration::Tool(n) => {
80 n.leading_trivia = leading;
81 n.trailing_trivia = trailing;
82 }
83 Declaration::Type(n) => {
84 n.leading_trivia = leading;
85 n.trailing_trivia = trailing;
86 }
87 Declaration::Flow(n) => {
88 n.leading_trivia = leading;
89 n.trailing_trivia = trailing;
90 }
91 Declaration::Intent(n) => {
92 n.leading_trivia = leading;
93 n.trailing_trivia = trailing;
94 }
95 Declaration::Run(n) => {
96 n.leading_trivia = leading;
97 n.trailing_trivia = trailing;
98 }
99 Declaration::Epistemic(n) => {
100 n.leading_trivia = leading;
101 n.trailing_trivia = trailing;
102 }
103 Declaration::Let(n) => {
104 n.leading_trivia = leading;
105 n.trailing_trivia = trailing;
106 }
107 Declaration::LambdaData(n) => {
108 n.leading_trivia = leading;
109 n.trailing_trivia = trailing;
110 }
111 Declaration::Agent(n) => {
112 n.leading_trivia = leading;
113 n.trailing_trivia = trailing;
114 }
115 Declaration::Shield(n) => {
116 n.leading_trivia = leading;
117 n.trailing_trivia = trailing;
118 }
119 Declaration::Window(n) => {
120 n.leading_trivia = leading;
121 n.trailing_trivia = trailing;
122 }
123 Declaration::Pix(n) => {
124 n.leading_trivia = leading;
125 n.trailing_trivia = trailing;
126 }
127 Declaration::Ledger(n) => {
128 n.leading_trivia = leading;
129 n.trailing_trivia = trailing;
130 }
131 Declaration::Psyche(n) => {
132 n.leading_trivia = leading;
133 n.trailing_trivia = trailing;
134 }
135 Declaration::Corpus(n) => {
136 n.leading_trivia = leading;
137 n.trailing_trivia = trailing;
138 }
139 Declaration::Dataspace(n) => {
140 n.leading_trivia = leading;
141 n.trailing_trivia = trailing;
142 }
143 Declaration::Ots(n) => {
144 n.leading_trivia = leading;
145 n.trailing_trivia = trailing;
146 }
147 Declaration::Mandate(n) => {
148 n.leading_trivia = leading;
149 n.trailing_trivia = trailing;
150 }
151 Declaration::Compute(n) => {
152 n.leading_trivia = leading;
153 n.trailing_trivia = trailing;
154 }
155 Declaration::Daemon(n) => {
156 n.leading_trivia = leading;
157 n.trailing_trivia = trailing;
158 }
159 Declaration::Extension(n) => {
160 n.leading_trivia = leading;
161 n.trailing_trivia = trailing;
162 }
163 Declaration::AxonStore(n) => {
164 n.leading_trivia = leading;
165 n.trailing_trivia = trailing;
166 }
167 Declaration::AxonEndpoint(n) => {
168 n.leading_trivia = leading;
169 n.trailing_trivia = trailing;
170 }
171 Declaration::Resource(n) => {
172 n.leading_trivia = leading;
173 n.trailing_trivia = trailing;
174 }
175 Declaration::Fabric(n) => {
176 n.leading_trivia = leading;
177 n.trailing_trivia = trailing;
178 }
179 Declaration::Manifest(n) => {
180 n.leading_trivia = leading;
181 n.trailing_trivia = trailing;
182 }
183 Declaration::Observe(n) => {
184 n.leading_trivia = leading;
185 n.trailing_trivia = trailing;
186 }
187 Declaration::Reconcile(n) => {
188 n.leading_trivia = leading;
189 n.trailing_trivia = trailing;
190 }
191 Declaration::Lease(n) => {
192 n.leading_trivia = leading;
193 n.trailing_trivia = trailing;
194 }
195 Declaration::Ensemble(n) => {
196 n.leading_trivia = leading;
197 n.trailing_trivia = trailing;
198 }
199 Declaration::Session(n) => {
200 n.leading_trivia = leading;
201 n.trailing_trivia = trailing;
202 }
203 Declaration::Topology(n) => {
204 n.leading_trivia = leading;
205 n.trailing_trivia = trailing;
206 }
207 Declaration::Immune(n) => {
208 n.leading_trivia = leading;
209 n.trailing_trivia = trailing;
210 }
211 Declaration::Reflex(n) => {
212 n.leading_trivia = leading;
213 n.trailing_trivia = trailing;
214 }
215 Declaration::Heal(n) => {
216 n.leading_trivia = leading;
217 n.trailing_trivia = trailing;
218 }
219 Declaration::Component(n) => {
220 n.leading_trivia = leading;
221 n.trailing_trivia = trailing;
222 }
223 Declaration::View(n) => {
224 n.leading_trivia = leading;
225 n.trailing_trivia = trailing;
226 }
227 Declaration::Channel(n) => {
228 n.leading_trivia = leading;
229 n.trailing_trivia = trailing;
230 }
231 Declaration::Socket(n) => {
232 n.leading_trivia = leading;
233 n.trailing_trivia = trailing;
234 }
235 Declaration::Upstream(n) => {
236 n.leading_trivia = leading;
237 n.trailing_trivia = trailing;
238 }
239 Declaration::Voice(n) => {
240 n.leading_trivia = leading;
241 n.trailing_trivia = trailing;
242 }
243 Declaration::Cors(n) => {
244 n.leading_trivia = leading;
245 n.trailing_trivia = trailing;
246 }
247 Declaration::Credential(n) => {
248 n.leading_trivia = leading;
249 n.trailing_trivia = trailing;
250 }
251 Declaration::Cache(n) => {
252 n.leading_trivia = leading;
253 n.trailing_trivia = trailing;
254 }
255 Declaration::Savant(n) => {
256 n.leading_trivia = leading;
257 n.trailing_trivia = trailing;
258 }
259 Declaration::Synth(n) => {
260 n.leading_trivia = leading;
261 n.trailing_trivia = trailing;
262 }
263 Declaration::Scope(n) => {
264 n.leading_trivia = leading;
265 n.trailing_trivia = trailing;
266 }
267 Declaration::Observable(n) => {
268 n.leading_trivia = leading;
269 n.trailing_trivia = trailing;
270 }
271 Declaration::Witness(n) => {
272 n.leading_trivia = leading;
273 n.trailing_trivia = trailing;
274 }
275 Declaration::Document(n) => {
276 n.leading_trivia = leading;
277 n.trailing_trivia = trailing;
278 }
279 Declaration::Deliver(n) => {
280 n.leading_trivia = leading;
281 n.trailing_trivia = trailing;
282 }
283 Declaration::Notify(n) => {
284 n.leading_trivia = leading;
285 n.trailing_trivia = trailing;
286 }
287 Declaration::Generic(n) => {
288 n.leading_trivia = leading;
289 n.trailing_trivia = trailing;
290 }
291 }
292}
293
294pub const SOURCE_CONTEXT_LINES_BEFORE: usize = 2;
301pub const SOURCE_CONTEXT_LINES_AFTER: usize = 2;
302
303#[derive(Debug, Clone)]
314pub struct SourceSnippet {
315 pub source: String,
316 pub line: u32,
317 pub column: u32,
318 pub filename: String,
319 pub context_before: usize,
320 pub context_after: usize,
321}
322
323impl SourceSnippet {
324 pub fn new(source: String, line: u32, column: u32, filename: String) -> Self {
326 Self {
327 source,
328 line,
329 column,
330 filename,
331 context_before: SOURCE_CONTEXT_LINES_BEFORE,
332 context_after: SOURCE_CONTEXT_LINES_AFTER,
333 }
334 }
335
336 #[must_use]
343 pub fn render(&self) -> String {
344 if self.source.is_empty() || self.line < 1 {
345 return String::new();
346 }
347 let raw: Vec<&str> = self.source.split('\n').collect();
348 let lines: Vec<&str> = if raw.last() == Some(&"") {
351 raw[..raw.len() - 1].to_vec()
352 } else {
353 raw
354 };
355 if lines.is_empty() || self.line as usize > lines.len() {
356 return String::new();
357 }
358
359 let line_idx = self.line as usize;
360 let start = line_idx.saturating_sub(self.context_before).max(1);
361 let end = (line_idx + self.context_after).min(lines.len());
362
363 let gutter = end.to_string().len();
364 let empty_gutter = " ".repeat(gutter);
365
366 let mut out: Vec<String> = Vec::with_capacity(end - start + 4);
367 out.push(format!(
368 "{empty_gutter} --> {}:{}:{}",
369 self.filename, self.line, self.column
370 ));
371 out.push(format!("{empty_gutter} |"));
372 for n in start..=end {
373 let line_text = lines[n - 1];
374 out.push(format!("{n:>gutter$} | {line_text}", gutter = gutter));
375 if n == line_idx {
376 let line_len = line_text.chars().count();
377 let col = (self.column as usize).clamp(1, line_len + 1);
378 out.push(format!(
379 "{empty_gutter} | {pad}^",
380 pad = " ".repeat(col - 1)
381 ));
382 }
383 }
384 out.join("\n")
385 }
386}
387
388#[derive(Debug, Clone, Default)]
389pub struct ParseError {
390 pub message: String,
391 pub line: u32,
392 pub column: u32,
393 pub source_snippet: Option<SourceSnippet>,
400}
401
402impl ParseError {
403 #[must_use]
407 pub fn attach_source(mut self, source: &str, filename: &str) -> Self {
408 if self.line >= 1 {
409 self.source_snippet = Some(SourceSnippet::new(
410 source.to_string(),
411 self.line,
412 self.column,
413 filename.to_string(),
414 ));
415 }
416 self
417 }
418}
419
420impl std::fmt::Display for ParseError {
421 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
422 write!(f, "[line {}:{}] {}", self.line, self.column, self.message)?;
423 if let Some(snippet) = &self.source_snippet {
424 let block = snippet.render();
425 if !block.is_empty() {
426 write!(f, "\n{block}")?;
427 }
428 }
429 Ok(())
430 }
431}
432
433impl std::error::Error for ParseError {}
434
435#[derive(Debug)]
453pub struct ParseResult {
454 pub program: Program,
455 pub errors: Vec<ParseError>,
456}
457
458impl ParseResult {
459 #[inline]
464 #[must_use]
465 pub fn has_errors(&self) -> bool {
466 !self.errors.is_empty()
467 }
468
469 #[inline]
472 #[must_use]
473 pub fn is_clean(&self) -> bool {
474 self.errors.is_empty()
475 }
476}
477
478#[inline]
492const fn is_top_level_decl_kw_for_recovery(tt: &TokenType) -> bool {
493 matches!(
494 tt,
495 TokenType::Import
496 | TokenType::Persona
497 | TokenType::Context
498 | TokenType::Anchor
499 | TokenType::Memory
500 | TokenType::Tool
501 | TokenType::Type
502 | TokenType::Flow
503 | TokenType::Intent
504 | TokenType::Run
505 | TokenType::Let
506 | TokenType::Know
507 | TokenType::Believe
508 | TokenType::Speculate
509 | TokenType::Doubt
510 | TokenType::Lambda
511 | TokenType::Agent
512 | TokenType::Shield
513 | TokenType::Pix
514 | TokenType::Ledger
515 | TokenType::Psyche
516 | TokenType::Corpus
517 | TokenType::Dataspace
518 | TokenType::Ots
519 | TokenType::Mandate
520 | TokenType::Compute
521 | TokenType::Daemon
522 | TokenType::Savant
524 | TokenType::Synth
525 | TokenType::Scope
527 | TokenType::AxonStore
528 | TokenType::AxonEndpoint
529 | TokenType::Resource
530 | TokenType::Fabric
531 | TokenType::Manifest
532 | TokenType::Observe
533 | TokenType::Reconcile
534 | TokenType::Lease
535 | TokenType::Ensemble
536 | TokenType::Session
537 | TokenType::Topology
538 | TokenType::Immune
539 | TokenType::Reflex
540 | TokenType::Heal
541 | TokenType::Component
542 | TokenType::View
543 | TokenType::Channel
544 | TokenType::Ingest
545 | TokenType::Persist
546 | TokenType::Retrieve
547 | TokenType::Mutate
548 | TokenType::Purge
549 | TokenType::Transact
550 | TokenType::Mcp
551 )
552}
553
554pub const AXONENDPOINT_TRANSPORT_VALUES: &[&str] = &["json", "sse", "ndjson"];
567
568pub const AXONENDPOINT_TRANSPORT_DIALECTS: &[&str] =
612 &["axon", "openai", "kimi", "glm", "anthropic"];
613
614pub const AXONENDPOINT_KEEPALIVE_VALUES: &[&str] = &["5s", "15s", "30s", "60s"];
616
617pub const AXONENDPOINT_METHOD_VALUES: &[&str] =
631 &["GET", "POST", "PUT", "DELETE", "PATCH", "QUERY"];
632
633pub const AXONENDPOINT_BACKEND_VALUES: &[&str] = &[
654 "anthropic",
655 "auto",
656 "gemini",
657 "glm",
658 "kimi",
659 "ollama",
660 "openai",
661 "openrouter",
662 "stub",
663];
664
665#[inline]
666fn axonendpoint_is_valid_transport(s: &str) -> bool {
667 AXONENDPOINT_TRANSPORT_VALUES.iter().any(|&v| v == s)
668}
669
670#[inline]
671fn axonendpoint_is_valid_method(s: &str) -> bool {
672 AXONENDPOINT_METHOD_VALUES.iter().any(|&v| v == s)
673}
674
675#[inline]
676fn axonendpoint_is_valid_backend(s: &str) -> bool {
677 AXONENDPOINT_BACKEND_VALUES.iter().any(|&v| v == s)
678}
679
680#[inline]
681fn axonendpoint_is_valid_keepalive(s: &str) -> bool {
682 AXONENDPOINT_KEEPALIVE_VALUES.iter().any(|&v| v == s)
683}
684
685pub const AXONENDPOINT_QUERY_PARAM_TYPES: &[&str] =
700 &["Text", "Int", "Float", "Bool", "Uuid"];
701
702#[inline]
705pub(crate) fn axonendpoint_is_valid_query_param_type(s: &str) -> bool {
706 AXONENDPOINT_QUERY_PARAM_TYPES.iter().any(|&v| v == s)
707}
708
709pub(crate) fn extract_path_param_names(path: &str) -> Result<Vec<String>, String> {
736 let mut out: Vec<String> = Vec::new();
737 let bytes = path.as_bytes();
738 let mut i = 0;
739 while i < bytes.len() {
740 if bytes[i] != b'{' {
741 i += 1;
742 continue;
743 }
744 let start = i + 1;
747 let mut end = start;
748 while end < bytes.len() && bytes[end] != b'}' {
749 end += 1;
750 }
751 if end == bytes.len() {
752 break;
755 }
756 let raw = &path[start..end];
757 let valid = !raw.is_empty()
759 && raw.bytes().enumerate().all(|(idx, b)| {
760 if idx == 0 {
761 b.is_ascii_alphabetic() || b == b'_'
762 } else {
763 b.is_ascii_alphanumeric() || b == b'_'
764 }
765 });
766 if valid {
767 let name = raw.to_string();
768 if out.iter().any(|existing| existing == &name) {
769 return Err(name);
770 }
771 out.push(name);
772 }
773 i = end + 1;
774 }
775 Ok(out)
776}
777
778pub fn is_valid_capability_slug(slug: &str) -> bool {
792 if slug.is_empty() {
793 return false;
794 }
795 for segment in slug.split('.') {
796 if !is_valid_slug_segment(segment) {
797 return false;
798 }
799 }
800 true
801}
802
803fn is_valid_slug_segment(seg: &str) -> bool {
804 let mut chars = seg.chars();
805 let first = match chars.next() {
806 Some(c) => c,
807 None => return false,
808 };
809 if !first.is_ascii_lowercase() {
810 return false;
811 }
812 chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
813}
814
815#[cfg(test)]
825mod query_param_catalog_tests {
826 use super::{axonendpoint_is_valid_query_param_type, AXONENDPOINT_QUERY_PARAM_TYPES};
827
828 #[test]
829 fn accepts_every_catalog_entry() {
830 for ty in AXONENDPOINT_QUERY_PARAM_TYPES {
831 assert!(
832 axonendpoint_is_valid_query_param_type(ty),
833 "catalog entry `{ty}` must validate"
834 );
835 }
836 }
837
838 #[test]
839 fn rejects_off_catalog_types() {
840 for off in &[
841 "Timestamp", "Date",
843 "DateTime",
844 "List<Text>", "Jsonb", "Bytea",
847 "text", "TEXT",
849 "Number", "", " ", ] {
853 assert!(
854 !axonendpoint_is_valid_query_param_type(off),
855 "off-catalog `{off}` must reject"
856 );
857 }
858 }
859
860 #[test]
861 fn catalog_size_matches_design() {
862 assert_eq!(AXONENDPOINT_QUERY_PARAM_TYPES.len(), 5);
866 }
867}
868
869#[cfg(test)]
870mod query_param_parser_tests {
871 use crate::lexer::Lexer;
872 use crate::parser::Parser;
873
874 fn parse_endpoint_source(src: &str) -> Result<crate::ast::AxonEndpointDefinition, String> {
875 let tokens = Lexer::new(src, "test.axon")
876 .tokenize()
877 .map_err(|e| format!("lex: {}", e.message))?;
878 let mut parser = Parser::new(tokens);
879 let program = parser.parse().map_err(|e| format!("parse: {}", e.message))?;
880 program
881 .declarations
882 .into_iter()
883 .find_map(|d| match d {
884 crate::ast::Declaration::AxonEndpoint(e) => Some(e),
885 _ => None,
886 })
887 .ok_or_else(|| "no axonendpoint in program".to_string())
888 }
889
890 #[test]
891 fn endpoint_with_no_query_block_keeps_empty_vec() {
892 let src = r#"
893 axonendpoint write_secret {
894 method: POST
895 path: "/api/users"
896 body: SecretWriteRequest
897 execute: WriteSecret
898 }
899 "#;
900 let ep = parse_endpoint_source(src).expect("parses");
901 assert!(
902 ep.query_params.is_empty(),
903 "D5 — no `query:` block ⇒ empty query_params"
904 );
905 }
906
907 #[test]
908 fn single_query_param_required() {
909 let src = r#"
910 axonendpoint list_users {
911 method: GET
912 path: "/api/users"
913 query: { status: Text }
914 execute: ListUsers
915 }
916 "#;
917 let ep = parse_endpoint_source(src).expect("parses");
918 assert_eq!(ep.query_params.len(), 1);
919 assert_eq!(ep.query_params[0].name, "status");
920 assert_eq!(ep.query_params[0].type_expr.name, "Text");
921 assert!(!ep.query_params[0].type_expr.optional);
922 }
923
924 #[test]
925 fn optional_query_param_via_question_suffix() {
926 let src = r#"
927 axonendpoint list_users {
928 method: GET
929 path: "/api/users"
930 query: { limit: Int? }
931 execute: ListUsers
932 }
933 "#;
934 let ep = parse_endpoint_source(src).expect("parses");
935 assert_eq!(ep.query_params.len(), 1);
936 assert_eq!(ep.query_params[0].name, "limit");
937 assert_eq!(ep.query_params[0].type_expr.name, "Int");
938 assert!(
939 ep.query_params[0].type_expr.optional,
940 "`?` suffix sets optional"
941 );
942 }
943
944 #[test]
945 fn multiple_query_params_preserve_declaration_order() {
946 let src = r#"
947 axonendpoint search {
948 method: GET
949 path: "/api/search"
950 query: { q: Text, page: Int?, limit: Int?, exact: Bool? }
951 execute: Search
952 }
953 "#;
954 let ep = parse_endpoint_source(src).expect("parses");
955 let names: Vec<&str> = ep.query_params.iter().map(|f| f.name.as_str()).collect();
956 assert_eq!(names, vec!["q", "page", "limit", "exact"]);
957 let types: Vec<&str> = ep
958 .query_params
959 .iter()
960 .map(|f| f.type_expr.name.as_str())
961 .collect();
962 assert_eq!(types, vec!["Text", "Int", "Int", "Bool"]);
963 let optionals: Vec<bool> = ep
964 .query_params
965 .iter()
966 .map(|f| f.type_expr.optional)
967 .collect();
968 assert_eq!(optionals, vec![false, true, true, true]);
969 }
970
971 #[test]
972 fn duplicate_query_param_is_parse_error() {
973 let src = r#"
974 axonendpoint bad {
975 method: GET
976 path: "/api/x"
977 query: { name: Text, name: Int? }
978 execute: Bad
979 }
980 "#;
981 let err = parse_endpoint_source(src).expect_err("must fail");
982 assert!(
983 err.contains("duplicate query param 'name'"),
984 "error must name the duplicate. Got: {err}"
985 );
986 }
987
988 #[test]
989 fn off_catalog_type_with_smart_suggest_hint() {
990 let src = r#"
995 axonendpoint bad {
996 method: GET
997 path: "/api/x"
998 query: { value: Strng }
999 execute: Bad
1000 }
1001 "#;
1002 let err = parse_endpoint_source(src).expect_err("must fail");
1003 assert!(
1004 err.contains("unsupported type 'Strng'"),
1005 "error must name the bad type. Got: {err}"
1006 );
1007 assert!(
1008 err.contains("Expected one of: Text | Int | Float | Bool | Uuid"),
1009 "error must list the closed catalog. Got: {err}"
1010 );
1011 }
1012
1013 #[test]
1014 fn close_typo_gets_did_you_mean_hint() {
1015 let src = r#"
1018 axonendpoint bad {
1019 method: GET
1020 path: "/api/x"
1021 query: { value: Txt }
1022 execute: Bad
1023 }
1024 "#;
1025 let err = parse_endpoint_source(src).expect_err("must fail");
1026 assert!(
1027 err.contains("Did you mean") && err.contains("`Text`"),
1028 "smart-suggest must hint `Text`. Got: {err}"
1029 );
1030 }
1031
1032 #[test]
1033 fn every_catalog_type_parses_cleanly() {
1034 for ty in &["Text", "Int", "Float", "Bool", "Uuid"] {
1036 let src = format!(
1037 r#"
1038 axonendpoint x {{
1039 method: GET
1040 path: "/api/x"
1041 query: {{ v: {ty} }}
1042 execute: X
1043 }}
1044 "#
1045 );
1046 let ep = parse_endpoint_source(&src)
1047 .unwrap_or_else(|e| panic!("`{ty}` should parse: {e}"));
1048 assert_eq!(ep.query_params[0].type_expr.name, *ty);
1049 }
1050 }
1051
1052 #[test]
1053 fn comma_optional_between_params() {
1054 let src = r#"
1058 axonendpoint x {
1059 method: GET
1060 path: "/api/x"
1061 query: { a: Text b: Int? }
1062 execute: X
1063 }
1064 "#;
1065 let ep = parse_endpoint_source(src).expect("parses without commas");
1066 assert_eq!(ep.query_params.len(), 2);
1067 }
1068
1069 #[test]
1072 fn double_query_block_is_parse_error() {
1073 let src = r#"
1077 axonendpoint x {
1078 method: GET
1079 path: "/api/x"
1080 query: { a: Text }
1081 query: { b: Int? }
1082 execute: X
1083 }
1084 "#;
1085 let err = parse_endpoint_source(src).expect_err("must fail");
1086 assert!(
1087 err.contains("declares `query: { … }` more than once"),
1088 "error must call out the duplicate block. Got: {err}"
1089 );
1090 assert!(
1091 err.contains("combine all params into a single block"),
1092 "error must hint the canonical fix. Got: {err}"
1093 );
1094 }
1095
1096 #[test]
1097 fn optional_generic_type_is_parse_error_with_canonical_hint() {
1098 let src = r#"
1102 axonendpoint x {
1103 method: GET
1104 path: "/api/x"
1105 query: { value: Optional<Text> }
1106 execute: X
1107 }
1108 "#;
1109 let err = parse_endpoint_source(src).expect_err("must fail");
1110 assert!(
1111 err.contains("generic type `Optional<Text>`"),
1112 "error must name the generic type literally. Got: {err}"
1113 );
1114 assert!(
1115 err.contains("Use `Text?` (the `?` suffix)"),
1116 "error must hint the canonical `Text?` syntax. Got: {err}"
1117 );
1118 }
1119
1120 #[test]
1121 fn list_generic_type_is_parse_error_with_deferral_hint() {
1122 let src = r#"
1127 axonendpoint x {
1128 method: GET
1129 path: "/api/x"
1130 query: { tags: List<Text> }
1131 execute: X
1132 }
1133 "#;
1134 let err = parse_endpoint_source(src).expect_err("must fail");
1135 assert!(
1136 err.contains("generic type `List<Text>`"),
1137 "error must name the generic type. Got: {err}"
1138 );
1139 assert!(
1140 err.contains("Multi-value query params")
1141 && err.contains("honest-deferred"),
1142 "error must mention the multi-value deferral. Got: {err}"
1143 );
1144 }
1145
1146 #[test]
1147 fn other_generic_types_caught_generically() {
1148 let src = r#"
1152 axonendpoint x {
1153 method: GET
1154 path: "/api/x"
1155 query: { value: Stream<Int> }
1156 execute: X
1157 }
1158 "#;
1159 let err = parse_endpoint_source(src).expect_err("must fail");
1160 assert!(
1161 err.contains("generic type `Stream<Int>`"),
1162 "error must name the generic type. Got: {err}"
1163 );
1164 assert!(
1165 err.contains("Text | Int | Float | Bool | Uuid"),
1166 "error must list the closed catalog. Got: {err}"
1167 );
1168 }
1169
1170 #[test]
1171 fn uuid_optional_parses_cleanly() {
1172 let src = r#"
1175 axonendpoint find {
1176 method: GET
1177 path: "/api/x"
1178 query: { after: Uuid? }
1179 execute: Find
1180 }
1181 "#;
1182 let ep = parse_endpoint_source(src).expect("parses");
1183 assert_eq!(ep.query_params.len(), 1);
1184 assert_eq!(ep.query_params[0].name, "after");
1185 assert_eq!(ep.query_params[0].type_expr.name, "Uuid");
1186 assert!(ep.query_params[0].type_expr.optional);
1187 assert_eq!(ep.query_params[0].type_expr.generic_param, "");
1188 }
1189
1190 #[test]
1191 fn empty_query_block_yields_empty_vec() {
1192 let src = r#"
1196 axonendpoint x {
1197 method: GET
1198 path: "/api/x"
1199 query: { }
1200 execute: X
1201 }
1202 "#;
1203 let ep = parse_endpoint_source(src).expect("empty block parses");
1204 assert!(ep.query_params.is_empty());
1205 }
1206
1207 #[test]
1208 fn kivi_secret_write_path_plus_query() {
1209 let src = r#"
1213 axonendpoint write_secret {
1214 method: POST
1215 path: "/api/tenants/{tenant_id}/secrets/{secret_name}"
1216 query: { dry_run: Bool?, overwrite: Bool? }
1217 body: SecretWriteRequest
1218 execute: WriteSecret
1219 }
1220 "#;
1221 let ep = parse_endpoint_source(src).expect("parses");
1222 assert_eq!(ep.path_params, vec!["tenant_id", "secret_name"]);
1224 assert_eq!(ep.query_params.len(), 2);
1226 assert_eq!(ep.query_params[0].name, "dry_run");
1227 assert_eq!(ep.query_params[0].type_expr.name, "Bool");
1228 assert!(ep.query_params[0].type_expr.optional);
1229 assert_eq!(ep.query_params[1].name, "overwrite");
1230 assert_eq!(ep.body_type, "SecretWriteRequest");
1232 }
1233}
1234
1235#[cfg(test)]
1236mod path_param_extraction_tests {
1237 use super::extract_path_param_names;
1238
1239 #[test]
1240 fn empty_path_no_placeholders() {
1241 assert_eq!(extract_path_param_names("/api/users"), Ok(vec![]));
1242 assert_eq!(extract_path_param_names("/"), Ok(vec![]));
1243 assert_eq!(extract_path_param_names(""), Ok(vec![]));
1244 }
1245
1246 #[test]
1247 fn single_placeholder() {
1248 assert_eq!(
1249 extract_path_param_names("/api/users/{id}"),
1250 Ok(vec!["id".to_string()])
1251 );
1252 }
1253
1254 #[test]
1255 fn multiple_placeholders_in_declaration_order() {
1256 assert_eq!(
1257 extract_path_param_names(
1258 "/api/tenants/{tenant_id}/secrets/{secret_name}"
1259 ),
1260 Ok(vec![
1261 "tenant_id".to_string(),
1262 "secret_name".to_string(),
1263 ])
1264 );
1265 }
1266
1267 #[test]
1268 fn kivi_chat_history_path_pattern() {
1269 let names = extract_path_param_names(
1273 "/api/tenants/{tenant_id}/secrets/{secret_name}",
1274 );
1275 assert_eq!(
1276 names,
1277 Ok(vec![
1278 "tenant_id".to_string(),
1279 "secret_name".to_string(),
1280 ])
1281 );
1282 }
1283
1284 #[test]
1285 fn duplicate_placeholder_returns_err() {
1286 assert_eq!(
1287 extract_path_param_names("/api/users/{id}/posts/{id}"),
1288 Err("id".to_string())
1289 );
1290 }
1291
1292 #[test]
1293 fn underscore_and_numeric_in_name() {
1294 assert_eq!(
1295 extract_path_param_names("/api/{user_id}/items/{item_2}"),
1296 Ok(vec!["user_id".to_string(), "item_2".to_string()])
1297 );
1298 }
1299
1300 #[test]
1301 fn leading_underscore_accepted() {
1302 assert_eq!(
1308 extract_path_param_names("/api/{_internal}"),
1309 Ok(vec!["_internal".to_string()])
1310 );
1311 }
1312
1313 #[test]
1314 fn malformed_placeholder_silently_ignored() {
1315 assert_eq!(
1319 extract_path_param_names("/api/{not valid}"),
1320 Ok(vec![])
1321 );
1322 assert_eq!(extract_path_param_names("/api/{}"), Ok(vec![]));
1324 assert_eq!(
1326 extract_path_param_names("/api/{tenant id}/users/{id}"),
1327 Ok(vec!["id".to_string()])
1328 );
1329 }
1330
1331 #[test]
1332 fn unterminated_brace_returns_clean() {
1333 assert_eq!(extract_path_param_names("/api/{id"), Ok(vec![]));
1336 }
1337
1338 #[test]
1339 fn placeholders_at_path_boundaries() {
1340 assert_eq!(
1343 extract_path_param_names("{prefix}/api/users/{id}"),
1344 Ok(vec!["prefix".to_string(), "id".to_string()])
1345 );
1346 assert_eq!(
1347 extract_path_param_names("/api/{id}"),
1348 Ok(vec!["id".to_string()])
1349 );
1350 }
1351
1352 #[test]
1353 fn deduplication_detects_non_adjacent_duplicates() {
1354 assert_eq!(
1356 extract_path_param_names(
1357 "/api/orgs/{org_id}/teams/{team_id}/repos/{org_id}"
1358 ),
1359 Err("org_id".to_string())
1360 );
1361 }
1362
1363 #[test]
1364 fn never_panics_on_arbitrary_input() {
1365 for input in &[
1367 "{",
1368 "}",
1369 "{}",
1370 "{{}}",
1371 "{{{",
1372 "/api/{}/{id}",
1373 "////",
1374 "\u{1F4A1}", "\u{0000}", ] {
1377 let _ = extract_path_param_names(input); }
1379 }
1380}
1381
1382#[cfg(test)]
1383mod capability_slug_tests {
1384 use super::is_valid_capability_slug;
1385
1386 #[test]
1387 fn accepts_canonical_examples() {
1388 assert!(is_valid_capability_slug("admin"));
1389 assert!(is_valid_capability_slug("legal.read"));
1390 assert!(is_valid_capability_slug("hipaa.phi.read"));
1391 assert!(is_valid_capability_slug("bank.officer.senior"));
1392 assert!(is_valid_capability_slug("a"));
1393 assert!(is_valid_capability_slug("a_b"));
1394 assert!(is_valid_capability_slug("a1"));
1395 assert!(is_valid_capability_slug("a.b1_c"));
1396 }
1397
1398 #[test]
1399 fn rejects_empty_string() {
1400 assert!(!is_valid_capability_slug(""));
1401 }
1402
1403 #[test]
1404 fn rejects_uppercase() {
1405 assert!(!is_valid_capability_slug("Admin"));
1406 assert!(!is_valid_capability_slug("admin.READ"));
1407 }
1408
1409 #[test]
1410 fn rejects_digit_first() {
1411 assert!(!is_valid_capability_slug("1admin"));
1412 assert!(!is_valid_capability_slug("admin.1read"));
1413 }
1414
1415 #[test]
1416 fn rejects_hyphen() {
1417 assert!(!is_valid_capability_slug("bank-officer"));
1418 }
1419
1420 #[test]
1421 fn rejects_empty_segments() {
1422 assert!(!is_valid_capability_slug("bank..a"));
1423 assert!(!is_valid_capability_slug(".admin"));
1424 assert!(!is_valid_capability_slug("admin."));
1425 }
1426
1427 #[test]
1428 fn rejects_special_chars() {
1429 assert!(!is_valid_capability_slug("admin@read"));
1430 assert!(!is_valid_capability_slug("admin/read"));
1431 assert!(!is_valid_capability_slug("admin read"));
1432 }
1433}
1434
1435pub struct Parser {
1438 tokens: Vec<Token>,
1439 pos: usize,
1440 hoisted: Vec<Declaration>,
1460 leading_trivia: Vec<Vec<Trivia>>,
1465 trailing_trivia: Vec<Vec<Trivia>>,
1469 last_let_value_kind: String,
1473 loop_depth: u32,
1478 source: Option<String>,
1484 filename: String,
1485}
1486
1487impl Parser {
1488 pub fn new(raw_tokens: Vec<Token>) -> Self {
1489 let mut effective: Vec<Token> = Vec::with_capacity(raw_tokens.len());
1499 let mut leading: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1500 let mut trailing: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1501
1502 let mut pending_leading: Vec<Trivia> = Vec::new();
1503 let mut last_effective_line: i64 = -1;
1504 for tok in raw_tokens {
1505 if is_comment_token(&tok.ttype) {
1506 let kind = token_to_trivia_kind(&tok.ttype)
1507 .expect("comment token must map to a trivia kind");
1508 let triv = Trivia {
1509 kind,
1510 text: tok.value,
1511 line: tok.line,
1512 column: tok.column,
1513 };
1514 if !effective.is_empty() && (tok.line as i64) == last_effective_line {
1515 trailing.last_mut().unwrap().push(triv);
1516 } else {
1517 pending_leading.push(triv);
1518 }
1519 } else {
1520 last_effective_line = tok.line as i64;
1521 effective.push(tok);
1522 leading.push(std::mem::take(&mut pending_leading));
1523 trailing.push(Vec::new());
1524 }
1525 }
1526
1527 Parser {
1528 hoisted: Vec::new(),
1529 tokens: effective,
1530 pos: 0,
1531 leading_trivia: leading,
1532 trailing_trivia: trailing,
1533 last_let_value_kind: "literal".to_string(),
1534 loop_depth: 0,
1535 source: None,
1536 filename: "<source>".to_string(),
1537 }
1538 }
1539
1540 #[must_use]
1552 pub fn with_source(mut self, source: &str, filename: &str) -> Self {
1553 self.source = Some(source.to_string());
1554 self.filename = filename.to_string();
1555 self
1556 }
1557
1558 pub fn parse(&mut self) -> Result<Program, ParseError> {
1561 let mut program = Program {
1562 declarations: Vec::new(),
1563 declaration_trivia: Vec::new(),
1564 loc: Loc { line: 1, column: 1 },
1565 };
1566 while !self.check(TokenType::Eof) {
1567 let start_pos = self.pos;
1573 let mut decl = match self.parse_declaration() {
1574 Ok(d) => d,
1575 Err(e) => return Err(self.attach_source_to_error(e)),
1576 };
1577 let end_pos = self.pos.saturating_sub(1);
1578 let leading = self
1579 .leading_trivia
1580 .get(start_pos)
1581 .cloned()
1582 .unwrap_or_default();
1583 let trailing = self
1584 .trailing_trivia
1585 .get(end_pos)
1586 .cloned()
1587 .unwrap_or_default();
1588 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1594 program.declarations.push(decl);
1595 program
1596 .declaration_trivia
1597 .push(DeclarationTrivia { leading, trailing });
1598 for hoisted in std::mem::take(&mut self.hoisted) {
1602 program.declarations.push(hoisted);
1603 program.declaration_trivia.push(DeclarationTrivia {
1604 leading: Vec::new(),
1605 trailing: Vec::new(),
1606 });
1607 }
1608 }
1609 crate::voice_desugar::expand(&mut program);
1616 crate::upstream_presets::expand(&mut program);
1617 Ok(program)
1618 }
1619
1620 pub fn parse_with_recovery(&mut self) -> ParseResult {
1653 let mut program = Program {
1654 declarations: Vec::new(),
1655 declaration_trivia: Vec::new(),
1656 loc: Loc { line: 1, column: 1 },
1657 };
1658 let mut errors: Vec<ParseError> = Vec::new();
1659
1660 while !self.check(TokenType::Eof) {
1661 let start_pos = self.pos;
1662 match self.parse_declaration() {
1663 Ok(mut decl) => {
1664 let end_pos = self.pos.saturating_sub(1);
1665 let leading = self
1666 .leading_trivia
1667 .get(start_pos)
1668 .cloned()
1669 .unwrap_or_default();
1670 let trailing = self
1671 .trailing_trivia
1672 .get(end_pos)
1673 .cloned()
1674 .unwrap_or_default();
1675 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1676 program.declarations.push(decl);
1677 program
1678 .declaration_trivia
1679 .push(DeclarationTrivia { leading, trailing });
1680 }
1681 Err(err) => {
1682 errors.push(self.attach_source_to_error(err));
1686 if self.pos == start_pos && !self.check(TokenType::Eof) {
1691 self.advance();
1692 }
1693 self.advance_to_sync_point();
1694 }
1695 }
1696 }
1697
1698 ParseResult { program, errors }
1699 }
1700
1701 fn attach_source_to_error(&self, err: ParseError) -> ParseError {
1706 match &self.source {
1707 Some(src) if err.line >= 1 => err.attach_source(src, &self.filename),
1708 _ => err,
1709 }
1710 }
1711
1712 fn advance_to_sync_point(&mut self) {
1717 let mut depth: i32 = 0;
1718 while !self.check(TokenType::Eof) {
1719 let tt = self.current().ttype.clone();
1720 if is_top_level_decl_kw_for_recovery(&tt) && depth <= 0 {
1724 return;
1725 }
1726 if matches!(tt, TokenType::LBrace) {
1727 depth += 1;
1728 } else if matches!(tt, TokenType::RBrace) {
1729 depth -= 1;
1730 }
1731 self.advance();
1732 }
1733 }
1734
1735 fn current(&self) -> &Token {
1738 if self.pos >= self.tokens.len() {
1739 self.tokens.last().unwrap() } else {
1741 &self.tokens[self.pos]
1742 }
1743 }
1744
1745 fn advance(&mut self) -> &Token {
1746 let idx = self.pos;
1747 if self.pos < self.tokens.len() {
1748 self.pos += 1;
1749 }
1750 &self.tokens[idx]
1751 }
1752
1753 fn check(&self, tt: TokenType) -> bool {
1754 self.current().ttype == tt
1755 }
1756
1757 fn consume(&mut self, expected: TokenType) -> Result<Token, ParseError> {
1758 let tok = self.current().clone();
1759 if tok.ttype != expected {
1760 return Err(ParseError {
1761 message: format!(
1762 "Expected {:?}, found {:?}('{}')",
1763 expected, tok.ttype, tok.value
1764 ),
1765 line: tok.line,
1766 column: tok.column,
1767 ..Default::default()
1768 });
1769 }
1770 self.pos += 1;
1771 Ok(tok)
1772 }
1773
1774 fn error(&self, message: &str) -> ParseError {
1776 let tok = self.current();
1777 ParseError { message: message.to_string(), line: tok.line, column: tok.column, ..Default::default() }
1778 }
1779
1780 fn consume_any_ident_or_kw(&mut self) -> Result<Token, ParseError> {
1782 let tok = self.current().clone();
1783 match tok.ttype {
1784 TokenType::Identifier
1785 | TokenType::Bool
1786 | TokenType::StringLit
1787 | TokenType::Integer
1788 | TokenType::Float => {
1789 self.pos += 1;
1790 Ok(tok)
1791 }
1792 _ => {
1793 if !tok.value.is_empty()
1795 && tok.value.chars().all(|c| c.is_alphanumeric() || c == '_')
1796 && tok.ttype != TokenType::Eof
1797 {
1798 self.pos += 1;
1799 Ok(tok)
1800 } else {
1801 Err(ParseError {
1802 message: format!(
1803 "Expected identifier or keyword value, found {:?}('{}')",
1804 tok.ttype, tok.value
1805 ),
1806 line: tok.line,
1807 column: tok.column,
1808 ..Default::default()
1809 })
1810 }
1811 }
1812 }
1813 }
1814
1815 fn consume_number(&mut self) -> Result<f64, ParseError> {
1816 let tok = self.current().clone();
1817 match tok.ttype {
1818 TokenType::Float | TokenType::Integer => {
1819 self.pos += 1;
1820 tok.value.parse::<f64>().map_err(|_| ParseError {
1821 message: format!("Invalid number '{}'", tok.value),
1822 line: tok.line,
1823 column: tok.column,
1824 ..Default::default()
1825 })
1826 }
1827 _ => Err(ParseError {
1828 message: format!("Expected number, found {:?}('{}')", tok.ttype, tok.value),
1829 line: tok.line,
1830 column: tok.column,
1831 ..Default::default()
1832 }),
1833 }
1834 }
1835
1836 fn parse_bool(&mut self) -> Result<bool, ParseError> {
1837 let tok = self.consume(TokenType::Bool)?;
1838 Ok(tok.value == "true")
1839 }
1840
1841 fn loc_of(&self, tok: &Token) -> Loc {
1842 Loc {
1843 line: tok.line,
1844 column: tok.column,
1845 }
1846 }
1847
1848 fn check_run_modifier(&self) -> bool {
1849 matches!(
1850 self.current().ttype,
1851 TokenType::As
1852 | TokenType::Within
1853 | TokenType::ConstrainedBy
1854 | TokenType::OnFailure
1855 | TokenType::OutputTo
1856 | TokenType::Effort
1857 )
1858 }
1859
1860 fn parse_string_list(&mut self) -> Result<Vec<String>, ParseError> {
1863 self.consume(TokenType::LBracket)?;
1864 let mut items = Vec::new();
1865 items.push(self.consume(TokenType::StringLit)?.value);
1866 while self.check(TokenType::Comma) {
1867 self.advance();
1868 items.push(self.consume(TokenType::StringLit)?.value);
1869 }
1870 self.consume(TokenType::RBracket)?;
1871 Ok(items)
1872 }
1873
1874 fn parse_bracketed_strings(&mut self) -> Result<Vec<String>, ParseError> {
1883 self.consume(TokenType::LBracket)?;
1884 let mut items = Vec::new();
1885 if !self.check(TokenType::RBracket) {
1886 items.push(self.consume(TokenType::StringLit)?.value);
1887 while self.check(TokenType::Comma) {
1888 self.advance();
1889 if self.check(TokenType::RBracket) {
1890 break; }
1892 items.push(self.consume(TokenType::StringLit)?.value);
1893 }
1894 }
1895 self.consume(TokenType::RBracket)?;
1896 Ok(items)
1897 }
1898
1899 fn parse_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1900 let mut names = Vec::new();
1901 names.push(self.consume(TokenType::Identifier)?.value);
1902 while self.check(TokenType::Comma) {
1903 self.advance();
1904 names.push(self.consume(TokenType::Identifier)?.value);
1905 }
1906 Ok(names)
1907 }
1908
1909 fn parse_bracketed_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1910 self.consume(TokenType::LBracket)?;
1911 let items = self.parse_extended_identifier_list()?;
1912 self.consume(TokenType::RBracket)?;
1913 Ok(items)
1914 }
1915
1916 fn parse_extended_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1917 let mut items = Vec::new();
1918 items.push(self.consume_any_ident_or_kw()?.value);
1919 while self.check(TokenType::Comma) {
1920 self.advance();
1921 items.push(self.consume_any_ident_or_kw()?.value);
1922 }
1923 Ok(items)
1924 }
1925
1926 fn parse_dotted_identifier(&mut self) -> Result<String, ParseError> {
1927 let mut parts = vec![self.consume_any_ident_or_kw()?.value];
1928 while self.check(TokenType::Dot) {
1929 self.advance();
1930 parts.push(self.consume_any_ident_or_kw()?.value);
1931 }
1932 Ok(parts.join("."))
1933 }
1934
1935 fn parse_expression_string(&mut self) -> Result<String, ParseError> {
1936 if self.check(TokenType::LBracket) {
1937 let items = self.parse_bracketed_dot_identifiers()?;
1938 return Ok(format!("[{}]", items.join(", ")));
1939 }
1940 self.parse_dotted_identifier()
1941 }
1942
1943 fn parse_bracketed_dot_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1944 self.consume(TokenType::LBracket)?;
1945 let mut items = vec![self.parse_dotted_identifier()?];
1946 while self.check(TokenType::Comma) {
1947 self.advance();
1948 items.push(self.parse_dotted_identifier()?);
1949 }
1950 self.consume(TokenType::RBracket)?;
1951 Ok(items)
1952 }
1953
1954 fn parse_argument_list(&mut self) -> Result<Vec<String>, ParseError> {
1955 let mut args = Vec::new();
1956 while !self.check(TokenType::RParen) {
1957 let tok = self.current().clone();
1958 match tok.ttype {
1959 TokenType::StringLit | TokenType::Integer | TokenType::Float => {
1960 self.advance();
1961 args.push(tok.value);
1962 }
1963 TokenType::Identifier => {
1964 self.advance();
1965 let mut val = tok.value;
1966 if self.check(TokenType::Dot) {
1967 self.advance();
1968 val.push('.');
1969 val.push_str(&self.consume_any_ident_or_kw()?.value);
1970 }
1971 args.push(val);
1972 }
1973 _ => {
1974 self.advance();
1975 let key = tok.value;
1976 if self.check(TokenType::Colon) {
1977 self.advance();
1978 let v = self.advance().value.clone();
1979 args.push(format!("{key}:{v}"));
1980 } else {
1981 args.push(key);
1982 }
1983 }
1984 }
1985 if self.check(TokenType::Comma) {
1986 self.advance();
1987 }
1988 }
1989 Ok(args)
1990 }
1991
1992 fn skip_value(&mut self) {
1994 match self.current().ttype {
1995 TokenType::LBracket => {
1996 self.advance();
1997 let mut depth = 1u32;
1998 while depth > 0 && !self.check(TokenType::Eof) {
1999 if self.check(TokenType::LBracket) {
2000 depth += 1;
2001 } else if self.check(TokenType::RBracket) {
2002 depth -= 1;
2003 }
2004 self.advance();
2005 }
2006 }
2007 TokenType::LBrace => {
2008 self.advance();
2009 let mut depth = 1u32;
2010 while depth > 0 && !self.check(TokenType::Eof) {
2011 if self.check(TokenType::LBrace) {
2012 depth += 1;
2013 } else if self.check(TokenType::RBrace) {
2014 depth -= 1;
2015 }
2016 self.advance();
2017 }
2018 }
2019 TokenType::Lt => {
2020 self.advance();
2022 let mut depth = 1u32;
2023 while depth > 0 && !self.check(TokenType::Eof) {
2024 if self.check(TokenType::Lt) {
2025 depth += 1;
2026 } else if self.check(TokenType::Gt) {
2027 depth -= 1;
2028 }
2029 self.advance();
2030 }
2031 }
2032 _ => {
2033 self.advance();
2034 while self.check(TokenType::Dot) {
2035 self.advance();
2036 self.advance();
2037 }
2038 }
2039 }
2040 }
2041
2042 fn skip_braced_block(&mut self) -> Result<(), ParseError> {
2044 self.consume(TokenType::LBrace)?;
2045 let mut depth = 1u32;
2046 while depth > 0 {
2047 if self.check(TokenType::Eof) {
2048 let tok = self.current();
2049 return Err(ParseError {
2050 message: "Unterminated block — expected '}'".to_string(),
2051 line: tok.line,
2052 column: tok.column,
2053 ..Default::default()
2054 });
2055 }
2056 if self.check(TokenType::LBrace) {
2057 depth += 1;
2058 } else if self.check(TokenType::RBrace) {
2059 depth -= 1;
2060 }
2061 self.advance();
2062 }
2063 Ok(())
2064 }
2065
2066 fn at_declaration_start(&self) -> bool {
2067 is_declaration_keyword(&self.current().ttype) || self.check(TokenType::Eof)
2068 }
2069
2070 fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
2073 let tok = self.current().clone();
2074
2075 if tok.ttype == TokenType::Budget && self.peek_is_identifier() {
2083 return self.parse_top_level_budget().map(Declaration::Budget);
2084 }
2085
2086 match tok.ttype {
2087 TokenType::Import => self.parse_import().map(Declaration::Import),
2088 TokenType::Persona => self.parse_persona().map(Declaration::Persona),
2089 TokenType::Context => self.parse_context().map(Declaration::Context),
2090 TokenType::Anchor => self.parse_anchor().map(Declaration::Anchor),
2091 TokenType::Memory => self.parse_memory().map(Declaration::Memory),
2092 TokenType::Tool => self.parse_tool().map(Declaration::Tool),
2093 TokenType::Type => self.parse_type_def().map(Declaration::Type),
2094 TokenType::Flow => self.parse_flow().map(Declaration::Flow),
2095 TokenType::Intent => self.parse_intent().map(Declaration::Intent),
2096 TokenType::Run => self.parse_run().map(Declaration::Run),
2097 TokenType::Let => self.parse_let().map(Declaration::Let),
2098 TokenType::Know | TokenType::Believe | TokenType::Speculate | TokenType::Doubt => {
2099 self.parse_epistemic_block().map(Declaration::Epistemic)
2100 }
2101 TokenType::Lambda => self.parse_lambda_data().map(Declaration::LambdaData),
2102
2103 TokenType::Agent => self.parse_agent().map(Declaration::Agent),
2105 TokenType::Shield => self.parse_shield().map(Declaration::Shield),
2106 TokenType::Window => self.parse_window().map(Declaration::Window),
2108 TokenType::Pix => self.parse_pix().map(Declaration::Pix),
2109 TokenType::Ledger => self.parse_ledger().map(Declaration::Ledger),
2110 TokenType::Psyche => self.parse_psyche().map(Declaration::Psyche),
2111 TokenType::Corpus => self.parse_corpus().map(Declaration::Corpus),
2112 TokenType::Dataspace => self.parse_dataspace().map(Declaration::Dataspace),
2113 TokenType::Ots => self.parse_ots().map(Declaration::Ots),
2114 TokenType::Mandate => self.parse_mandate().map(Declaration::Mandate),
2115 TokenType::Compute => self.parse_compute().map(Declaration::Compute),
2116 TokenType::Daemon => self.parse_daemon().map(Declaration::Daemon),
2117 TokenType::Extension => self.parse_extension().map(Declaration::Extension),
2118 TokenType::AxonStore => self.parse_axonstore().map(Declaration::AxonStore),
2119 TokenType::AxonEndpoint => self.parse_axonendpoint().map(Declaration::AxonEndpoint),
2120
2121 TokenType::Resource => self.parse_resource().map(Declaration::Resource),
2123 TokenType::Fabric => self.parse_fabric().map(Declaration::Fabric),
2124 TokenType::Manifest => self.parse_manifest().map(Declaration::Manifest),
2125 TokenType::Observe => self.parse_observe().map(Declaration::Observe),
2126
2127 TokenType::Reconcile => self.parse_reconcile().map(Declaration::Reconcile),
2129 TokenType::Lease => self.parse_lease().map(Declaration::Lease),
2130 TokenType::Ensemble => self.parse_ensemble().map(Declaration::Ensemble),
2131
2132 TokenType::Session => self.parse_session_definition().map(Declaration::Session),
2134 TokenType::Topology => self.parse_topology().map(Declaration::Topology),
2135
2136 TokenType::Socket => self.parse_socket().map(Declaration::Socket),
2138
2139 TokenType::Upstream => self.parse_upstream().map(Declaration::Upstream),
2141
2142 TokenType::Voice => self.parse_voice().map(Declaration::Voice),
2144
2145 TokenType::Cors => self.parse_cors().map(Declaration::Cors),
2147
2148 TokenType::Cache => self.parse_cache().map(Declaration::Cache),
2150 TokenType::Document => self.parse_document().map(Declaration::Document),
2151
2152 TokenType::Deliver => self.parse_deliver().map(Declaration::Deliver),
2154 TokenType::Notify => self.parse_notify().map(Declaration::Notify),
2155
2156 TokenType::Savant => self.parse_savant().map(Declaration::Savant),
2158
2159 TokenType::Synth => self.parse_synth().map(Declaration::Synth),
2161
2162 TokenType::Scope => self.parse_scope().map(Declaration::Scope),
2164
2165 TokenType::Credential => self.parse_credential().map(Declaration::Credential),
2167
2168 TokenType::Observable => self.parse_observable().map(Declaration::Observable),
2170
2171 TokenType::Witness => self.parse_witness().map(Declaration::Witness),
2173
2174 TokenType::Immune => self.parse_immune().map(Declaration::Immune),
2176 TokenType::Reflex => self.parse_reflex().map(Declaration::Reflex),
2177 TokenType::Heal => self.parse_heal().map(Declaration::Heal),
2178
2179 TokenType::Component => self.parse_component().map(Declaration::Component),
2181 TokenType::View => self.parse_view().map(Declaration::View),
2182
2183 TokenType::Channel => self.parse_channel().map(Declaration::Channel),
2185
2186 TokenType::Ingest
2189 | TokenType::Persist
2190 | TokenType::Retrieve
2191 | TokenType::Mutate
2192 | TokenType::Purge
2193 | TokenType::Transact => self.parse_generic_declaration(),
2194
2195 TokenType::Mcp => self.parse_generic_declaration(),
2197
2198 _ => {
2199 let hint = crate::smart_suggest::suggest_for(
2203 &tok.value,
2204 crate::smart_suggest::TOP_LEVEL_KEYWORD_NAMES,
2205 );
2206 let base = format!(
2207 "Unexpected token at top level: '{}' — expected declaration \
2208 (persona, context, anchor, flow, run, ...)",
2209 tok.value
2210 );
2211 let message = if hint.is_empty() {
2212 base
2213 } else {
2214 format!("{base}. {hint}")
2215 };
2216 Err(ParseError {
2217 message,
2218 line: tok.line,
2219 column: tok.column,
2220 ..Default::default()
2221 })
2222 }
2223 }
2224 }
2225
2226 fn parse_import(&mut self) -> Result<ImportNode, ParseError> {
2229 let tok = self.consume(TokenType::Import)?;
2230 let loc = self.loc_of(&tok);
2231
2232 let mut path_parts = Vec::new();
2233
2234 if self.check(TokenType::At) {
2236 self.advance();
2237 let first = self.consume(TokenType::Identifier)?;
2238 path_parts.push(format!("@{}", first.value));
2239 } else {
2240 let first = self.consume(TokenType::Identifier)?;
2241 path_parts.push(first.value);
2242 }
2243
2244 while self.check(TokenType::Dot) {
2245 self.advance();
2246 if self.check(TokenType::LBrace) {
2247 break;
2248 }
2249 let part = self.consume(TokenType::Identifier)?;
2250 path_parts.push(part.value);
2251 }
2252
2253 let mut names = Vec::new();
2254 if self.check(TokenType::LBrace) {
2255 self.advance();
2256 names = self.parse_identifier_list()?;
2257 self.consume(TokenType::RBrace)?;
2258 }
2259
2260 let mut allow_downgrade = false;
2269 if self.check(TokenType::At) {
2270 let at_tok = self.current().clone();
2271 self.advance();
2272 let ident = self.consume(TokenType::Identifier)?;
2273 if ident.value == "allow_downgrade" {
2274 allow_downgrade = true;
2275 } else {
2276 return Err(ParseError {
2277 message: format!(
2278 "unknown import annotation '@{}' — the only import annotation is \
2279 `@allow_downgrade` (the §115 epistemic-downgrade acknowledgment).",
2280 ident.value
2281 ),
2282 line: at_tok.line,
2283 column: at_tok.column,
2284 ..Default::default()
2285 });
2286 }
2287 }
2288
2289 let next_is_apx = self
2304 .tokens
2305 .get(self.pos + 1)
2306 .map(|t| t.value == "apx")
2307 .unwrap_or(false);
2308 if self.current().value == "with" && next_is_apx {
2309 let tok = self.current().clone();
2310 return Err(ParseError {
2311 message: "`import … with apx { … }` is RETRACTED (§111). The apx policy block was \
2312 parsed and silently DISCARDED — it never reached the IR, and no epistemic \
2313 dependency manager exists: no MEC/PCC verification, no EPR ranking, no \
2314 quarantine, no compliance gate. Declaring it verified nothing while \
2315 implying your supply chain was checked. Remove the `with apx { … }` \
2316 clause; the plain `import` resolves through the §115 Epistemic Module \
2317 System."
2318 .to_string(),
2319 line: tok.line,
2320 column: tok.column,
2321 ..Default::default()
2322 });
2323 }
2324
2325 Ok(ImportNode {
2326 module_path: path_parts,
2327 names,
2328 allow_downgrade,
2329 loc,
2330 leading_trivia: Vec::new(),
2331 trailing_trivia: Vec::new(),
2332 })
2333 }
2334
2335 fn parse_persona(&mut self) -> Result<PersonaDefinition, ParseError> {
2338 let tok = self.consume(TokenType::Persona)?;
2339 let loc = self.loc_of(&tok);
2340 let name = self.consume(TokenType::Identifier)?.value;
2341 self.consume(TokenType::LBrace)?;
2342
2343 let mut node = PersonaDefinition {
2344 name,
2345 domain: Vec::new(),
2346 tone: String::new(),
2347 confidence_threshold: None,
2348 cite_sources: None,
2349 refuse_if: Vec::new(),
2350 language: String::new(),
2351 description: String::new(),
2352 loc,
2353 leading_trivia: Vec::new(),
2354 trailing_trivia: Vec::new(),
2355 };
2356
2357 while !self.check(TokenType::RBrace) {
2358 let field_name = self.current().value.clone();
2359 self.advance();
2360 self.consume(TokenType::Colon)?;
2361
2362 match field_name.as_str() {
2363 "domain" => node.domain = self.parse_string_list()?,
2364 "tone" => node.tone = self.consume_any_ident_or_kw()?.value,
2365 "confidence_threshold" => node.confidence_threshold = Some(self.consume_number()?),
2366 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2367 "refuse_if" => node.refuse_if = self.parse_bracketed_identifiers()?,
2368 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2369 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2370 _ => self.skip_value(),
2371 }
2372 }
2373 self.consume(TokenType::RBrace)?;
2374 Ok(node)
2375 }
2376
2377 fn parse_context(&mut self) -> Result<ContextDefinition, ParseError> {
2380 let tok = self.consume(TokenType::Context)?;
2381 let loc = self.loc_of(&tok);
2382 let name = self.consume(TokenType::Identifier)?.value;
2383 self.consume(TokenType::LBrace)?;
2384
2385 let mut node = ContextDefinition {
2386 name,
2387 memory_scope: String::new(),
2388 language: String::new(),
2389 depth: String::new(),
2390 max_tokens: None,
2391 temperature: None,
2392 cite_sources: None,
2393 now_tz: None,
2394 loc,
2395 leading_trivia: Vec::new(),
2396 trailing_trivia: Vec::new(),
2397 };
2398
2399 while !self.check(TokenType::RBrace) {
2400 let field_name = self.current().value.clone();
2401 self.advance();
2402 self.consume(TokenType::Colon)?;
2403
2404 match field_name.as_str() {
2405 "memory" => node.memory_scope = self.consume_any_ident_or_kw()?.value,
2406 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2407 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
2408 "now" => node.now_tz = Some(self.consume(TokenType::StringLit)?.value),
2410 "max_tokens" => {
2411 node.max_tokens = Some(
2412 self.consume(TokenType::Integer)?
2413 .value
2414 .parse::<i64>()
2415 .unwrap_or(0),
2416 )
2417 }
2418 "temperature" => node.temperature = Some(self.consume_number()?),
2419 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2420 _ => self.skip_value(),
2421 }
2422 }
2423 self.consume(TokenType::RBrace)?;
2424 Ok(node)
2425 }
2426
2427 fn parse_anchor(&mut self) -> Result<AnchorConstraint, ParseError> {
2430 let tok = self.consume(TokenType::Anchor)?;
2431 let loc = self.loc_of(&tok);
2432 let name = self.consume(TokenType::Identifier)?.value;
2433 self.consume(TokenType::LBrace)?;
2434
2435 let mut node = AnchorConstraint {
2436 name,
2437 require: String::new(),
2438 reject: Vec::new(),
2439 enforce: String::new(),
2440 description: String::new(),
2441 confidence_floor: None,
2442 unknown_response: String::new(),
2443 on_violation: String::new(),
2444 on_violation_target: String::new(),
2445 loc,
2446 leading_trivia: Vec::new(),
2447 trailing_trivia: Vec::new(),
2448 };
2449
2450 while !self.check(TokenType::RBrace) {
2451 let field_name = self.current().value.clone();
2452 self.advance();
2453 self.consume(TokenType::Colon)?;
2454
2455 match field_name.as_str() {
2456 "require" => node.require = self.consume_any_ident_or_kw()?.value,
2457 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2458 "reject" => node.reject = self.parse_bracketed_identifiers()?,
2459 "enforce" => node.enforce = self.consume_any_ident_or_kw()?.value,
2460 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
2461 "unknown_response" => {
2462 node.unknown_response = self.consume(TokenType::StringLit)?.value
2463 }
2464 "on_violation" => {
2465 let action = self.consume_any_ident_or_kw()?.value;
2467 node.on_violation = action.clone();
2468 if action == "raise" || action == "fallback" {
2469 node.on_violation_target = self.consume_any_ident_or_kw()?.value;
2470 }
2471 }
2472 _ => self.skip_value(),
2473 }
2474 }
2475 self.consume(TokenType::RBrace)?;
2476 Ok(node)
2477 }
2478
2479 fn parse_memory(&mut self) -> Result<MemoryDefinition, ParseError> {
2482 let tok = self.consume(TokenType::Memory)?;
2483 let loc = self.loc_of(&tok);
2484 let name = self.consume(TokenType::Identifier)?.value;
2485 self.consume(TokenType::LBrace)?;
2486
2487 let mut node = MemoryDefinition {
2488 name,
2489 store: String::new(),
2490 backend: String::new(),
2491 retrieval: String::new(),
2492 decay: String::new(),
2493 loc,
2494 leading_trivia: Vec::new(),
2495 trailing_trivia: Vec::new(),
2496 };
2497
2498 while !self.check(TokenType::RBrace) {
2499 let field_name = self.current().value.clone();
2500 self.advance();
2501 self.consume(TokenType::Colon)?;
2502
2503 match field_name.as_str() {
2504 "store" => node.store = self.consume_any_ident_or_kw()?.value,
2505 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
2506 "retrieval" => node.retrieval = self.consume_any_ident_or_kw()?.value,
2507 "decay" => {
2508 if self.check(TokenType::Duration) {
2509 node.decay = self.advance().value.clone();
2510 } else {
2511 node.decay = self.consume_any_ident_or_kw()?.value;
2512 }
2513 }
2514 _ => self.skip_value(),
2515 }
2516 }
2517 self.consume(TokenType::RBrace)?;
2518 Ok(node)
2519 }
2520
2521 fn parse_tool(&mut self) -> Result<ToolDefinition, ParseError> {
2524 let tok = self.consume(TokenType::Tool)?;
2525 let loc = self.loc_of(&tok);
2526 let name = self.consume(TokenType::Identifier)?.value;
2527 self.consume(TokenType::LBrace)?;
2528
2529 let mut node = ToolDefinition {
2530 name,
2531 provider: String::new(),
2532 max_results: None,
2533 filter_expr: String::new(),
2534 timeout: String::new(),
2535 runtime: String::new(),
2536 resource_ref: String::new(),
2537 sandbox: None,
2538 effects: None,
2539 parameters: Vec::new(),
2540 output_type: None,
2541 requires: Vec::new(),
2542 secret: String::new(),
2543 secret_partition: String::new(),
2544 target: None,
2545 risk: None,
2546 argv: Vec::new(),
2547 cache: String::new(),
2548 scrape: None,
2549 loc,
2550 leading_trivia: Vec::new(),
2551 trailing_trivia: Vec::new(),
2552 };
2553
2554 let mut unknown_fields: Vec<(String, u32, u32)> = Vec::new();
2561
2562 while !self.check(TokenType::RBrace) {
2563 let field_tok = self.current().clone();
2564 let field_name = field_tok.value.clone();
2565 self.advance();
2566 self.consume(TokenType::Colon)?;
2567
2568 match field_name.as_str() {
2569 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
2570 "max_results" => {
2571 node.max_results = Some(
2572 self.consume(TokenType::Integer)?
2573 .value
2574 .parse::<i64>()
2575 .unwrap_or(0),
2576 )
2577 }
2578 "filter" => node.filter_expr = self.parse_filter_expression()?,
2579 "timeout" => node.timeout = self.consume(TokenType::Duration)?.value,
2580 "runtime" => node.runtime = self.consume_any_ident_or_kw()?.value,
2581 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
2585 "sandbox" => node.sandbox = Some(self.parse_bool()?),
2586 "effects" => node.effects = Some(self.parse_effect_row()?),
2587 "parameters" => node.parameters = self.parse_tool_param_schema()?,
2589 "output_type" => node.output_type = Some(self.parse_output_type_string()?),
2590 "requires" => {
2596 let bracket_tok = self.current().clone();
2597 let items = self.parse_bracketed_dot_identifiers()?;
2598 for slug in &items {
2599 if !is_valid_capability_slug(slug) {
2600 return Err(ParseError {
2601 message: format!(
2602 "Invalid capability slug '{slug}' in tool '{}' \
2603 `requires:`. Scope slugs must match \
2604 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — the same \
2605 grammar as `credential.grants`. Examples: \
2606 `w_organization_social`, `video.publish`.",
2607 node.name
2608 ),
2609 line: bracket_tok.line,
2610 column: bracket_tok.column,
2611 ..Default::default()
2612 });
2613 }
2614 }
2615 node.requires = items;
2616 }
2617 "secret" => node.secret = self.parse_dotted_identifier()?,
2621 "secret_partition" => {
2627 node.secret_partition = self.consume_any_ident_or_kw()?.value
2628 }
2629 "target" => node.target = Some(self.consume_any_ident_or_kw()?.value),
2631 "risk" => node.risk = Some(self.consume_any_ident_or_kw()?.value),
2632 "argv" => node.argv = self.parse_bracketed_strings()?,
2636 "cache" => node.cache = self.consume_any_ident_or_kw()?.value,
2639 "scrape" => node.scrape = Some(self.parse_scrape_spec()?),
2642 _ => {
2643 unknown_fields.push((field_name, field_tok.line, field_tok.column));
2644 self.skip_value();
2645 }
2646 }
2647 }
2648 self.consume(TokenType::RBrace)?;
2649
2650 if node.target.is_some() || node.scrape.is_some() {
2658 if let Some((field_name, line, column)) = unknown_fields.into_iter().next() {
2659 let (surface, valid) = if node.target.is_some() {
2660 (
2661 "technician tool (§Fase 84 D84.13)",
2662 "provider, parameters, output_type, timeout, effects, target, risk, argv",
2663 )
2664 } else {
2665 (
2666 "web-acquisition tool (§Fase 98 D98.2)",
2667 "provider, parameters, output_type, timeout, effects, secret, \
2668 secret_partition, cache, scrape",
2669 )
2670 };
2671 return Err(ParseError {
2672 message: format!(
2673 "unknown field `{field_name}` in {surface} `{}` — this tool uses \
2674 strict field checking; valid fields: {valid}",
2675 node.name
2676 ),
2677 line,
2678 column,
2679 ..Default::default()
2680 });
2681 }
2682 }
2683 Ok(node)
2684 }
2685
2686 fn parse_scrape_spec(&mut self) -> Result<crate::ast::ScrapeSpec, ParseError> {
2691 let open = self.consume(TokenType::LBrace)?;
2692 let loc = self.loc_of(&open);
2693 let mut spec = crate::ast::ScrapeSpec {
2694 loc,
2695 ..Default::default()
2696 };
2697 while !self.check(TokenType::RBrace) {
2698 let field_tok = self.current().clone();
2699 let field_name = field_tok.value.clone();
2700 self.advance();
2701 self.consume(TokenType::Colon)?;
2702 match field_name.as_str() {
2703 "engine" => spec.engine = Some(self.consume_any_ident_or_kw()?.value),
2704 "impersonate" => spec.impersonate = Some(self.consume_any_ident_or_kw()?.value),
2705 "render_wait" => spec.render_wait = Some(self.consume(TokenType::Duration)?.value),
2706 "proxy" => spec.proxy = self.parse_dotted_identifier()?,
2707 "respect_robots" => spec.respect_robots = Some(self.parse_bool()?),
2708 "extract" => spec.extract = self.parse_bracketed_strings()?,
2709 "adaptive" => spec.adaptive = Some(self.parse_bool()?),
2710 "similarity_floor" => spec.similarity_floor = self.parse_optional_float(),
2711 "follow" => spec.follow = self.consume(TokenType::StringLit)?.value,
2712 "max_depth" => {
2713 spec.max_depth =
2714 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2715 }
2716 "max_pages" => {
2717 spec.max_pages =
2718 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2719 }
2720 "concurrency" => {
2721 spec.concurrency =
2722 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2723 }
2724 "politeness" => spec.politeness = self.consume_any_ident_or_kw()?.value,
2725 "checkpoint" => spec.checkpoint = self.consume_any_ident_or_kw()?.value,
2726 other => {
2727 return Err(self.error(&format!(
2728 "unknown scrape field `{other}` — the `scrape: {{ … }}` block is a \
2729 closed catalog (§Fase 98 D98.2); valid fields: engine, impersonate, \
2730 render_wait, proxy, respect_robots, extract, adaptive, \
2731 similarity_floor, follow, max_depth, max_pages, concurrency, \
2732 politeness, checkpoint"
2733 )));
2734 }
2735 }
2736 }
2737 self.consume(TokenType::RBrace)?;
2738 Ok(spec)
2739 }
2740
2741 fn parse_tool_param_schema(&mut self) -> Result<Vec<Parameter>, ParseError> {
2747 self.consume(TokenType::LBrace)?;
2748 let mut params = Vec::new();
2749 while !self.check(TokenType::RBrace) {
2750 let name = self.consume_any_ident_or_kw()?;
2754 let ploc = self.loc_of(&name);
2755 self.consume(TokenType::Colon)?;
2756 let type_expr = self.parse_type_expr()?;
2757 params.push(Parameter {
2758 name: name.value,
2759 type_expr,
2760 loc: ploc,
2761 });
2762 if self.check(TokenType::Comma) {
2763 self.advance();
2764 } else {
2765 break;
2766 }
2767 }
2768 self.consume(TokenType::RBrace)?;
2769 Ok(params)
2770 }
2771
2772 fn parse_filter_expression(&mut self) -> Result<String, ParseError> {
2773 let name = self.consume_any_ident_or_kw()?.value;
2774 if self.check(TokenType::LParen) {
2775 self.advance();
2776 let mut parts = vec![name, "(".to_string()];
2777 while !self.check(TokenType::RParen) {
2778 parts.push(self.advance().value.clone());
2779 }
2780 self.consume(TokenType::RParen)?;
2781 parts.push(")".to_string());
2782 Ok(parts.join(""))
2783 } else {
2784 Ok(name)
2785 }
2786 }
2787
2788 fn parse_effect_row(&mut self) -> Result<EffectRow, ParseError> {
2789 let tok = self.consume(TokenType::Lt)?;
2790 let loc = self.loc_of(&tok);
2791 let mut effects = Vec::new();
2792 let mut epistemic_level = String::new();
2793
2794 while !self.check(TokenType::Gt) {
2795 let name = self.consume_any_ident_or_kw()?.value;
2796 if self.check(TokenType::Colon) {
2797 self.advance();
2798 let level = self.parse_qualifier_value()?;
2815 if name == "epistemic" {
2816 epistemic_level = level;
2817 } else {
2818 effects.push(format!("{name}:{level}"));
2819 }
2820 } else {
2821 effects.push(name);
2822 }
2823 if self.check(TokenType::Comma) {
2824 self.advance();
2825 }
2826 }
2827 self.consume(TokenType::Gt)?;
2828
2829 Ok(EffectRow {
2830 effects,
2831 epistemic_level,
2832 loc,
2833 })
2834 }
2835
2836 fn parse_qualifier_value(&mut self) -> Result<String, ParseError> {
2845 let mut buf = self.consume_dotted_slug_segment()?;
2846 loop {
2847 let sep = if self.check(TokenType::Dot) {
2848 '.'
2849 } else if self.check(TokenType::Colon) {
2850 ':'
2851 } else {
2852 break;
2853 };
2854 self.advance();
2855 let part = self.consume_dotted_slug_segment()?;
2856 buf.push(sep);
2857 buf.push_str(&part);
2858 }
2859 Ok(buf)
2860 }
2861
2862 fn consume_dotted_slug_segment(&mut self) -> Result<String, ParseError> {
2874 let first = self.consume_any_ident_or_kw()?;
2875 let mut buf = first.value.clone();
2876 let mut next_line = first.line;
2877 let mut next_col = first.column + first.value.chars().count() as u32;
2878 loop {
2879 let cur = self.current();
2880 let is_segment_token = matches!(cur.ttype, TokenType::Identifier | TokenType::Integer,);
2881 if !is_segment_token {
2882 break;
2883 }
2884 if cur.line != next_line || cur.column != next_col {
2885 break;
2886 }
2887 buf.push_str(&cur.value);
2888 next_col = cur.column + cur.value.chars().count() as u32;
2889 next_line = cur.line;
2890 self.pos += 1;
2891 }
2892 Ok(buf)
2893 }
2894
2895 fn parse_type_def(&mut self) -> Result<TypeDefinition, ParseError> {
2898 let tok = self.consume(TokenType::Type)?;
2899 let loc = self.loc_of(&tok);
2900 let name = self.consume(TokenType::Identifier)?.value;
2901
2902 let mut node = TypeDefinition {
2903 name,
2904 fields: Vec::new(),
2905 range_constraint: None,
2906 where_clause: None,
2907 compliance: Vec::new(),
2908 loc: loc.clone(),
2909 leading_trivia: Vec::new(),
2910 trailing_trivia: Vec::new(),
2911 };
2912
2913 if self.check(TokenType::LParen) {
2915 self.advance();
2916 let min_val = self.consume_number()?;
2917 self.consume(TokenType::DotDot)?;
2918 let max_val = self.consume_number()?;
2919 self.consume(TokenType::RParen)?;
2920 node.range_constraint = Some(RangeConstraint {
2921 min_value: min_val,
2922 max_value: max_val,
2923 loc: loc.clone(),
2924 });
2925 }
2926
2927 if self.check(TokenType::Where) {
2929 self.advance();
2930 let mut expr_parts = Vec::new();
2931 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
2932 if self.check(TokenType::Eof) {
2933 break;
2934 }
2935 expr_parts.push(self.advance().value.clone());
2936 }
2937 node.where_clause = Some(WhereClause {
2938 expression: expr_parts.join(" "),
2939 loc: loc.clone(),
2940 });
2941 }
2942
2943 if self.check(TokenType::Identifier) && self.current().value == "compliance" {
2946 self.advance();
2947 node.compliance = self.parse_bracketed_identifiers()?;
2948 }
2949
2950 if self.check(TokenType::LBrace) {
2952 self.advance();
2953 while !self.check(TokenType::RBrace) {
2954 let field_name = self.consume(TokenType::Identifier)?;
2955 let field_loc = self.loc_of(&field_name);
2956 self.consume(TokenType::Colon)?;
2957 let type_expr = self.parse_type_expr()?;
2958 node.fields.push(TypeField {
2959 name: field_name.value,
2960 type_expr,
2961 loc: field_loc,
2962 });
2963 if self.check(TokenType::Comma) {
2964 self.advance();
2965 }
2966 }
2967 self.consume(TokenType::RBrace)?;
2968 }
2969
2970 Ok(node)
2971 }
2972
2973 fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
2974 if self.check(TokenType::LBracket) {
2980 let open = self.current().clone();
2981 self.advance();
2982 let inner = self.parse_type_expr()?;
2983 self.consume(TokenType::RBracket)?;
2984 let mut optional = false;
2985 if self.check(TokenType::Question) {
2986 self.advance();
2987 optional = true;
2988 }
2989 return Ok(TypeExpr {
2990 name: "List".to_string(),
2991 generic_param: if inner.generic_param.is_empty() {
2992 inner.name
2993 } else {
2994 format!("{}<{}>", inner.name, inner.generic_param)
2995 },
2996 optional,
2997 loc: self.loc_of(&open),
2998 });
2999 }
3000 let name_tok = self.consume(TokenType::Identifier)?;
3001 let loc = self.loc_of(&name_tok);
3002 let mut generic_param = String::new();
3003 let mut optional = false;
3004
3005 if self.check(TokenType::Lt) {
3006 self.advance();
3007 let inner = self.parse_type_expr()?;
3017 generic_param = if inner.generic_param.is_empty() {
3018 inner.name
3019 } else {
3020 format!("{}<{}>", inner.name, inner.generic_param)
3021 };
3022 self.consume(TokenType::Gt)?;
3023 }
3024 if self.check(TokenType::LBracket) {
3028 self.advance();
3029 if matches!(self.current().ttype, TokenType::Integer | TokenType::Float) {
3030 generic_param = self.advance().value.clone();
3031 } else {
3032 let inner = self.parse_type_expr()?;
3033 generic_param = if inner.generic_param.is_empty() {
3034 inner.name
3035 } else {
3036 format!("{}[{}]", inner.name, inner.generic_param)
3037 };
3038 }
3039 self.consume(TokenType::RBracket)?;
3040 }
3041 if self.check(TokenType::Question) {
3042 self.advance();
3043 optional = true;
3044 }
3045
3046 Ok(TypeExpr {
3047 name: name_tok.value,
3048 generic_param,
3049 optional,
3050 loc,
3051 })
3052 }
3053
3054 fn parse_output_type_string(&mut self) -> Result<String, ParseError> {
3079 let expr = self.parse_type_expr()?;
3080 let mut s = expr.name;
3081 if !expr.generic_param.is_empty() {
3082 s.push('<');
3083 s.push_str(&expr.generic_param);
3084 s.push('>');
3085 }
3086 if expr.optional {
3087 s.push('?');
3088 }
3089 Ok(s)
3090 }
3091
3092 fn parse_flow(&mut self) -> Result<FlowDefinition, ParseError> {
3095 let tok = self.consume(TokenType::Flow)?;
3096 let loc = self.loc_of(&tok);
3097 let name = self.consume(TokenType::Identifier)?.value;
3098
3099 self.consume(TokenType::LParen)?;
3100 let mut parameters = Vec::new();
3101 if !self.check(TokenType::RParen) {
3102 parameters = self.parse_param_list()?;
3103 }
3104 self.consume(TokenType::RParen)?;
3105
3106 let mut return_type = None;
3107 if self.check(TokenType::Arrow) {
3108 self.advance();
3109 return_type = Some(self.parse_type_expr()?);
3110 }
3111
3112 self.consume(TokenType::LBrace)?;
3113 let mut body = Vec::new();
3114 while !self.check(TokenType::RBrace) {
3115 body.push(self.parse_flow_step()?);
3116 }
3117 self.consume(TokenType::RBrace)?;
3118
3119 Ok(FlowDefinition {
3120 name,
3121 parameters,
3122 return_type,
3123 body,
3124 loc,
3125 leading_trivia: Vec::new(),
3126 trailing_trivia: Vec::new(),
3127 })
3128 }
3129
3130 fn parse_param_list(&mut self) -> Result<Vec<Parameter>, ParseError> {
3131 let mut params = Vec::new();
3132
3133 let name = self.consume(TokenType::Identifier)?;
3134 let ploc = self.loc_of(&name);
3135 self.consume(TokenType::Colon)?;
3136 let type_expr = self.parse_type_expr()?;
3137 params.push(Parameter {
3138 name: name.value,
3139 type_expr,
3140 loc: ploc,
3141 });
3142
3143 while self.check(TokenType::Comma) {
3144 self.advance();
3145 let name = self.consume(TokenType::Identifier)?;
3146 let ploc = self.loc_of(&name);
3147 self.consume(TokenType::Colon)?;
3148 let type_expr = self.parse_type_expr()?;
3149 params.push(Parameter {
3150 name: name.value,
3151 type_expr,
3152 loc: ploc,
3153 });
3154 }
3155 Ok(params)
3156 }
3157
3158 fn parse_flow_step(&mut self) -> Result<FlowStep, ParseError> {
3161 let tok = self.current().clone();
3162
3163 match tok.ttype {
3164 TokenType::Know | TokenType::Believe | TokenType::Speculate
3170 if self
3171 .tokens
3172 .get(self.pos + 1)
3173 .is_some_and(|t| t.ttype == TokenType::LBrace) =>
3174 {
3175 let block = self.parse_epistemic_block()?;
3176 self.hoisted.push(Declaration::Epistemic(block));
3177 self.parse_flow_step()
3178 }
3179 TokenType::Doubt
3180 if self
3181 .tokens
3182 .get(self.pos + 1)
3183 .is_some_and(|t| t.ttype == TokenType::LBrace) =>
3184 {
3185 let block = self.parse_epistemic_block()?;
3186 self.hoisted.push(Declaration::Epistemic(block));
3187 self.parse_flow_step()
3188 }
3189 TokenType::Step => self.parse_step().map(FlowStep::Step),
3190 TokenType::If => self.parse_if().map(FlowStep::If),
3191 TokenType::For => self.parse_for_in().map(FlowStep::ForIn),
3192 TokenType::Let => self.parse_let().map(FlowStep::Let),
3193 TokenType::Return => self.parse_return().map(FlowStep::Return),
3194 TokenType::Break => self.parse_break().map(FlowStep::Break),
3195 TokenType::Continue => self.parse_continue().map(FlowStep::Continue),
3196 TokenType::Lambda => self.parse_lambda_data_apply().map(FlowStep::LambdaDataApply),
3197
3198 TokenType::Probe => self.parse_flow_step_simple("probe").map(|l| FlowStep::Probe(ProbeStep { target: l.1, fields: Vec::new(), loc: l.0 })),
3200 TokenType::Reason => self.parse_flow_step_simple("reason").map(|l| FlowStep::Reason(ReasonStep { strategy: String::new(), target: l.1, loc: l.0 })),
3201 TokenType::Validate => self.parse_flow_step_simple("validate").map(|l| FlowStep::Validate(ValidateStep { target: l.1, rule: String::new(), loc: l.0 })),
3202 TokenType::Refine => self.parse_flow_step_simple("refine").map(|l| FlowStep::Refine(RefineStep { target: l.1, strategy: String::new(), loc: l.0 })),
3203 TokenType::Weave => self.parse_weave_step(),
3204 TokenType::Use => self.parse_use_step(),
3205 TokenType::Remember => self.parse_remember_step(),
3206 TokenType::Recall => self.parse_recall_step(),
3207 TokenType::Par => self.parse_par_block().map(FlowStep::Par),
3208 TokenType::Hibernate => self.parse_hibernate_step(),
3209 TokenType::Deliberate => self.parse_block_step("deliberate").map(|l| FlowStep::Deliberate(DeliberateBlock { loc: l })),
3210 TokenType::Consensus => self.parse_block_step("consensus").map(|l| FlowStep::Consensus(ConsensusBlock { loc: l })),
3211 TokenType::Forge => self.parse_forge_step().map(FlowStep::Forge),
3212 TokenType::Focus => self.parse_focus_step(),
3213 TokenType::Grad => self.parse_grad_step(),
3214 TokenType::Associate => self.parse_associate_step(),
3215 TokenType::Aggregate => self.parse_aggregate_step(),
3216 TokenType::Explore => self.parse_explore_step(),
3217 TokenType::Ingest => self.parse_ingest_step(),
3218 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 })),
3219 TokenType::Stream => self.parse_stream_block().map(FlowStep::Stream),
3225 TokenType::Navigate => self.parse_navigate_step(),
3226 TokenType::Drill => self.parse_drill_step(),
3227 TokenType::Trail => self.parse_flow_step_simple("trail").map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 })),
3228 TokenType::Corroborate => self.parse_corroborate_step(),
3229 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 })),
3230 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 })),
3231 TokenType::Compute => self.parse_compute_apply().map(FlowStep::ComputeApply),
3236 TokenType::Listen => self.parse_listen_step(),
3237 TokenType::Daemon => self.parse_flow_step_simple("daemon").map(|l| FlowStep::DaemonStep(DaemonStepNode { daemon_ref: l.1, loc: l.0 })),
3238 TokenType::Emit => self.parse_emit_step(),
3240 TokenType::Mint => self.parse_mint_step(),
3242 TokenType::Rotate => self.parse_rotate_step(),
3245 TokenType::Publish => self.parse_publish_step(),
3246 TokenType::Discover => self.parse_discover_step(),
3247 TokenType::Persist => self.parse_persist_step(),
3248 TokenType::Retrieve => self.parse_retrieve_step(),
3249 TokenType::Mutate => self.parse_mutate_step(),
3250 TokenType::Purge => self.parse_store_where_step().map(|(loc, store_name, where_expr)| FlowStep::Purge(PurgeStep { store_name, where_expr, loc })),
3251 TokenType::Transact => self.parse_block_step("transact").map(|l| FlowStep::Transact(TransactBlock { loc: l })),
3252 TokenType::Warden => self.parse_warden().map(FlowStep::Warden),
3254 TokenType::Quant => self.parse_quant().map(FlowStep::Quant),
3256 TokenType::Yield => self.parse_yield().map(FlowStep::Yield),
3258 TokenType::Run => self.parse_run().map(FlowStep::Run),
3262
3263 _ => {
3264 let hint = crate::smart_suggest::suggest_for(
3268 &tok.value,
3269 crate::smart_suggest::FLOW_BODY_KEYWORD_NAMES,
3270 );
3271 let base = format!(
3272 "Unexpected token in flow body: '{}' — expected step, if, for, let, return, ...",
3273 tok.value
3274 );
3275 let message = if hint.is_empty() {
3276 base
3277 } else {
3278 format!("{base}. {hint}")
3279 };
3280 Err(ParseError {
3281 message,
3282 line: tok.line,
3283 column: tok.column,
3284 ..Default::default()
3285 })
3286 }
3287 }
3288 }
3289
3290 fn parse_step(&mut self) -> Result<StepNode, ParseError> {
3293 let tok = self.consume(TokenType::Step)?;
3294 let loc = self.loc_of(&tok);
3295 let name = self.consume(TokenType::Identifier)?.value;
3296
3297 let mut persona_ref = String::new();
3298 if self.check(TokenType::Use) {
3299 self.advance();
3300 persona_ref = self.consume_any_ident_or_kw()?.value;
3301 }
3302
3303 self.consume(TokenType::LBrace)?;
3304
3305 let mut node = StepNode {
3306 name,
3307 persona_ref,
3308 given: String::new(),
3309 ask: String::new(),
3310 output_type: String::new(),
3311 confidence_floor: None,
3312 navigate_ref: String::new(),
3313 apply_ref: String::new(),
3314 requires_context: None,
3315 now_tz: None,
3316 guards: Vec::new(),
3317 pix_ops: Vec::new(),
3318 loc,
3319 };
3320
3321 while !self.check(TokenType::RBrace) {
3322 let inner = self.current().clone();
3323
3324 match inner.ttype {
3325 TokenType::Given => {
3326 self.advance();
3327 self.consume(TokenType::Colon)?;
3328 node.given = self.parse_expression_string()?;
3329 }
3330 TokenType::Ask => {
3331 self.advance();
3332 self.consume(TokenType::Colon)?;
3333 node.ask = self.consume(TokenType::StringLit)?.value;
3334 }
3335 TokenType::Output => {
3336 self.advance();
3346 self.consume(TokenType::Colon)?;
3347 node.output_type = self.parse_output_type_string()?;
3348 }
3349 TokenType::Navigate
3356 if self
3357 .tokens
3358 .get(self.pos + 1)
3359 .is_some_and(|t| t.ttype != TokenType::Colon) =>
3360 {
3361 let op = self.parse_navigate_step()?;
3362 node.pix_ops.push(op);
3363 }
3364 TokenType::Drill => {
3365 let op = self.parse_drill_step()?;
3366 node.pix_ops.push(op);
3367 }
3368 TokenType::Trail => {
3369 let op = self
3370 .parse_flow_step_simple("trail")
3371 .map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 }))?;
3372 node.pix_ops.push(op);
3373 }
3374 TokenType::Validate => {
3379 let tok = self.current().clone();
3380 self.advance();
3381 let target = self.consume_any_ident_or_kw()?.value.clone();
3382 let mut rule = String::new();
3383 if self.current().value == "against" {
3384 self.advance();
3385 self.consume(TokenType::Colon)?;
3386 rule = self.consume_any_ident_or_kw()?.value.clone();
3387 }
3388 node.pix_ops.push(FlowStep::Validate(ValidateStep {
3389 target,
3390 rule,
3391 loc: Loc { line: tok.line, column: tok.column },
3392 }));
3393 }
3394 TokenType::Navigate => {
3395 self.advance();
3396 self.consume(TokenType::Colon)?;
3397 node.navigate_ref = self.parse_dotted_identifier()?;
3398 }
3399 TokenType::Identifier if inner.value == "confidence_floor" => {
3400 self.advance();
3401 self.consume(TokenType::Colon)?;
3402 node.confidence_floor = Some(self.consume_number()?);
3403 }
3404 TokenType::Identifier if inner.value == "apply" => {
3405 self.advance();
3406 self.consume(TokenType::Colon)?;
3407 node.apply_ref = self.consume_any_ident_or_kw()?.value;
3408 }
3409 TokenType::Identifier if inner.value == "requires_context" => {
3417 self.advance();
3418 self.consume(TokenType::Colon)?;
3419 let num = self.current().clone();
3420 let bad = |tok: &crate::tokens::Token| ParseError {
3421 message: format!(
3422 "`requires_context:` must be a positive integer token count \
3423 (got '{}')",
3424 tok.value
3425 ),
3426 line: tok.line,
3427 column: tok.column,
3428 ..Default::default()
3429 };
3430 if num.ttype != TokenType::Integer {
3431 return Err(bad(&num));
3432 }
3433 let value = num.value.parse::<u32>().map_err(|_| bad(&num))?;
3434 self.advance();
3435 node.requires_context = Some(value);
3436 }
3437 TokenType::Identifier if inner.value == "now" => {
3442 self.advance();
3443 self.consume(TokenType::Colon)?;
3444 let tz = self.current().clone();
3445 if tz.ttype != TokenType::StringLit {
3446 return Err(ParseError {
3447 message: format!(
3448 "`now:` must be an IANA timezone string literal like \
3449 \"America/Bogota\" or \"UTC\" (got '{}')",
3450 tz.value
3451 ),
3452 line: tz.line,
3453 column: tz.column,
3454 ..Default::default()
3455 });
3456 }
3457 self.advance();
3458 node.now_tz = Some(tz.value);
3459 }
3460 TokenType::Use => {
3470 let tool = self
3471 .tokens
3472 .get(self.pos + 1)
3473 .map(|t| t.value.as_str())
3474 .filter(|v| !v.is_empty())
3475 .unwrap_or("<Tool>");
3476 return Err(ParseError {
3477 message: format!(
3478 "`use` is not valid inside a `step {{ }}` body — the tool dispatch \
3479 would be silently dropped. To invoke a tool, either write the \
3480 flow-level step `use {tool} on <arg>` (outside this block), or bind \
3481 it inside this step with `apply: {tool}`. To attach a persona, put \
3482 it in the step header: `step <name> use <Persona> {{ … }}`."
3483 ),
3484 line: inner.line,
3485 column: inner.column,
3486 ..Default::default()
3487 });
3488 }
3489 TokenType::Mandate => {
3500 let g = self.parse_step_guard("mandate")?;
3501 node.guards.push(g);
3502 }
3503 TokenType::Shield => {
3504 let g = self.parse_step_guard("shield")?;
3505 node.guards.push(g);
3506 }
3507 TokenType::Ots => {
3508 let g = self.parse_step_guard("ots")?;
3509 node.guards.push(g);
3510 }
3511 TokenType::Lambda => {
3518 let g = self.parse_step_guard("lambda")?;
3519 node.guards.push(g);
3520 }
3521 TokenType::Probe
3529 if self
3530 .tokens
3531 .get(self.pos + 1)
3532 .is_some_and(|t| t.ttype != TokenType::Colon) =>
3533 {
3534 let tok = self.current().clone();
3535 self.advance();
3536 let target = self.consume_any_ident_or_kw()?.value.clone();
3537 let mut fields = Vec::new();
3538 if self.check(TokenType::For) {
3539 self.advance();
3540 self.consume(TokenType::LBracket)?;
3541 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
3542 fields.push(self.consume_any_ident_or_kw()?.value.clone());
3543 if self.check(TokenType::Comma) {
3544 self.advance();
3545 }
3546 }
3547 self.consume(TokenType::RBracket)?;
3548 }
3549 node.pix_ops.push(FlowStep::Probe(ProbeStep {
3550 target,
3551 fields,
3552 loc: Loc { line: tok.line, column: tok.column },
3553 }));
3554 }
3555 TokenType::Identifier if inner.value == "use_tool" => {
3562 let tok = self.current().clone();
3563 self.advance();
3564 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
3565 let args = if self.current().value == "with" {
3566 self.advance();
3567 let mut named: Vec<(String, String, String)> = Vec::new();
3568 loop {
3569 let k = self.consume_any_ident_or_kw()?.value.clone();
3570 self.consume(TokenType::Colon)?;
3571 let kind = if self.check(TokenType::StringLit) {
3575 "literal"
3576 } else {
3577 "reference"
3578 };
3579 let v = self.parse_expression_string()?;
3580 named.push((k, v, kind.to_string()));
3581 if self.check(TokenType::Comma) {
3582 self.advance();
3583 } else {
3584 break;
3585 }
3586 }
3587 UseArgs::Named(named)
3588 } else if self.current().value == "on" {
3589 self.advance();
3590 UseArgs::LegacyPositional(
3591 self.consume_any_ident_or_kw()?.value.clone(),
3592 )
3593 } else {
3594 UseArgs::LegacyPositional(String::new())
3595 };
3596 node.pix_ops.push(FlowStep::UseTool(UseToolStep {
3597 tool_name,
3598 args,
3599 loc: Loc { line: tok.line, column: tok.column },
3600 }));
3601 }
3602 TokenType::Par => {
3604 let block = self.parse_par_block()?;
3605 node.pix_ops.push(FlowStep::Par(block));
3606 }
3607 TokenType::Probe
3617 | TokenType::Reason
3618 | TokenType::Weave
3619 | TokenType::Stream => {
3620 self.skip_flow_step_structural()?;
3621 }
3622 _ => {
3623 return Err(ParseError {
3624 message: format!(
3625 "Unexpected token in step body: '{}' — expected given, ask, \
3626 probe, reason, weave, stream, output, confidence_floor, navigate, \
3627 apply, requires_context, now",
3628 inner.value
3629 ),
3630 line: inner.line,
3631 column: inner.column,
3632 ..Default::default()
3633 });
3634 }
3635 }
3636 }
3637 self.consume(TokenType::RBrace)?;
3638 Ok(node)
3639 }
3640
3641 fn skip_flow_step_structural(&mut self) -> Result<(), ParseError> {
3643 self.advance();
3645 while !self.check(TokenType::LBrace)
3647 && !self.check(TokenType::RBrace)
3648 && !self.check(TokenType::Eof)
3649 {
3650 let tt = &self.current().ttype;
3652 if matches!(
3653 tt,
3654 TokenType::Step
3655 | TokenType::Given
3656 | TokenType::Ask
3657 | TokenType::Output
3658 | TokenType::Navigate
3659 | TokenType::Use
3660 | TokenType::Probe
3661 | TokenType::Reason
3662 | TokenType::Weave
3663 | TokenType::Stream
3664 | TokenType::If
3665 | TokenType::For
3666 | TokenType::Let
3667 | TokenType::Return
3668 ) {
3669 return Ok(());
3670 }
3671 self.advance();
3672 }
3673 if self.check(TokenType::LBrace) {
3675 self.skip_braced_block()?;
3676 }
3677 Ok(())
3678 }
3679
3680 fn parse_intent(&mut self) -> Result<IntentNode, ParseError> {
3683 let tok = self.consume(TokenType::Intent)?;
3684 let loc = self.loc_of(&tok);
3685 let name = self.consume(TokenType::Identifier)?.value;
3686 self.consume(TokenType::LBrace)?;
3687
3688 let mut node = IntentNode {
3689 name,
3690 given: String::new(),
3691 ask: String::new(),
3692 output_type: None,
3693 confidence_floor: None,
3694 loc,
3695 leading_trivia: Vec::new(),
3696 trailing_trivia: Vec::new(),
3697 };
3698
3699 while !self.check(TokenType::RBrace) {
3700 let field_name = self.current().value.clone();
3701 self.advance();
3702 self.consume(TokenType::Colon)?;
3703
3704 match field_name.as_str() {
3705 "given" => node.given = self.consume(TokenType::Identifier)?.value,
3706 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
3707 "output" => node.output_type = Some(self.parse_type_expr()?),
3708 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
3709 _ => self.skip_value(),
3710 }
3711 }
3712 self.consume(TokenType::RBrace)?;
3713 Ok(node)
3714 }
3715
3716 fn parse_run(&mut self) -> Result<RunStatement, ParseError> {
3719 let tok = self.consume(TokenType::Run)?;
3720 let loc = self.loc_of(&tok);
3721 let flow_name = self.consume(TokenType::Identifier)?.value;
3722
3723 self.consume(TokenType::LParen)?;
3724 let mut arguments = Vec::new();
3725 if !self.check(TokenType::RParen) {
3726 arguments = self.parse_argument_list()?;
3727 }
3728 self.consume(TokenType::RParen)?;
3729
3730 let mut node = RunStatement {
3731 flow_name,
3732 arguments,
3733 persona: String::new(),
3734 context: String::new(),
3735 anchors: Vec::new(),
3736 on_failure: String::new(),
3737 on_failure_params: Vec::new(),
3738 output_to: String::new(),
3739 effort: String::new(),
3740 loc,
3741 leading_trivia: Vec::new(),
3742 trailing_trivia: Vec::new(),
3743 };
3744
3745 while self.check_run_modifier() {
3746 let mod_tok = self.current().clone();
3747 match mod_tok.ttype {
3748 TokenType::As => {
3749 self.advance();
3750 node.persona = self.consume(TokenType::Identifier)?.value;
3751 }
3752 TokenType::Within => {
3753 self.advance();
3754 node.context = self.consume(TokenType::Identifier)?.value;
3755 }
3756 TokenType::ConstrainedBy => {
3757 self.advance();
3758 node.anchors = self.parse_bracketed_identifiers()?;
3759 }
3760 TokenType::OnFailure => {
3761 self.advance();
3762 self.consume(TokenType::Colon)?;
3763 node.on_failure = self.consume_any_ident_or_kw()?.value;
3764 if self.check(TokenType::LParen) {
3766 self.advance();
3767 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3768 let key = self.consume_any_ident_or_kw()?.value;
3769 self.consume(TokenType::Colon)?;
3770 let val = self.consume_any_ident_or_kw()?.value;
3771 node.on_failure_params.push((key, val));
3772 if self.check(TokenType::Comma) {
3773 self.advance();
3774 }
3775 }
3776 if self.check(TokenType::RParen) {
3777 self.advance();
3778 }
3779 }
3780 }
3781 TokenType::OutputTo => {
3782 self.advance();
3783 self.consume(TokenType::Colon)?;
3784 node.output_to = self.consume(TokenType::StringLit)?.value;
3785 }
3786 TokenType::Effort => {
3787 self.advance();
3788 self.consume(TokenType::Colon)?;
3789 node.effort = self.consume_any_ident_or_kw()?.value;
3790 }
3791 _ => break,
3792 }
3793 }
3794
3795 Ok(node)
3796 }
3797
3798 fn parse_epistemic_block(&mut self) -> Result<EpistemicBlock, ParseError> {
3801 let tok = self.current().clone();
3802 let mode = match tok.ttype {
3803 TokenType::Know => "know",
3804 TokenType::Believe => "believe",
3805 TokenType::Speculate => "speculate",
3806 TokenType::Doubt => "doubt",
3807 _ => unreachable!(),
3808 };
3809 self.advance();
3810 let loc = self.loc_of(&tok);
3811
3812 self.consume(TokenType::LBrace)?;
3813 let mut body = Vec::new();
3814 while !self.check(TokenType::RBrace) {
3815 body.push(self.parse_declaration()?);
3816 }
3817 self.consume(TokenType::RBrace)?;
3818
3819 Ok(EpistemicBlock {
3820 mode: mode.to_string(),
3821 body,
3822 loc,
3823 leading_trivia: Vec::new(),
3824 trailing_trivia: Vec::new(),
3825 })
3826 }
3827
3828 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
3836 self.parse_expr_bp(0)
3837 }
3838
3839 fn parse_expr_bp(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
3840 let mut lhs = match self.current().ttype {
3843 TokenType::Minus => {
3844 self.advance();
3845 Expr::Unary(UnOp::Neg, Box::new(self.parse_expr_bp(6)?))
3846 }
3847 TokenType::Not => {
3848 self.advance();
3849 Expr::Unary(UnOp::Not, Box::new(self.parse_expr_bp(6)?))
3850 }
3851 _ => self.parse_postfix()?,
3852 };
3853 while let Some((op, lbp)) = Self::binop_of(self.current().ttype.clone()) {
3855 if lbp < min_bp {
3856 break;
3857 }
3858 self.advance();
3859 let rhs = self.parse_expr_bp(lbp + 1)?;
3860 lhs = Expr::Binary(op, Box::new(lhs), Box::new(rhs));
3861 }
3862 Ok(lhs)
3863 }
3864
3865 fn binop_of(t: TokenType) -> Option<(BinOp, u8)> {
3868 Some(match t {
3869 TokenType::Or => (BinOp::Or, 1),
3870 TokenType::And => (BinOp::And, 2),
3871 TokenType::Eq => (BinOp::Eq, 3),
3872 TokenType::Neq => (BinOp::Ne, 3),
3873 TokenType::Lt => (BinOp::Lt, 3),
3874 TokenType::Lte => (BinOp::Le, 3),
3875 TokenType::Gt => (BinOp::Gt, 3),
3876 TokenType::Gte => (BinOp::Ge, 3),
3877 TokenType::Plus => (BinOp::Add, 4),
3878 TokenType::Minus => (BinOp::Sub, 4),
3879 TokenType::Star => (BinOp::Mul, 5),
3880 TokenType::Slash => (BinOp::Div, 5),
3881 TokenType::Percent => (BinOp::Mod, 5),
3882 _ => return None,
3883 })
3884 }
3885
3886 fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
3892 let mut expr = self.parse_expr_atom()?;
3893 loop {
3894 if self.check(TokenType::Dot) {
3895 self.advance();
3896 let name = self.consume_any_ident_or_kw()?.value;
3897 if let Some(builtin) = Builtin::from_name(&name) {
3898 let mut args = vec![expr];
3899 if self.check(TokenType::LParen) {
3900 self.advance();
3901 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3902 args.push(self.parse_expr_bp(0)?);
3903 if self.check(TokenType::Comma) {
3904 self.advance();
3905 } else {
3906 break;
3907 }
3908 }
3909 self.consume(TokenType::RParen)?;
3910 }
3911 expr = Expr::Call(builtin, args);
3912 } else {
3913 expr = match expr {
3917 Expr::Ref(p) => Expr::Ref(format!("{p}.{name}")),
3918 other => Expr::Field(Box::new(other), name),
3919 };
3920 }
3921 } else if self.check(TokenType::LBracket) {
3922 self.advance();
3924 let index = self.parse_expr_bp(0)?;
3925 self.consume(TokenType::RBracket)?;
3926 expr = Expr::Index(Box::new(expr), Box::new(index));
3927 } else {
3928 break;
3929 }
3930 }
3931 Ok(expr)
3932 }
3933
3934 fn parse_expr_atom(&mut self) -> Result<Expr, ParseError> {
3935 let tok = self.current().clone();
3936 match tok.ttype {
3937 TokenType::Integer => {
3938 self.advance();
3939 let lit = tok
3940 .value
3941 .parse::<i64>()
3942 .map(ExprLit::Int)
3943 .or_else(|_| tok.value.parse::<f64>().map(ExprLit::Float))
3944 .map_err(|_| ParseError {
3945 message: format!("invalid integer literal '{}'", tok.value),
3946 line: tok.line,
3947 column: tok.column,
3948 ..Default::default()
3949 })?;
3950 Ok(Expr::Lit(lit))
3951 }
3952 TokenType::Float => {
3953 self.advance();
3954 let f = tok.value.parse::<f64>().map_err(|_| ParseError {
3955 message: format!("invalid float literal '{}'", tok.value),
3956 line: tok.line,
3957 column: tok.column,
3958 ..Default::default()
3959 })?;
3960 Ok(Expr::Lit(ExprLit::Float(f)))
3961 }
3962 TokenType::Bool => {
3963 self.advance();
3964 Ok(Expr::Lit(ExprLit::Bool(tok.value == "true")))
3965 }
3966 TokenType::StringLit => {
3967 self.advance();
3968 Ok(Expr::Lit(ExprLit::Str(tok.value)))
3969 }
3970 TokenType::LParen => {
3971 self.advance();
3972 let inner = self.parse_expr_bp(0)?;
3973 self.consume(TokenType::RParen)?;
3974 Ok(inner)
3975 }
3976 _ => {
3977 Ok(Expr::Ref(self.consume_any_ident_or_kw()?.value))
3981 }
3982 }
3983 }
3984
3985 fn expr_lit_surface(lit: &ExprLit) -> String {
3990 match lit {
3991 ExprLit::Int(i) => i.to_string(),
3992 ExprLit::Float(f) => f.to_string(),
3993 ExprLit::Bool(b) => b.to_string(),
3994 ExprLit::Str(s) => s.clone(),
3995 }
3996 }
3997
3998 fn expr_leaf_surface(expr: &Expr) -> Option<String> {
3999 match expr {
4000 Expr::Ref(p) => Some(p.clone()),
4001 Expr::Lit(l) => Some(Self::expr_lit_surface(l)),
4002 _ => None,
4003 }
4004 }
4005
4006 fn expr_legacy_leaf(expr: &Expr) -> Option<(String, String, String)> {
4010 match expr {
4011 Expr::Ref(p) => Some((p.clone(), String::new(), String::new())),
4012 Expr::Binary(op, l, r) => {
4013 let op_s = match op {
4014 BinOp::Eq => "==",
4015 BinOp::Ne => "!=",
4016 BinOp::Lt => "<",
4017 BinOp::Le => "<=",
4018 BinOp::Gt => ">",
4019 BinOp::Ge => ">=",
4020 _ => return None,
4021 };
4022 let lhs = match &**l {
4023 Expr::Ref(p) => p.clone(),
4024 _ => return None,
4025 };
4026 let rhs = Self::expr_leaf_surface(r)?;
4027 Some((lhs, op_s.to_string(), rhs))
4028 }
4029 _ => None,
4030 }
4031 }
4032
4033 fn collect_or_leaves(expr: &Expr, out: &mut Vec<(String, String, String)>) -> bool {
4036 match expr {
4037 Expr::Binary(BinOp::Or, l, r) => {
4038 Self::collect_or_leaves(l, out) && Self::collect_or_leaves(r, out)
4039 }
4040 _ => match Self::expr_legacy_leaf(expr) {
4041 Some(t) => {
4042 out.push(t);
4043 true
4044 }
4045 None => false,
4046 },
4047 }
4048 }
4049
4050 #[allow(clippy::type_complexity)]
4056 fn cond_as_legacy(
4057 expr: &Expr,
4058 ) -> Option<(String, String, String, Vec<(String, String, String)>, String)> {
4059 let mut leaves = Vec::new();
4060 if !Self::collect_or_leaves(expr, &mut leaves) || leaves.is_empty() {
4061 return None;
4062 }
4063 let (c0, o0, v0) = leaves[0].clone();
4064 let rest = leaves[1..].to_vec();
4065 let conjunctor = if rest.is_empty() {
4066 String::new()
4067 } else {
4068 "or".to_string()
4069 };
4070 Some((c0, o0, v0, rest, conjunctor))
4071 }
4072
4073 fn parse_if(&mut self) -> Result<ConditionalNode, ParseError> {
4074 let tok = self.consume(TokenType::If)?;
4075 let loc = self.loc_of(&tok);
4076
4077 let expr = self.parse_expr()?;
4082 let (condition, comparison_op, comparison_value, conditions, conjunctor, cond) =
4083 match Self::cond_as_legacy(&expr) {
4084 Some((c, o, v, more, conj)) => (c, o, v, more, conj, None),
4085 None => (
4086 String::new(),
4087 String::new(),
4088 String::new(),
4089 Vec::new(),
4090 String::new(),
4091 Some(expr),
4092 ),
4093 };
4094
4095 let mut then_body = Vec::new();
4096 let mut else_body = Vec::new();
4097
4098 if self.check(TokenType::Arrow) {
4100 self.advance();
4101 then_body.push(self.parse_flow_step()?);
4102 } else if self.check(TokenType::LBrace) {
4103 self.advance();
4104 while !self.check(TokenType::RBrace) {
4105 then_body.push(self.parse_flow_step()?);
4106 }
4107 self.consume(TokenType::RBrace)?;
4108 }
4109
4110 if self.check(TokenType::Else) {
4112 self.advance();
4113 if self.check(TokenType::Arrow) {
4114 self.advance();
4115 else_body.push(self.parse_flow_step()?);
4116 } else if self.check(TokenType::LBrace) {
4117 self.advance();
4118 while !self.check(TokenType::RBrace) {
4119 else_body.push(self.parse_flow_step()?);
4120 }
4121 self.consume(TokenType::RBrace)?;
4122 }
4123 }
4124
4125 Ok(ConditionalNode {
4126 condition,
4127 comparison_op,
4128 comparison_value,
4129 then_body,
4130 else_body,
4131 conditions,
4132 conjunctor,
4133 cond,
4134 loc,
4135 })
4136 }
4137
4138 fn parse_for_in(&mut self) -> Result<ForInStatement, ParseError> {
4141 let tok = self.consume(TokenType::For)?;
4142 let loc = self.loc_of(&tok);
4143 let variable = self.consume(TokenType::Identifier)?.value;
4144 self.consume(TokenType::In)?;
4145 let iterable = self.parse_dotted_identifier()?;
4146
4147 self.consume(TokenType::LBrace)?;
4148 self.loop_depth += 1;
4155 let body_result = (|| -> Result<Vec<FlowStep>, ParseError> {
4156 let mut body = Vec::new();
4157 while !self.check(TokenType::RBrace) {
4158 body.push(self.parse_flow_step()?);
4159 }
4160 Ok(body)
4161 })();
4162 self.loop_depth -= 1;
4163 let body = body_result?;
4164 self.consume(TokenType::RBrace)?;
4165
4166 Ok(ForInStatement {
4167 variable,
4168 iterable,
4169 body,
4170 loc,
4171 })
4172 }
4173
4174 fn parse_break(&mut self) -> Result<BreakStatement, ParseError> {
4177 let tok = self.consume(TokenType::Break)?;
4178 let loc = self.loc_of(&tok);
4179 if self.loop_depth == 0 {
4180 return Err(ParseError {
4181 message: "'break' outside of a for-in loop body".to_string(),
4182 line: tok.line,
4183 column: tok.column,
4184 ..Default::default()
4185 });
4186 }
4187 Ok(BreakStatement { loc })
4188 }
4189
4190 fn parse_continue(&mut self) -> Result<ContinueStatement, ParseError> {
4193 let tok = self.consume(TokenType::Continue)?;
4194 let loc = self.loc_of(&tok);
4195 if self.loop_depth == 0 {
4196 return Err(ParseError {
4197 message: "'continue' outside of a for-in loop body".to_string(),
4198 line: tok.line,
4199 column: tok.column,
4200 ..Default::default()
4201 });
4202 }
4203 Ok(ContinueStatement { loc })
4204 }
4205
4206 fn parse_let(&mut self) -> Result<LetStatement, ParseError> {
4209 let tok = self.consume(TokenType::Let)?;
4210 let loc = self.loc_of(&tok);
4211
4212 let name = self.consume_any_ident_or_kw()?.value;
4214 let type_annotation = if self.check(TokenType::Colon) {
4216 self.advance();
4217 Some(self.parse_type_expr()?)
4218 } else {
4219 None
4220 };
4221 self.consume(TokenType::Assign)?;
4222 self.last_let_value_kind = "literal".to_string();
4225 let (value, value_ast) = self.parse_let_value_expr_with_ast()?;
4226
4227 Ok(LetStatement {
4228 identifier: name,
4229 value_expr: value,
4230 value_kind: self.last_let_value_kind.clone(),
4231 type_annotation,
4232 value_ast,
4233 loc,
4234 leading_trivia: Vec::new(),
4235 trailing_trivia: Vec::new(),
4236 })
4237 }
4238
4239 fn parse_let_value_expr(&mut self) -> Result<String, ParseError> {
4240 let atom = self.parse_let_atom()?;
4241
4242 if matches!(
4244 self.current().ttype,
4245 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
4246 ) {
4247 let mut parts = vec![atom];
4248 while matches!(
4249 self.current().ttype,
4250 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
4251 ) {
4252 parts.push(self.advance().value.clone());
4253 parts.push(self.parse_let_atom()?);
4254 }
4255 self.last_let_value_kind = "expression".to_string();
4256 return Ok(parts.join(" "));
4257 }
4258 Ok(atom)
4259 }
4260
4261 fn parse_let_value_expr_with_ast(&mut self) -> Result<(String, Option<Expr>), ParseError> {
4271 if self.check(TokenType::LBracket) {
4272 self.last_let_value_kind = "literal".to_string();
4273 return Ok((self.parse_let_list_literal()?, None));
4274 }
4275 let expr = self.parse_expr()?;
4276 Ok(match expr {
4277 Expr::Lit(lit) => {
4278 self.last_let_value_kind = "literal".to_string();
4279 (Self::expr_lit_surface(&lit), None)
4280 }
4281 Expr::Ref(p) => {
4282 self.last_let_value_kind = "reference".to_string();
4283 (p, None)
4284 }
4285 other => {
4286 self.last_let_value_kind = "expression".to_string();
4287 (Self::render_expr(&other), Some(other))
4288 }
4289 })
4290 }
4291
4292 fn render_expr(e: &Expr) -> String {
4295 match e {
4296 Expr::Lit(l) => Self::expr_lit_surface(l),
4297 Expr::Ref(p) => p.clone(),
4298 Expr::Unary(UnOp::Neg, x) => format!("-{}", Self::render_expr(x)),
4299 Expr::Unary(UnOp::Not, x) => format!("not {}", Self::render_expr(x)),
4300 Expr::Binary(op, l, r) => {
4301 let sym = match op {
4302 BinOp::Add => "+",
4303 BinOp::Sub => "-",
4304 BinOp::Mul => "*",
4305 BinOp::Div => "/",
4306 BinOp::Mod => "%",
4307 BinOp::Eq => "==",
4308 BinOp::Ne => "!=",
4309 BinOp::Lt => "<",
4310 BinOp::Le => "<=",
4311 BinOp::Gt => ">",
4312 BinOp::Ge => ">=",
4313 BinOp::And => "and",
4314 BinOp::Or => "or",
4315 };
4316 format!("({} {sym} {})", Self::render_expr(l), Self::render_expr(r))
4317 }
4318 Expr::Call(b, args) => {
4319 let recv = args.first().map(Self::render_expr).unwrap_or_default();
4320 let rest: Vec<String> = args.iter().skip(1).map(Self::render_expr).collect();
4321 if rest.is_empty() {
4322 format!("{recv}.{}", b.surface())
4323 } else {
4324 format!("{recv}.{}({})", b.surface(), rest.join(", "))
4325 }
4326 }
4327 Expr::Field(b, f) => format!("{}.{f}", Self::render_expr(b)),
4328 Expr::Index(b, i) => format!("{}[{}]", Self::render_expr(b), Self::render_expr(i)),
4329 }
4330 }
4331
4332 fn parse_let_atom(&mut self) -> Result<String, ParseError> {
4333 let tok = self.current().clone();
4334
4335 match tok.ttype {
4336 TokenType::StringLit => {
4337 self.last_let_value_kind = "literal".to_string();
4338 self.advance();
4339 Ok(tok.value)
4340 }
4341 TokenType::Integer | TokenType::Float => {
4342 self.last_let_value_kind = "literal".to_string();
4343 self.advance();
4344 Ok(tok.value)
4345 }
4346 TokenType::Bool => {
4347 self.last_let_value_kind = "literal".to_string();
4348 self.advance();
4349 Ok(tok.value)
4350 }
4351 TokenType::Identifier => {
4352 self.last_let_value_kind = "reference".to_string();
4353 self.parse_dotted_identifier()
4354 }
4355 TokenType::LBracket => {
4356 self.last_let_value_kind = "literal".to_string();
4357 self.parse_let_list_literal()
4358 }
4359 _ => {
4360 if self.pos + 1 < self.tokens.len()
4362 && self.tokens[self.pos + 1].ttype == TokenType::Dot
4363 {
4364 self.last_let_value_kind = "reference".to_string();
4365 return self.parse_dotted_identifier();
4366 }
4367 Err(ParseError {
4368 message: format!(
4369 "Expected value expression, found {:?}('{}')",
4370 tok.ttype, tok.value
4371 ),
4372 line: tok.line,
4373 column: tok.column,
4374 ..Default::default()
4375 })
4376 }
4377 }
4378 }
4379
4380 fn parse_let_list_literal(&mut self) -> Result<String, ParseError> {
4381 self.consume(TokenType::LBracket)?;
4382 let mut items = Vec::new();
4383 if !self.check(TokenType::RBracket) {
4384 items.push(self.parse_let_value_expr()?);
4385 while self.check(TokenType::Comma) {
4386 self.advance();
4387 if self.check(TokenType::RBracket) {
4388 break; }
4390 items.push(self.parse_let_value_expr()?);
4391 }
4392 }
4393 self.consume(TokenType::RBracket)?;
4394 Ok(format!("[{}]", items.join(", ")))
4395 }
4396
4397 fn parse_return(&mut self) -> Result<ReturnStatement, ParseError> {
4400 let tok = self.consume(TokenType::Return)?;
4401 let loc = self.loc_of(&tok);
4402 let value = self.parse_let_value_expr()?;
4403 Ok(ReturnStatement {
4404 value_expr: value,
4405 loc,
4406 })
4407 }
4408
4409 fn parse_flow_step_simple(&mut self, _kw: &str) -> Result<(Loc, String), ParseError> {
4413 let tok = self.current().clone();
4414 self.advance(); let target = if self.at_declaration_start()
4416 || self.check(TokenType::RBrace)
4417 || self.check(TokenType::Eof)
4418 {
4419 String::new()
4420 } else {
4421 self.consume_any_ident_or_kw()?.value.clone()
4422 };
4423 if self.check(TokenType::LBrace) {
4425 self.skip_braced_block()?;
4426 }
4427 Ok((
4428 Loc {
4429 line: tok.line,
4430 column: tok.column,
4431 },
4432 target,
4433 ))
4434 }
4435
4436 fn parse_stream_block(&mut self) -> Result<StreamBlock, ParseError> {
4447 let tok = self.current().clone();
4448 let loc = self.loc_of(&tok);
4449 self.advance(); while !self.check(TokenType::LBrace)
4455 && !self.check(TokenType::RBrace)
4456 && !self.check(TokenType::Eof)
4457 && !self.at_declaration_start()
4458 {
4459 self.advance();
4460 }
4461
4462 let mut body = Vec::new();
4463 if self.check(TokenType::LBrace) {
4464 self.advance();
4465 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4466 body.push(self.parse_flow_step()?);
4467 }
4468 self.consume(TokenType::RBrace)?;
4469 }
4470
4471 Ok(StreamBlock { body, loc })
4472 }
4473
4474 fn parse_block_step(&mut self, _kw: &str) -> Result<Loc, ParseError> {
4475 let tok = self.current().clone();
4476 self.advance();
4477 while !self.check(TokenType::LBrace)
4479 && !self.check(TokenType::RBrace)
4480 && !self.check(TokenType::Eof)
4481 && !self.at_declaration_start()
4482 {
4483 self.advance();
4484 }
4485 if self.check(TokenType::LBrace) {
4486 self.skip_braced_block()?;
4487 }
4488 Ok(Loc {
4489 line: tok.line,
4490 column: tok.column,
4491 })
4492 }
4493
4494 fn parse_forge_step(&mut self) -> Result<ForgeBlock, ParseError> {
4501 let tok = self.consume(TokenType::Forge)?;
4502 let name = self.consume(TokenType::Identifier)?.value;
4503 let mut node = ForgeBlock {
4504 name,
4505 novelty: 0.5,
4506 depth: 1,
4507 branches: 1,
4508 loc: Loc { line: tok.line, column: tok.column },
4509 ..Default::default()
4510 };
4511 self.consume(TokenType::LParen)?;
4513 let arg = self.consume_any_ident_or_kw()?.value;
4514 self.consume(TokenType::Colon)?;
4515 if arg != "seed" {
4516 return Err(self.error(&format!(
4517 "forge '{}' expects `seed:` as its argument, found `{arg}`",
4518 node.name
4519 )));
4520 }
4521 node.seed = self.consume(TokenType::StringLit)?.value;
4522 self.consume(TokenType::RParen)?;
4523 self.consume(TokenType::Arrow)?;
4525 node.output_type = self.consume_any_ident_or_kw()?.value;
4526 self.consume(TokenType::LBrace)?;
4528 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4529 let field = self.consume_any_ident_or_kw()?.value;
4530 self.consume(TokenType::Colon)?;
4531 match field.as_str() {
4532 "mode" => node.mode = self.consume_any_ident_or_kw()?.value,
4533 "novelty" => node.novelty = self.consume_number()?,
4534 "depth" => {
4535 node.depth = self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4536 }
4537 "branches" => {
4538 node.branches =
4539 self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4540 }
4541 "constraints" => node.constraints_ref = self.consume_any_ident_or_kw()?.value,
4542 other => {
4543 return Err(self.error(&format!("unknown forge field `{other}`")))
4544 }
4545 }
4546 if self.check(TokenType::Comma) {
4547 self.consume(TokenType::Comma)?;
4548 }
4549 }
4550 self.consume(TokenType::RBrace)?;
4551 Ok(node)
4552 }
4553
4554 fn parse_par_block(&mut self) -> Result<ParBlock, ParseError> {
4563 let tok = self.current().clone();
4564 self.advance(); self.consume(TokenType::LBrace)?;
4566 let mut branches: Vec<Vec<FlowStep>> = Vec::new();
4567 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4568 branches.push(vec![self.parse_flow_step()?]);
4569 }
4570 self.consume(TokenType::RBrace)?;
4571 Ok(ParBlock {
4572 branches,
4573 loc: Loc {
4574 line: tok.line,
4575 column: tok.column,
4576 },
4577 })
4578 }
4579
4580 fn parse_warden(&mut self) -> Result<WardenBlock, ParseError> {
4597 let tok = self.consume(TokenType::Warden)?;
4598 self.consume(TokenType::LParen)?;
4600 let target = self.consume_any_ident_or_kw()?.value;
4601 self.consume(TokenType::RParen)?;
4602 self.consume(TokenType::Within)?;
4604 let scope_ref = self.consume(TokenType::Identifier)?.value;
4605 let mut block = WardenBlock {
4606 target,
4607 scope_ref,
4608 body: Vec::new(),
4609 loc: Loc {
4610 line: tok.line,
4611 column: tok.column,
4612 },
4613 };
4614 self.consume(TokenType::LBrace)?;
4616 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4617 block.body.push(self.parse_flow_step()?);
4618 }
4619 self.consume(TokenType::RBrace)?;
4620 Ok(block)
4621 }
4622
4623 fn parse_scope(&mut self) -> Result<ScopeDefinition, ParseError> {
4628 let tok = self.consume(TokenType::Scope)?;
4629 let name = self.consume(TokenType::Identifier)?.value;
4630 let mut node = ScopeDefinition {
4631 name,
4632 loc: Loc {
4633 line: tok.line,
4634 column: tok.column,
4635 },
4636 ..Default::default()
4637 };
4638 self.consume(TokenType::LBrace)?;
4639 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4640 let key = self.consume_any_ident_or_kw()?.value;
4641 self.consume(TokenType::Colon)?;
4642 match key.as_str() {
4643 "targets" => {
4644 self.consume(TokenType::LBracket)?;
4645 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4646 let t = if self.check(TokenType::StringLit) {
4647 self.consume(TokenType::StringLit)?.value
4648 } else {
4649 self.consume_any_ident_or_kw()?.value
4650 };
4651 node.targets.push(t);
4652 if self.check(TokenType::Comma) {
4653 self.advance();
4654 }
4655 }
4656 self.consume(TokenType::RBracket)?;
4657 }
4658 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
4659 "approver" => {
4660 if self.current().value == "requires" {
4662 self.advance();
4663 }
4664 node.approver = self.consume(TokenType::StringLit)?.value;
4665 }
4666 other => {
4667 return Err(self.error(&format!(
4668 "unknown scope field `{other}` in scope `{}` — expected \
4669 `targets` / `depth` / `approver`",
4670 node.name
4671 )))
4672 }
4673 }
4674 if self.check(TokenType::Comma) {
4675 self.consume(TokenType::Comma)?;
4676 }
4677 }
4678 self.consume(TokenType::RBrace)?;
4679 Ok(node)
4680 }
4681
4682 fn parse_quant(&mut self) -> Result<QuantBlock, ParseError> {
4683 let tok = self.current().clone();
4684 self.advance(); let mut block = QuantBlock {
4687 encoding: None,
4688 observable: None,
4689 qubits: None,
4690 depth: None,
4691 bandwidth: None,
4692 reupload: None,
4693 effect: "quant_sim".to_string(),
4696 body: Vec::new(),
4697 loc: Loc {
4698 line: tok.line,
4699 column: tok.column,
4700 },
4701 };
4702
4703 if self.check(TokenType::LParen) {
4705 self.advance();
4706 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
4707 let key = self.consume_any_ident_or_kw()?.value;
4708 self.consume(TokenType::Colon)?;
4709 match key.as_str() {
4710 "encoding" => {
4711 block.encoding = Some(self.consume_any_ident_or_kw()?.value)
4712 }
4713 "observable" => {
4714 block.observable = Some(self.parse_dotted_identifier()?)
4715 }
4716 "qubits" => block.qubits = Some(self.consume_number()? as i64),
4717 "depth" => block.depth = Some(self.consume_number()? as i64),
4718 "bandwidth" => block.bandwidth = Some(self.consume_number()?),
4719 "reupload" => block.reupload = Some(self.consume_number()? as i64),
4721 "backend" => block.effect = self.consume_any_ident_or_kw()?.value,
4723 other => {
4724 return Err(ParseError {
4725 message: format!(
4726 "Unknown `quant` attribute `{other}` — expected one of \
4727 encoding, observable, qubits, depth, bandwidth, reupload, backend"
4728 ),
4729 line: self.current().line,
4730 column: self.current().column,
4731 ..Default::default()
4732 });
4733 }
4734 }
4735 if self.check(TokenType::Comma) {
4737 self.advance();
4738 }
4739 }
4740 self.consume(TokenType::RParen)?;
4741 }
4742
4743 self.consume(TokenType::LBrace)?;
4745 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4746 block.body.push(self.parse_flow_step()?);
4747 }
4748 self.consume(TokenType::RBrace)?;
4749
4750 Ok(block)
4751 }
4752
4753 fn parse_yield(&mut self) -> Result<YieldStatement, ParseError> {
4757 let tok = self.consume(TokenType::Yield)?;
4758 let loc = self.loc_of(&tok);
4759 self.last_let_value_kind = "literal".to_string();
4760 let value_expr = self.parse_let_value_expr()?;
4761 Ok(YieldStatement {
4762 value_expr,
4763 value_kind: self.last_let_value_kind.clone(),
4764 loc,
4765 })
4766 }
4767
4768 fn parse_compute_apply(&mut self) -> Result<ComputeApplyStep, ParseError> {
4775 let tok = self.current().clone();
4776 let loc = self.loc_of(&tok);
4777 self.advance(); let compute_name = self.consume_any_ident_or_kw()?.value.clone();
4779
4780 let mut arguments = Vec::new();
4781 if self.current().value == "on" {
4782 self.advance();
4783 loop {
4784 arguments.push(self.consume_any_ident_or_kw()?.value.clone());
4785 if self.check(TokenType::Comma) {
4786 self.advance();
4787 } else {
4788 break;
4789 }
4790 }
4791 }
4792
4793 let mut output_name = String::new();
4794 if self.check(TokenType::Arrow) {
4795 self.advance();
4796 output_name = self.consume_any_ident_or_kw()?.value.clone();
4797 }
4798
4799 Ok(ComputeApplyStep {
4800 compute_name,
4801 arguments,
4802 output_name,
4803 loc,
4804 })
4805 }
4806
4807 fn parse_apply_step(&mut self, _kw: &str) -> Result<(Loc, String, String, String), ParseError> {
4808 let tok = self.current().clone();
4809 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
4811 let mut target = String::new();
4812 let mut output_type = String::new();
4813 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4815 let next = self.current().clone();
4816 if next.value == "on" {
4817 self.advance();
4818 target = self.consume_any_ident_or_kw()?.value.clone();
4819 }
4820 }
4821 if self.check(TokenType::Arrow) {
4823 self.advance();
4824 output_type = self.consume_any_ident_or_kw()?.value.clone();
4825 }
4826 if self.check(TokenType::LBrace) {
4828 self.skip_braced_block()?;
4829 }
4830 Ok((
4831 Loc {
4832 line: tok.line,
4833 column: tok.column,
4834 },
4835 name,
4836 target,
4837 output_type,
4838 ))
4839 }
4840
4841 fn parse_step_guard(&mut self, kind: &str) -> Result<StepGuardNode, ParseError> {
4852 let tok = self.current().clone();
4853 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
4855 let mut target = String::new();
4856 let mut binding = String::new();
4857 if self.current().value == "on" {
4858 self.advance();
4859 target = self.consume_any_ident_or_kw()?.value.clone();
4860 if self.check(TokenType::LParen) {
4863 let mut depth = 0usize;
4864 loop {
4865 let t = self.current().clone();
4866 match t.ttype {
4867 TokenType::LParen => depth += 1,
4868 TokenType::RParen => depth -= 1,
4869 TokenType::Eof => {
4870 return Err(ParseError {
4871 message: format!(
4872 "unterminated argument list in `{kind} {name} on {target}(…`"
4873 ),
4874 line: t.line,
4875 column: t.column,
4876 ..Default::default()
4877 })
4878 }
4879 _ => {}
4880 }
4881 target.push_str(&t.value);
4882 self.advance();
4883 if depth == 0 {
4884 break;
4885 }
4886 }
4887 }
4888 }
4889 if self.check(TokenType::Arrow) {
4890 self.advance();
4891 binding = self.consume_any_ident_or_kw()?.value.clone();
4892 }
4893 Ok(StepGuardNode {
4894 kind: kind.to_string(),
4895 name,
4896 target,
4897 binding,
4898 loc: Loc {
4899 line: tok.line,
4900 column: tok.column,
4901 },
4902 })
4903 }
4904
4905 fn parse_weave_step(&mut self) -> Result<FlowStep, ParseError> {
4906 let tok = self.current().clone();
4907 self.advance();
4908 let mut node = WeaveStep {
4909 sources: Vec::new(),
4910 target: String::new(),
4911 format_type: String::new(),
4912 priority: Vec::new(),
4913 style: String::new(),
4914 loc: Loc {
4915 line: tok.line,
4916 column: tok.column,
4917 },
4918 };
4919 if self.check(TokenType::LBrace) {
4920 self.advance();
4921 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4922 let f = self.current().value.clone();
4923 self.advance();
4924 if self.check(TokenType::Colon) {
4925 self.advance();
4926 match f.as_str() {
4927 "sources" => node.sources = self.parse_bracketed_identifiers()?,
4928 "target" => node.target = self.consume_any_ident_or_kw()?.value.clone(),
4929 "format" => {
4930 node.format_type = self.consume_any_ident_or_kw()?.value.clone()
4931 }
4932 "priority" => node.priority = self.parse_bracketed_identifiers()?,
4933 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
4934 _ => self.skip_value(),
4935 }
4936 }
4937 }
4938 if self.check(TokenType::RBrace) {
4939 self.advance();
4940 }
4941 }
4942 Ok(FlowStep::Weave(node))
4943 }
4944
4945 fn parse_use_step(&mut self) -> Result<FlowStep, ParseError> {
4946 let tok = self.current().clone();
4947 self.advance();
4948 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
4949 let args = if self.check(TokenType::LParen) {
4960 UseArgs::Named(self.parse_named_arg_list()?)
4961 } else {
4962 let mut argument = String::new();
4963 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4964 let next = self.current().clone();
4965 if next.value == "on" {
4966 self.advance();
4967 argument = self.consume_any_ident_or_kw()?.value.clone();
4968 }
4969 }
4970 UseArgs::LegacyPositional(argument)
4971 };
4972 if self.check(TokenType::LBrace) {
4973 self.skip_braced_block()?;
4974 }
4975 Ok(FlowStep::UseTool(UseToolStep {
4976 tool_name,
4977 args,
4978 loc: Loc {
4979 line: tok.line,
4980 column: tok.column,
4981 },
4982 }))
4983 }
4984
4985 fn parse_named_arg_list(&mut self) -> Result<Vec<(String, String, String)>, ParseError> {
4991 self.consume(TokenType::LParen)?;
4992 let mut args = Vec::new();
4993 while !self.check(TokenType::RParen) {
4994 let name = self.consume_any_ident_or_kw()?.value;
4997 self.consume(TokenType::Assign)?;
4998 let value = self.parse_let_atom()?;
4999 let value_kind = self.last_let_value_kind.clone();
5003 args.push((name, value, value_kind));
5004 if self.check(TokenType::Comma) {
5005 self.advance();
5006 } else {
5007 break;
5008 }
5009 }
5010 self.consume(TokenType::RParen)?;
5011 Ok(args)
5012 }
5013
5014 fn parse_remember_step(&mut self) -> Result<FlowStep, ParseError> {
5015 let tok = self.current().clone();
5016 self.advance();
5017 let expr = self.consume_any_ident_or_kw()?.value.clone();
5018 let mut mem = String::new();
5019 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5020 let next = self.current().clone();
5021 if next.value == "in" || next.ttype == TokenType::In {
5022 self.advance();
5023 mem = self.consume_any_ident_or_kw()?.value.clone();
5024 }
5025 }
5026 Ok(FlowStep::Remember(RememberStep {
5027 expression: expr,
5028 memory_target: mem,
5029 loc: Loc {
5030 line: tok.line,
5031 column: tok.column,
5032 },
5033 }))
5034 }
5035
5036 fn parse_recall_step(&mut self) -> Result<FlowStep, ParseError> {
5037 let tok = self.current().clone();
5038 self.advance();
5039 let query = if self.check(TokenType::StringLit) {
5040 self.consume(TokenType::StringLit)?.value.clone()
5041 } else {
5042 self.consume_any_ident_or_kw()?.value.clone()
5043 };
5044 let mut mem = String::new();
5045 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5046 let next = self.current().clone();
5047 if next.value == "from" || next.ttype == TokenType::From {
5048 self.advance();
5049 mem = self.consume_any_ident_or_kw()?.value.clone();
5050 }
5051 }
5052 Ok(FlowStep::Recall(RecallStep {
5053 query,
5054 memory_source: mem,
5055 loc: Loc {
5056 line: tok.line,
5057 column: tok.column,
5058 },
5059 }))
5060 }
5061
5062 fn parse_hibernate_step(&mut self) -> Result<FlowStep, ParseError> {
5063 let tok = self.current().clone();
5064 self.advance();
5065 let mut event = String::new();
5066 let mut timeout = String::new();
5067 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5068 let first = self.consume_any_ident_or_kw()?.value.clone();
5073 if first == "until" && self.check(TokenType::StringLit) {
5074 event = self.consume(TokenType::StringLit)?.value.clone();
5075 } else {
5076 event = first;
5077 }
5078 }
5079 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5080 let next = self.current().clone();
5081 if next.ttype == TokenType::Duration {
5082 self.advance();
5083 timeout = next.value.clone();
5084 }
5085 }
5086 Ok(FlowStep::Hibernate(HibernateStep {
5087 event_name: event,
5088 timeout,
5089 loc: Loc {
5090 line: tok.line,
5091 column: tok.column,
5092 },
5093 }))
5094 }
5095
5096 fn parse_grad_step(&mut self) -> Result<FlowStep, ParseError> {
5105 let tok = self.current().clone();
5106 self.advance();
5107 let target = self.consume_any_ident_or_kw()?.value.clone();
5108 let mut wrt: Vec<String> = Vec::new();
5109 let mut output = String::new();
5110 if !self.at_declaration_start() && self.current().value == "wrt" {
5111 self.advance();
5112 if self.check(TokenType::LBracket) {
5113 wrt = self.parse_bracketed_identifiers()?;
5114 } else {
5115 wrt.push(self.consume_any_ident_or_kw()?.value.clone());
5116 }
5117 }
5118 if !self.at_declaration_start() && self.current().value == "as" {
5119 self.advance();
5120 output = self.consume_any_ident_or_kw()?.value.clone();
5121 }
5122 Ok(FlowStep::Grad(GradStep {
5123 target,
5124 wrt,
5125 output,
5126 loc: Loc {
5127 line: tok.line,
5128 column: tok.column,
5129 },
5130 }))
5131 }
5132
5133 fn parse_focus_step(&mut self) -> Result<FlowStep, ParseError> {
5134 let tok = self.current().clone();
5135 self.advance();
5136 let expression = if self.at_declaration_start()
5137 || self.check(TokenType::RBrace)
5138 || self.check(TokenType::Eof)
5139 {
5140 String::new()
5141 } else {
5142 self.consume_any_ident_or_kw()?.value.clone()
5143 };
5144 let mut where_expr = String::new();
5145 let mut select: Vec<String> = Vec::new();
5146 let mut output = String::new();
5147 if self.check(TokenType::LBrace) {
5148 self.advance();
5149 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5150 if self.check(TokenType::Comma) {
5151 self.advance();
5152 continue;
5153 }
5154 let f = self.current().value.clone();
5155 self.advance();
5156 if self.check(TokenType::Colon) {
5157 self.advance();
5158 match f.as_str() {
5159 "where" => {
5160 where_expr = self.consume(TokenType::StringLit)?.value.clone()
5161 }
5162 "select" => select = self.parse_bracketed_identifiers()?,
5163 "as" | "alias" => {
5164 output = self.consume_any_ident_or_kw()?.value.clone()
5165 }
5166 _ => self.skip_value(),
5167 }
5168 }
5169 }
5170 if self.check(TokenType::RBrace) {
5171 self.advance();
5172 }
5173 }
5174 Ok(FlowStep::Focus(FocusStep {
5175 expression,
5176 where_expr,
5177 select,
5178 output,
5179 loc: Loc {
5180 line: tok.line,
5181 column: tok.column,
5182 },
5183 }))
5184 }
5185
5186 fn parse_associate_step(&mut self) -> Result<FlowStep, ParseError> {
5187 let tok = self.current().clone();
5188 self.advance();
5189 let left = self.consume_any_ident_or_kw()?.value.clone();
5190 let mut right = String::new();
5191 let mut using = String::new();
5192 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5193 right = self.consume_any_ident_or_kw()?.value.clone();
5194 }
5195 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5196 let next = self.current().clone();
5197 if next.value == "using" {
5198 self.advance();
5199 using = self.consume_any_ident_or_kw()?.value.clone();
5200 }
5201 }
5202 let mut output = String::new();
5203 if self.check(TokenType::LBrace) {
5204 self.advance();
5205 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5206 let f = self.current().value.clone();
5207 self.advance();
5208 if self.check(TokenType::Colon) {
5209 self.advance();
5210 match f.as_str() {
5211 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
5212 _ => self.skip_value(),
5213 }
5214 }
5215 }
5216 if self.check(TokenType::RBrace) {
5217 self.advance();
5218 }
5219 }
5220 Ok(FlowStep::Associate(AssociateStep {
5221 left,
5222 right,
5223 using_field: using,
5224 output,
5225 loc: Loc {
5226 line: tok.line,
5227 column: tok.column,
5228 },
5229 }))
5230 }
5231
5232 fn parse_aggregate_step(&mut self) -> Result<FlowStep, ParseError> {
5233 let tok = self.current().clone();
5234 self.advance();
5235 let target = self.consume_any_ident_or_kw()?.value.clone();
5236 let mut group_by = Vec::new();
5237 let mut alias = String::new();
5238 let mut compute: Vec<String> = Vec::new();
5239 let mut where_expr = String::new();
5240 if self.check(TokenType::LBrace) {
5241 self.advance();
5242 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5243 let f = self.current().value.clone();
5244 self.advance();
5245 if self.check(TokenType::Colon) {
5246 self.advance();
5247 match f.as_str() {
5248 "group_by" => group_by = self.parse_bracketed_identifiers()?,
5249 "alias" | "as" => alias = self.consume_any_ident_or_kw()?.value.clone(),
5250 "compute" => compute = self.parse_bracketed_aggregates()?,
5253 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
5255 _ => self.skip_value(),
5256 }
5257 }
5258 }
5259 if self.check(TokenType::RBrace) {
5260 self.advance();
5261 }
5262 }
5263 Ok(FlowStep::Aggregate(AggregateStep {
5264 target,
5265 group_by,
5266 alias,
5267 compute,
5268 where_expr,
5269 loc: Loc {
5270 line: tok.line,
5271 column: tok.column,
5272 },
5273 }))
5274 }
5275
5276 fn parse_explore_step(&mut self) -> Result<FlowStep, ParseError> {
5277 let tok = self.current().clone();
5278 self.advance();
5279 let target = self.consume_any_ident_or_kw()?.value.clone();
5280 let mut limit = None;
5281 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5282 if self.current().ttype == TokenType::Integer {
5283 limit = self.current().value.parse::<i64>().ok();
5284 self.advance();
5285 }
5286 }
5287 let mut output = String::new();
5288 if self.check(TokenType::LBrace) {
5289 self.advance();
5290 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5291 let f = self.current().value.clone();
5292 self.advance();
5293 if self.check(TokenType::Colon) {
5294 self.advance();
5295 match f.as_str() {
5296 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
5297 _ => self.skip_value(),
5298 }
5299 }
5300 }
5301 if self.check(TokenType::RBrace) {
5302 self.advance();
5303 }
5304 }
5305 Ok(FlowStep::ExploreStep(ExploreStepNode {
5306 target,
5307 limit,
5308 output,
5309 loc: Loc {
5310 line: tok.line,
5311 column: tok.column,
5312 },
5313 }))
5314 }
5315
5316 fn parse_bracketed_aggregates(&mut self) -> Result<Vec<String>, ParseError> {
5319 let mut out = Vec::new();
5320 self.consume(TokenType::LBracket)?;
5321 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5322 let name = self.consume_any_ident_or_kw()?.value.clone();
5323 if self.check(TokenType::LParen) {
5324 self.advance();
5325 let col = self.consume_any_ident_or_kw()?.value.clone();
5326 self.consume(TokenType::RParen)?;
5327 out.push(format!("{name}({col})"));
5328 } else {
5329 out.push(name);
5330 }
5331 if self.check(TokenType::Comma) {
5332 self.advance();
5333 }
5334 }
5335 self.consume(TokenType::RBracket)?;
5336 Ok(out)
5337 }
5338
5339 fn parse_ingest_step(&mut self) -> Result<FlowStep, ParseError> {
5354 let tok = self.current().clone();
5355 self.advance();
5356 let source = self.consume_any_ident_or_kw()?.value.clone();
5357 let mut target = String::new();
5358 let mut format = String::new();
5359 let mut max_bytes: Option<u64> = None;
5360 let mut max_rows: Option<u64> = None;
5361 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5362 let next = self.current().clone();
5363 if next.value == "into" || next.ttype == TokenType::Into {
5364 self.advance();
5365 target = self.consume_any_ident_or_kw()?.value.clone();
5366 }
5367 }
5368 if self.check(TokenType::LBrace) {
5369 self.consume(TokenType::LBrace)?;
5370 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5371 if self.check(TokenType::Comma) {
5373 self.advance();
5374 continue;
5375 }
5376 let entry = self.current().clone();
5377 match entry.value.as_str() {
5378 "format" => {
5379 self.advance();
5380 self.consume(TokenType::Colon)?;
5381 format = self.consume_any_ident_or_kw()?.value.clone();
5382 }
5383 "limits" => {
5384 self.advance();
5385 self.consume(TokenType::LBrace)?;
5386 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5387 let bound = self.current().clone();
5388 self.advance();
5389 self.consume(TokenType::Colon)?;
5390 let num_tok = self.consume(TokenType::Integer)?.clone();
5391 let value = num_tok.value.parse::<u64>().map_err(|_| ParseError {
5392 message: format!(
5393 "ingest `limits` bound `{}` must be a non-negative \
5394 integer byte/row count, got `{}`.",
5395 bound.value, num_tok.value
5396 ),
5397 line: num_tok.line,
5398 column: num_tok.column,
5399 ..Default::default()
5400 })?;
5401 match bound.value.as_str() {
5402 "max_bytes" => max_bytes = Some(value),
5403 "max_rows" => max_rows = Some(value),
5404 other => {
5405 return Err(ParseError {
5406 message: format!(
5407 "Unknown ingest limit `{other}`. The closed \
5408 limits grammar is `max_bytes: <N>` and \
5409 `max_rows: <N>` — bounds enforced on the raw \
5410 stream BEFORE parsing (§100).",
5411 ),
5412 line: bound.line,
5413 column: bound.column,
5414 ..Default::default()
5415 });
5416 }
5417 }
5418 if self.check(TokenType::Comma) {
5419 self.advance();
5420 }
5421 }
5422 self.consume(TokenType::RBrace)?;
5423 }
5424 other => {
5425 return Err(ParseError {
5426 message: format!(
5427 "Unknown entry `{other}` in ingest body. The closed \
5428 grammar is `format: csv|json` and \
5429 `limits {{ max_bytes: <N>, max_rows: <N> }}`.",
5430 ),
5431 line: entry.line,
5432 column: entry.column,
5433 ..Default::default()
5434 });
5435 }
5436 }
5437 }
5438 self.consume(TokenType::RBrace)?;
5439 }
5440 Ok(FlowStep::Ingest(IngestStep {
5441 source,
5442 target,
5443 format,
5444 max_bytes,
5445 max_rows,
5446 loc: Loc {
5447 line: tok.line,
5448 column: tok.column,
5449 },
5450 }))
5451 }
5452
5453 fn at_navigate_field(&self) -> bool {
5460 const FIELDS: &[&str] = &[
5461 "corpus", "query", "trail", "as", "from", "budget", "where",
5469 "depth", "recall",
5470 ];
5471 self.field_ahead(FIELDS)
5472 }
5473
5474 fn at_drill_field(&self) -> bool {
5476 const FIELDS: &[&str] = &["subtree", "path", "query", "as"];
5479 self.field_ahead(FIELDS)
5480 }
5481
5482 fn field_ahead(&self, names: &[&str]) -> bool {
5484 let cur = self.current();
5485 if !names.contains(&cur.value.as_str()) {
5486 return false;
5487 }
5488 self.tokens
5489 .get(self.pos + 1)
5490 .is_some_and(|t| t.ttype == TokenType::Colon)
5491 }
5492
5493 fn parse_config_key(&mut self) -> Result<String, ParseError> {
5503 if self.check(TokenType::StringLit) {
5504 return Ok(self.consume(TokenType::StringLit)?.value.clone());
5505 }
5506 let scheme = self.consume_any_ident_or_kw()?.value.clone();
5507 if self.check(TokenType::Colon) {
5508 self.advance();
5509 let key = self.consume_any_ident_or_kw()?.value.clone();
5510 return Ok(format!("{scheme}:{key}"));
5511 }
5512 Ok(scheme)
5513 }
5514
5515 fn parse_pix_value(&mut self) -> Result<String, ParseError> {
5520 if self.check(TokenType::StringLit) {
5521 return Ok(self.consume(TokenType::StringLit)?.value.clone());
5522 }
5523 Ok(self.consume_any_ident_or_kw()?.value.clone())
5524 }
5525
5526 fn parse_navigate_step(&mut self) -> Result<FlowStep, ParseError> {
5527 let tok = self.current().clone();
5528 self.advance();
5529 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
5530 let mut node = NavigateStep {
5531 depth: None,
5532 pix_name,
5533 corpus_name: String::new(),
5534 query_expr: String::new(),
5535 trail_enabled: false,
5536 output_name: String::new(),
5537 seed: String::new(),
5538 budget: None,
5539 where_expr: String::new(),
5540 loc: Loc {
5541 line: tok.line,
5542 column: tok.column,
5543 },
5544 };
5545 if !self.check(TokenType::LBrace) {
5559 while self.at_navigate_field() {
5560 let f = self.current().value.clone();
5561 self.advance();
5562 self.consume(TokenType::Colon)?;
5563 match f.as_str() {
5564 "corpus" => node.corpus_name = self.consume_any_ident_or_kw()?.value.clone(),
5565 "query" => node.query_expr = self.parse_pix_value()?,
5566 "trail" => {
5567 let v = self.consume_any_ident_or_kw()?.value;
5568 node.trail_enabled = matches!(v.as_str(), "true" | "enabled" | "on");
5569 }
5570 "output" | "as" => {
5571 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5572 }
5573 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
5574 "budget" => node.budget = self.parse_optional_int(),
5575 "where" => node.where_expr = self.parse_pix_value()?,
5576 "depth" => node.depth = self.parse_optional_int(),
5577 "recall" => {
5585 let mode = self.consume_any_ident_or_kw()?.value.clone();
5586 if node.seed.is_empty() {
5587 node.seed = format!("recall:{mode}");
5588 }
5589 }
5590 _ => self.skip_value(),
5591 }
5592 }
5593 }
5594 if self.check(TokenType::LBrace) {
5595 self.advance();
5596 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5597 let f = self.current().value.clone();
5598 self.advance();
5599 if self.check(TokenType::Colon) {
5600 self.advance();
5601 match f.as_str() {
5602 "corpus" => {
5603 node.corpus_name = self.consume_any_ident_or_kw()?.value.clone()
5604 }
5605 "query" => node.query_expr = self.parse_pix_value()?,
5606 "trail" => {
5607 let v = self.consume_any_ident_or_kw()?.value;
5608 node.trail_enabled =
5609 matches!(v.as_str(), "true" | "enabled" | "on");
5610 }
5611 "output" | "as" => {
5612 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5613 }
5614 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
5616 "budget" => node.budget = self.parse_optional_int(),
5617 "where" => {
5625 node.where_expr = self.consume(TokenType::StringLit)?.value.clone()
5626 }
5627 _ => self.skip_value(),
5628 }
5629 }
5630 }
5631 if self.check(TokenType::RBrace) {
5632 self.advance();
5633 }
5634 }
5635 Ok(FlowStep::Navigate(node))
5636 }
5637
5638 fn parse_drill_step(&mut self) -> Result<FlowStep, ParseError> {
5639 let tok = self.current().clone();
5640 self.advance();
5641 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
5642 let mut node = DrillStep {
5643 pix_name,
5644 subtree_path: String::new(),
5645 query_expr: String::new(),
5646 output_name: String::new(),
5647 loc: Loc {
5648 line: tok.line,
5649 column: tok.column,
5650 },
5651 };
5652 if self.current().value == "into" {
5656 self.advance();
5657 node.subtree_path = if self.check(TokenType::StringLit) {
5662 self.consume(TokenType::StringLit)?.value.clone()
5663 } else {
5664 self.parse_dotted_identifier()?
5665 };
5666 }
5667 if !self.check(TokenType::LBrace) {
5668 while self.at_drill_field() {
5669 let f = self.current().value.clone();
5670 self.advance();
5671 self.consume(TokenType::Colon)?;
5672 match f.as_str() {
5673 "subtree" | "path" => {
5674 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
5675 }
5676 "query" => node.query_expr = self.parse_pix_value()?,
5677 "output" | "as" => {
5678 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5679 }
5680 _ => self.skip_value(),
5681 }
5682 }
5683 }
5684 if self.check(TokenType::LBrace) {
5685 self.advance();
5686 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5687 let f = self.current().value.clone();
5688 self.advance();
5689 if self.check(TokenType::Colon) {
5690 self.advance();
5691 match f.as_str() {
5692 "subtree" | "path" => {
5693 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
5694 }
5695 "query" => node.query_expr = self.parse_pix_value()?,
5696 "output" | "as" => {
5697 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5698 }
5699 _ => self.skip_value(),
5700 }
5701 }
5702 }
5703 if self.check(TokenType::RBrace) {
5704 self.advance();
5705 }
5706 }
5707 Ok(FlowStep::Drill(node))
5708 }
5709
5710 fn parse_corroborate_step(&mut self) -> Result<FlowStep, ParseError> {
5711 let tok = self.current().clone();
5712 self.advance();
5713 let nav_ref = self.consume_any_ident_or_kw()?.value.clone();
5714 let mut output = String::new();
5715 if self.check(TokenType::Arrow) {
5716 self.advance();
5717 output = self.consume_any_ident_or_kw()?.value.clone();
5718 }
5719 Ok(FlowStep::Corroborate(CorroborateStep {
5720 navigate_ref: nav_ref,
5721 output_name: output,
5722 loc: Loc {
5723 line: tok.line,
5724 column: tok.column,
5725 },
5726 }))
5727 }
5728
5729 fn parse_listen_step(&mut self) -> Result<FlowStep, ParseError> {
5730 let tok = self.current().clone();
5731 self.advance();
5732 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
5736 (self.consume(TokenType::StringLit)?.value.clone(), false)
5737 } else {
5738 (self.consume_any_ident_or_kw()?.value.clone(), true)
5739 };
5740 let mut alias = String::new();
5741 if !self.at_declaration_start()
5742 && !self.check(TokenType::RBrace)
5743 && !self.check(TokenType::LBrace)
5744 {
5745 let next = self.current().clone();
5746 if next.value == "as" || next.ttype == TokenType::As {
5747 self.advance();
5748 alias = self.consume_any_ident_or_kw()?.value.clone();
5749 }
5750 }
5751 let body = self.parse_listener_body()?;
5755 Ok(FlowStep::Listen(ListenStep {
5756 channel,
5757 channel_is_ref,
5758 event_alias: alias,
5759 body,
5760 loc: Loc {
5761 line: tok.line,
5762 column: tok.column,
5763 },
5764 }))
5765 }
5766
5767 fn parse_listener_body(&mut self) -> Result<Vec<FlowStep>, ParseError> {
5772 let mut body = Vec::new();
5773 if self.check(TokenType::LBrace) {
5774 self.advance(); while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5776 body.push(self.parse_flow_step()?);
5777 }
5778 self.consume(TokenType::RBrace)?;
5779 }
5780 Ok(body)
5781 }
5782
5783 fn parse_retrieve_step(&mut self) -> Result<FlowStep, ParseError> {
5784 let tok = self.current().clone();
5785 self.advance();
5786 let store = self.consume_any_ident_or_kw()?.value.clone();
5787 let mut where_expr = String::new();
5788 let mut alias = String::new();
5789 let mut order_by = String::new();
5790 let mut limit_expr = String::new();
5791 let mut aggregate = String::new();
5792 let mut group_by = String::new();
5793 let mut cache = String::new();
5794 if self.check(TokenType::LBrace) {
5795 self.advance();
5796 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5797 let f = self.current().value.clone();
5798 self.advance();
5799 if self.check(TokenType::Colon) {
5800 self.advance();
5801 match f.as_str() {
5802 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
5803 "as" | "alias" => alias = self.consume_any_ident_or_kw()?.value.clone(),
5804 "order_by" => {
5807 order_by = self.consume(TokenType::StringLit)?.value.clone()
5808 }
5809 "limit" => {
5814 let t = self.current().clone();
5815 match t.ttype {
5816 TokenType::Integer | TokenType::StringLit => {
5817 limit_expr = t.value.clone();
5818 self.advance();
5819 }
5820 _ => self.skip_value(),
5821 }
5822 }
5823 "aggregate" => {
5830 aggregate = self.consume(TokenType::StringLit)?.value.clone()
5831 }
5832 "group_by" => {
5833 group_by = self.consume(TokenType::StringLit)?.value.clone()
5834 }
5835 "cache" => cache = self.consume_any_ident_or_kw()?.value.clone(),
5841 _ => self.skip_value(),
5842 }
5843 }
5844 }
5845 if self.check(TokenType::RBrace) {
5846 self.advance();
5847 }
5848 }
5849 Ok(FlowStep::Retrieve(RetrieveStep {
5850 store_name: store,
5851 where_expr,
5852 alias,
5853 order_by,
5854 limit_expr,
5855 aggregate,
5856 group_by,
5857 cache,
5858 loc: Loc {
5859 line: tok.line,
5860 column: tok.column,
5861 },
5862 }))
5863 }
5864
5865 fn parse_store_where_step(
5878 &mut self,
5879 ) -> Result<(Loc, String, String), ParseError> {
5880 let tok = self.current().clone();
5881 self.advance(); let store = if self.at_declaration_start()
5883 || self.check(TokenType::RBrace)
5884 || self.check(TokenType::Eof)
5885 {
5886 String::new()
5887 } else {
5888 self.consume_any_ident_or_kw()?.value.clone()
5889 };
5890 let mut where_expr = String::new();
5891 if self.check(TokenType::LBrace) {
5892 self.advance();
5893 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5894 let field = self.current().value.clone();
5895 self.advance();
5896 if self.check(TokenType::Colon) {
5897 self.advance();
5898 match field.as_str() {
5899 "where" => {
5900 where_expr =
5901 self.consume(TokenType::StringLit)?.value.clone()
5902 }
5903 _ => self.skip_value(),
5904 }
5905 }
5906 }
5907 if self.check(TokenType::RBrace) {
5908 self.advance();
5909 }
5910 }
5911 Ok((
5912 Loc {
5913 line: tok.line,
5914 column: tok.column,
5915 },
5916 store,
5917 where_expr,
5918 ))
5919 }
5920
5921 fn parse_persist_step(&mut self) -> Result<FlowStep, ParseError> {
5940 let tok = self.current().clone();
5941 self.advance(); if self.current().value == "into" && !self.check(TokenType::LBrace) {
5945 self.advance();
5946 }
5947 let store = if self.at_declaration_start()
5948 || self.check(TokenType::LBrace)
5949 || self.check(TokenType::RBrace)
5950 || self.check(TokenType::Eof)
5951 {
5952 String::new()
5953 } else {
5954 self.consume_any_ident_or_kw()?.value.clone()
5955 };
5956 let mut fields: Vec<(String, String)> = Vec::new();
5957 if self.check(TokenType::LBrace) {
5958 self.advance();
5959 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5960 let col = self.current().value.clone();
5961 self.advance();
5962 if self.check(TokenType::Colon) {
5963 self.advance();
5964 let value = if self.check(TokenType::StringLit) {
5965 self.consume(TokenType::StringLit)?.value.clone()
5966 } else if self.check(TokenType::RBrace)
5967 || self.check(TokenType::Eof)
5968 || self.check(TokenType::Colon)
5969 {
5970 String::new()
5971 } else {
5972 let v = self.current().clone();
5973 self.advance();
5974 v.value.clone()
5975 };
5976 fields.push((col, value));
5977 }
5978 }
5979 if self.check(TokenType::RBrace) {
5980 self.advance();
5981 }
5982 }
5983 Ok(FlowStep::Persist(PersistStep {
5984 store_name: store,
5985 fields,
5986 loc: Loc {
5987 line: tok.line,
5988 column: tok.column,
5989 },
5990 }))
5991 }
5992
5993 fn parse_mutate_step(&mut self) -> Result<FlowStep, ParseError> {
6007 let tok = self.current().clone();
6008 self.advance(); let store = if self.at_declaration_start()
6010 || self.check(TokenType::LBrace)
6011 || self.check(TokenType::RBrace)
6012 || self.check(TokenType::Eof)
6013 {
6014 String::new()
6015 } else {
6016 self.consume_any_ident_or_kw()?.value.clone()
6017 };
6018 let mut where_expr = String::new();
6019 let mut fields: Vec<(String, String)> = Vec::new();
6020 if self.check(TokenType::LBrace) {
6021 self.advance();
6022 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6023 let key = self.current().value.clone();
6024 self.advance();
6025 if self.check(TokenType::Colon) {
6026 self.advance();
6027 if key == "where" {
6028 where_expr =
6029 self.consume(TokenType::StringLit)?.value.clone();
6030 } else {
6031 let value = if self.check(TokenType::StringLit) {
6032 self.consume(TokenType::StringLit)?.value.clone()
6033 } else if self.check(TokenType::RBrace)
6034 || self.check(TokenType::Eof)
6035 || self.check(TokenType::Colon)
6036 {
6037 String::new()
6038 } else {
6039 let v = self.current().clone();
6040 self.advance();
6041 v.value.clone()
6042 };
6043 fields.push((key, value));
6044 }
6045 }
6046 }
6047 if self.check(TokenType::RBrace) {
6048 self.advance();
6049 }
6050 }
6051 Ok(FlowStep::Mutate(MutateStep {
6052 store_name: store,
6053 where_expr,
6054 fields,
6055 loc: Loc {
6056 line: tok.line,
6057 column: tok.column,
6058 },
6059 }))
6060 }
6061
6062 fn parse_agent(&mut self) -> Result<AgentDefinition, ParseError> {
6065 let tok = self.consume(TokenType::Agent)?;
6066 let name = self.consume(TokenType::Identifier)?.value;
6067 let mut node = AgentDefinition {
6068 name,
6069 goal: String::new(),
6070 tools: Vec::new(),
6071 memory_ref: String::new(),
6072 strategy: String::new(),
6073 on_stuck: String::new(),
6074 shield_ref: String::new(),
6075 max_iterations: None,
6076 max_tokens: None,
6077 max_time: String::new(),
6078 max_cost: None,
6079 loc: Loc {
6080 line: tok.line,
6081 column: tok.column,
6082 },
6083 leading_trivia: Vec::new(),
6084 trailing_trivia: Vec::new(),
6085 };
6086 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
6088 self.advance();
6089 }
6090 self.consume(TokenType::LBrace)?;
6091 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6092 let field = self.current().clone();
6093 let field_name = field.value.clone();
6094 self.advance();
6095 if self.check(TokenType::Colon) {
6096 self.advance();
6097 match field_name.as_str() {
6098 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
6099 "tools" => node.tools = self.parse_bracketed_identifiers()?,
6100 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
6101 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
6102 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
6103 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6104 "max_iterations" => node.max_iterations = self.parse_optional_int(),
6105 "max_tokens" => node.max_tokens = self.parse_optional_int(),
6106 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
6107 "max_cost" => node.max_cost = self.parse_optional_float(),
6108 _ => self.skip_value(),
6109 }
6110 } else if self.check(TokenType::LBrace) {
6111 self.skip_braced_block()?;
6112 }
6113 }
6114 self.consume(TokenType::RBrace)?;
6115 Ok(node)
6116 }
6117
6118 fn parse_extension(&mut self) -> Result<ExtensionDefinition, ParseError> {
6123 let tok = self.consume(TokenType::Extension)?;
6124 let name = self.consume(TokenType::Identifier)?.value;
6125 let mut node = ExtensionDefinition {
6126 name,
6127 category: String::new(),
6128 members: Vec::new(),
6129 loc: Loc {
6130 line: tok.line,
6131 column: tok.column,
6132 },
6133 leading_trivia: Vec::new(),
6134 trailing_trivia: Vec::new(),
6135 };
6136 self.consume(TokenType::LBrace)?;
6137 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6138 let field_name = self.current().value.clone();
6139 self.advance();
6140 if self.check(TokenType::Colon) {
6141 self.advance();
6142 match field_name.as_str() {
6143 "category" => {
6144 node.category = self.consume_any_ident_or_kw()?.value.clone()
6145 }
6146 "members" => node.members = self.parse_extension_members()?,
6147 _ => self.skip_value(),
6148 }
6149 } else if self.check(TokenType::LBrace) {
6150 self.skip_braced_block()?;
6151 }
6152 }
6153 self.consume(TokenType::RBrace)?;
6154 Ok(node)
6155 }
6156
6157 fn parse_extension_members(&mut self) -> Result<Vec<ExtensionMember>, ParseError> {
6161 let mut members = Vec::new();
6162 self.consume(TokenType::LBracket)?;
6163 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
6164 let name_tok = self.consume(TokenType::StringLit)?;
6165 let mut member = ExtensionMember {
6166 name: name_tok.value.clone(),
6167 semantics: None,
6168 default_confidence: None,
6169 loc: Loc {
6170 line: name_tok.line,
6171 column: name_tok.column,
6172 },
6173 };
6174 if self.check(TokenType::Colon) {
6176 self.advance();
6177 self.consume(TokenType::LBrace)?;
6178 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6179 let mkey = self.current().value.clone();
6180 self.advance();
6181 if self.check(TokenType::Colon) {
6182 self.advance();
6183 match mkey.as_str() {
6184 "semantics" => {
6185 member.semantics =
6186 Some(self.consume(TokenType::StringLit)?.value.clone())
6187 }
6188 "default_confidence" => {
6189 member.default_confidence = self.parse_optional_float()
6190 }
6191 _ => self.skip_value(),
6192 }
6193 }
6194 if self.check(TokenType::Comma) {
6195 self.advance();
6196 }
6197 }
6198 self.consume(TokenType::RBrace)?;
6199 }
6200 members.push(member);
6201 if self.check(TokenType::Comma) {
6202 self.advance();
6203 }
6204 }
6205 self.consume(TokenType::RBracket)?;
6206 Ok(members)
6207 }
6208
6209 fn parse_window(&mut self) -> Result<WindowDefinition, ParseError> {
6212 let tok = self.consume(TokenType::Window)?;
6213 let name = self.consume(TokenType::Identifier)?.value;
6214 let mut node = WindowDefinition {
6215 name,
6216 timezone: String::new(),
6217 allow: Vec::new(),
6218 exclude: Vec::new(),
6219 on_outside: String::new(),
6220 loc: Loc {
6221 line: tok.line,
6222 column: tok.column,
6223 },
6224 leading_trivia: Vec::new(),
6225 trailing_trivia: Vec::new(),
6226 };
6227 self.consume(TokenType::LBrace)?;
6228 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6229 let field_name = self.consume_any_ident_or_kw()?.value;
6230 self.consume(TokenType::Colon)?;
6231 match field_name.as_str() {
6232 "timezone" => node.timezone = self.consume(TokenType::StringLit)?.value,
6233 "allow" => node.allow = self.parse_window_allow()?,
6234 "exclude" => node.exclude = self.parse_window_exclude()?,
6235 "on_outside" => node.on_outside = self.consume_any_ident_or_kw()?.value,
6236 _ => self.skip_value(),
6237 }
6238 }
6239 self.consume(TokenType::RBrace)?;
6240 Ok(node)
6241 }
6242
6243 fn parse_window_allow(&mut self) -> Result<Vec<WindowSpan>, ParseError> {
6245 self.consume(TokenType::LBracket)?;
6246 let mut spans = Vec::new();
6247 if !self.check(TokenType::RBracket) {
6248 spans.push(self.parse_window_span()?);
6249 while self.check(TokenType::Comma) {
6250 self.advance();
6251 if self.check(TokenType::RBracket) {
6252 break; }
6254 spans.push(self.parse_window_span()?);
6255 }
6256 }
6257 self.consume(TokenType::RBracket)?;
6258 Ok(spans)
6259 }
6260
6261 fn parse_window_exclude(&mut self) -> Result<Vec<String>, ParseError> {
6265 self.consume(TokenType::LBracket)?;
6266 let mut dates = Vec::new();
6267 if !self.check(TokenType::RBracket) {
6268 dates.push(self.consume(TokenType::StringLit)?.value);
6269 while self.check(TokenType::Comma) {
6270 self.advance();
6271 if self.check(TokenType::RBracket) {
6272 break; }
6274 dates.push(self.consume(TokenType::StringLit)?.value);
6275 }
6276 }
6277 self.consume(TokenType::RBracket)?;
6278 Ok(dates)
6279 }
6280
6281 fn parse_window_span(&mut self) -> Result<WindowSpan, ParseError> {
6283 let tok = self.consume(TokenType::LBrace)?;
6284 let mut span = WindowSpan {
6285 day_start: String::new(),
6286 day_end: String::new(),
6287 hour_start: 0,
6288 hour_end: 0,
6289 loc: Loc {
6290 line: tok.line,
6291 column: tok.column,
6292 },
6293 };
6294 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6295 let field = self.consume_any_ident_or_kw()?.value;
6296 self.consume(TokenType::Colon)?;
6297 match field.as_str() {
6298 "days" => {
6299 span.day_start = self.consume_any_ident_or_kw()?.value;
6300 self.consume(TokenType::DotDot)?;
6301 span.day_end = self.consume_any_ident_or_kw()?.value;
6302 }
6303 "hours" => {
6304 span.hour_start = self.consume_number()? as i64;
6305 self.consume(TokenType::DotDot)?;
6306 span.hour_end = self.consume_number()? as i64;
6307 }
6308 _ => self.skip_value(),
6309 }
6310 if self.check(TokenType::Comma) {
6311 self.advance();
6312 }
6313 }
6314 self.consume(TokenType::RBrace)?;
6315 Ok(span)
6316 }
6317
6318 fn parse_shield(&mut self) -> Result<ShieldDefinition, ParseError> {
6319 let tok = self.consume(TokenType::Shield)?;
6320 let name = self.consume(TokenType::Identifier)?.value;
6321 let mut node = ShieldDefinition {
6322 name,
6323 scan: Vec::new(),
6324 strategy: String::new(),
6325 on_breach: String::new(),
6326 severity: String::new(),
6327 quarantine: String::new(),
6328 max_retries: None,
6329 confidence_threshold: None,
6330 allow_tools: Vec::new(),
6331 deny_tools: Vec::new(),
6332 sandbox: None,
6333 redact: Vec::new(),
6334 log: String::new(),
6335 deflect_message: String::new(),
6336 taint: String::new(),
6337 compliance: Vec::new(),
6338 sign: String::new(),
6339 unknown_fields: Vec::new(),
6340 loc: Loc {
6341 line: tok.line,
6342 column: tok.column,
6343 },
6344 leading_trivia: Vec::new(),
6345 trailing_trivia: Vec::new(),
6346 };
6347 self.consume(TokenType::LBrace)?;
6348 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6349 let field_name = self.current().value.clone();
6350 let field_loc = Loc {
6351 line: self.current().line,
6352 column: self.current().column,
6353 };
6354 self.advance();
6355 if self.check(TokenType::Colon) {
6356 self.advance();
6357 match field_name.as_str() {
6358 "scan" => node.scan = self.parse_bracketed_identifiers()?,
6359 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
6360 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
6361 "severity" => node.severity = self.consume_any_ident_or_kw()?.value.clone(),
6362 "quarantine" => {
6363 node.quarantine = self.consume(TokenType::StringLit)?.value.clone()
6364 }
6365 "max_retries" => node.max_retries = self.parse_optional_int(),
6366 "confidence_threshold" => {
6367 node.confidence_threshold = self.parse_optional_float()
6368 }
6369 "allow_tools" => node.allow_tools = self.parse_bracketed_identifiers()?,
6370 "deny_tools" => node.deny_tools = self.parse_bracketed_identifiers()?,
6371 "sandbox" => {
6372 node.sandbox = Some(self.consume_any_ident_or_kw()?.value == "true")
6373 }
6374 "redact" => node.redact = self.parse_bracketed_identifiers()?,
6375 "log" => node.log = self.consume_any_ident_or_kw()?.value.clone(),
6376 "deflect_message" => {
6377 node.deflect_message = self.consume(TokenType::StringLit)?.value.clone()
6378 }
6379 "taint" => node.taint = self.consume_any_ident_or_kw()?.value.clone(),
6380 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
6382 "sign" => node.sign = self.consume_any_ident_or_kw()?.value.clone(),
6385 _ => {
6389 node.unknown_fields.push((field_name.clone(), field_loc));
6390 self.skip_value()
6391 }
6392 }
6393 } else if self.check(TokenType::LBrace) {
6394 self.skip_braced_block()?;
6395 }
6396 }
6397 self.consume(TokenType::RBrace)?;
6398 Ok(node)
6399 }
6400
6401 fn parse_pix(&mut self) -> Result<PixDefinition, ParseError> {
6402 let tok = self.consume(TokenType::Pix)?;
6403 let name = self.consume(TokenType::Identifier)?.value;
6404 let mut node = PixDefinition {
6405 name,
6406 source: String::new(),
6407 depth: None,
6408 branching: None,
6409 model: String::new(),
6410 loc: Loc {
6411 line: tok.line,
6412 column: tok.column,
6413 },
6414 leading_trivia: Vec::new(),
6415 trailing_trivia: Vec::new(),
6416 };
6417 self.consume(TokenType::LBrace)?;
6418 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6419 let field_name = self.current().value.clone();
6420 self.advance();
6421 if self.check(TokenType::Colon) {
6422 self.advance();
6423 match field_name.as_str() {
6424 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
6425 "depth" => node.depth = self.parse_optional_int(),
6426 "branching" => node.branching = self.parse_optional_int(),
6427 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
6428 _ => self.skip_value(),
6429 }
6430 } else if self.check(TokenType::LBrace) {
6431 self.skip_braced_block()?;
6432 }
6433 }
6434 self.consume(TokenType::RBrace)?;
6435 Ok(node)
6436 }
6437
6438 fn parse_ledger(&mut self) -> Result<LedgerDefinition, ParseError> {
6444 let tok = self.consume(TokenType::Ledger)?;
6445 let name = self.consume(TokenType::Identifier)?.value;
6446 let mut node = LedgerDefinition {
6447 name,
6448 source: String::new(),
6449 depth: None,
6450 branching: None,
6451 model: String::new(),
6452 loc: Loc {
6453 line: tok.line,
6454 column: tok.column,
6455 },
6456 leading_trivia: Vec::new(),
6457 trailing_trivia: Vec::new(),
6458 };
6459 self.consume(TokenType::LBrace)?;
6460 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6461 let field_name = self.current().value.clone();
6462 self.advance();
6463 if self.check(TokenType::Colon) {
6464 self.advance();
6465 match field_name.as_str() {
6466 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
6467 "depth" => node.depth = self.parse_optional_int(),
6468 "branching" => node.branching = self.parse_optional_int(),
6469 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
6470 _ => self.skip_value(),
6471 }
6472 } else if self.check(TokenType::LBrace) {
6473 self.skip_braced_block()?;
6474 }
6475 }
6476 self.consume(TokenType::RBrace)?;
6477 Ok(node)
6478 }
6479
6480 fn parse_psyche(&mut self) -> Result<PsycheDefinition, ParseError> {
6481 let tok = self.consume(TokenType::Psyche)?;
6482 let name = self.consume(TokenType::Identifier)?.value;
6483 let mut node = PsycheDefinition {
6484 name,
6485 dimensions: Vec::new(),
6486 manifold_noise: None,
6487 manifold_momentum: None,
6488 safety_constraints: Vec::new(),
6489 quantum_enabled: None,
6490 inference_mode: String::new(),
6491 loc: Loc {
6492 line: tok.line,
6493 column: tok.column,
6494 },
6495 leading_trivia: Vec::new(),
6496 trailing_trivia: Vec::new(),
6497 };
6498 self.consume(TokenType::LBrace)?;
6499 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6500 let field_name = self.current().value.clone();
6501 self.advance();
6502 if self.check(TokenType::Colon) {
6503 self.advance();
6504 match field_name.as_str() {
6505 "dimensions" => node.dimensions = self.parse_bracketed_identifiers()?,
6506 "manifold_noise" => node.manifold_noise = self.parse_optional_float(),
6507 "manifold_momentum" => node.manifold_momentum = self.parse_optional_float(),
6508 "safety_constraints" | "safety" => {
6513 node.safety_constraints = self.parse_bracketed_identifiers()?
6514 }
6515 "quantum_enabled" => {
6516 node.quantum_enabled = Some(self.consume_any_ident_or_kw()?.value == "true")
6517 }
6518 "inference_mode" => {
6519 node.inference_mode = self.consume_any_ident_or_kw()?.value.clone()
6520 }
6521 _ => self.skip_value(),
6522 }
6523 } else if self.check(TokenType::LBrace) {
6524 self.skip_braced_block()?;
6525 }
6526 }
6527 self.consume(TokenType::RBrace)?;
6528 Ok(node)
6529 }
6530
6531 fn parse_corpus(&mut self) -> Result<CorpusDefinition, ParseError> {
6532 let tok = self.consume(TokenType::Corpus)?;
6533 let name = self.consume(TokenType::Identifier)?.value;
6534 let mut node = CorpusDefinition {
6535 name,
6536 documents: Vec::new(),
6537 relations: Vec::new(),
6538 adaptive: false,
6539 mcp_server: String::new(),
6540 mcp_resource_uri: String::new(),
6541 store_source: None,
6542 loc: Loc {
6543 line: tok.line,
6544 column: tok.column,
6545 },
6546 leading_trivia: Vec::new(),
6547 trailing_trivia: Vec::new(),
6548 };
6549 let mut dynamic = false;
6553 if self.check(TokenType::From) {
6554 self.advance();
6555 if self.check(TokenType::AxonStore) {
6556 self.advance();
6557 dynamic = true;
6558 } else {
6559 self.consume(TokenType::Mcp)?;
6560 self.consume(TokenType::LParen)?;
6561 node.mcp_server = self.consume(TokenType::StringLit)?.value.clone();
6562 self.consume(TokenType::Comma)?;
6563 node.mcp_resource_uri = self.consume(TokenType::StringLit)?.value.clone();
6564 self.consume(TokenType::RParen)?;
6565 return Ok(node);
6566 }
6567 }
6568 self.consume(TokenType::LBrace)?;
6569 let mut src = CorpusStoreSource {
6572 doc_store: String::new(),
6573 doc_id_col: String::new(),
6574 doc_title_col: String::new(),
6575 edge_store: String::new(),
6576 edge_from_col: String::new(),
6577 edge_to_col: String::new(),
6578 edge_type_col: String::new(),
6579 edge_weight_col: String::new(),
6580 loc: Loc {
6581 line: tok.line,
6582 column: tok.column,
6583 },
6584 };
6585 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6586 let field_name = self.current().value.clone();
6587 self.advance();
6588 if self.check(TokenType::Colon) {
6589 self.advance();
6590 match field_name.as_str() {
6591 "documents" if dynamic => {
6593 let (store, cols) = self.parse_corpus_store_mapping(2)?;
6594 src.doc_store = store;
6595 src.doc_id_col = cols[0].clone();
6596 src.doc_title_col = cols[1].clone();
6597 }
6598 "documents" => node.documents = self.parse_bracketed_identifiers()?,
6599 "relations" if dynamic => {
6601 let (store, cols) = self.parse_corpus_store_mapping(4)?;
6602 src.edge_store = store;
6603 src.edge_from_col = cols[0].clone();
6604 src.edge_to_col = cols[1].clone();
6605 src.edge_type_col = cols[2].clone();
6606 src.edge_weight_col = cols[3].clone();
6607 }
6608 "relations" => node.relations = self.parse_corpus_relations()?,
6610 "adaptive" => node.adaptive = self.consume_any_ident_or_kw()?.value == "true",
6612 _ => self.skip_value(),
6613 }
6614 } else if self.check(TokenType::LBrace) {
6615 self.skip_braced_block()?;
6616 }
6617 }
6618 self.consume(TokenType::RBrace)?;
6619 if dynamic {
6620 node.store_source = Some(src);
6621 }
6622 Ok(node)
6623 }
6624
6625 fn parse_corpus_store_mapping(&mut self, n: usize) -> Result<(String, Vec<String>), ParseError> {
6632 let store = self.consume(TokenType::Identifier)?.value.clone();
6633 self.consume(TokenType::LParen)?;
6634 let mut cols = Vec::with_capacity(n);
6635 for i in 0..n {
6636 if i > 0 {
6637 self.consume(TokenType::Comma)?;
6638 }
6639 cols.push(self.consume_any_ident_or_kw()?.value.clone());
6640 }
6641 self.consume(TokenType::RParen)?;
6642 Ok((store, cols))
6643 }
6644
6645 fn parse_corpus_relations(&mut self) -> Result<Vec<CorpusRelation>, ParseError> {
6651 let mut out = Vec::new();
6652 self.consume(TokenType::LBracket)?;
6653 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
6654 if self.check(TokenType::Comma) {
6655 self.advance();
6656 continue;
6657 }
6658 let tok = self.current().clone();
6659 let etype = self.consume_any_ident_or_kw()?.value.clone();
6660 self.consume(TokenType::LParen)?;
6661 let from = self.consume_any_ident_or_kw()?.value.clone();
6662 self.consume(TokenType::Comma)?;
6663 let to = self.consume_any_ident_or_kw()?.value.clone();
6664 self.consume(TokenType::Comma)?;
6665 let weight = self.consume_number()?;
6666 self.consume(TokenType::RParen)?;
6667 out.push(CorpusRelation {
6668 etype,
6669 from,
6670 to,
6671 weight,
6672 loc: Loc { line: tok.line, column: tok.column },
6673 });
6674 }
6675 self.consume(TokenType::RBracket)?;
6676 Ok(out)
6677 }
6678
6679 fn parse_dataspace(&mut self) -> Result<DataspaceDefinition, ParseError> {
6696 let tok = self.consume(TokenType::Dataspace)?;
6697 let name = self.consume(TokenType::Identifier)?.value;
6698 let mut node = DataspaceDefinition {
6699 name,
6700 columns: Vec::new(),
6701 loc: Loc {
6702 line: tok.line,
6703 column: tok.column,
6704 },
6705 leading_trivia: Vec::new(),
6706 trailing_trivia: Vec::new(),
6707 };
6708 if self.check(TokenType::LBrace) {
6709 self.consume(TokenType::LBrace)?;
6710 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6711 let entry = self.current().clone();
6712 if entry.value != "column" {
6713 return Err(ParseError {
6714 message: format!(
6715 "Unknown entry `{}` in dataspace `{}`. A dataspace body \
6716 declares its columnar schema: `column <name>: <Type>` \
6717 (one per line, over the closed type catalog — \
6718 Text, Int, Float, Bool, Timestamp, Json).",
6719 entry.value, node.name
6720 ),
6721 line: entry.line,
6722 column: entry.column,
6723 ..Default::default()
6724 });
6725 }
6726 self.advance(); let col_tok = self.current().clone();
6728 let col_name = self.consume_any_ident_or_kw()?.value.clone();
6729 self.consume(TokenType::Colon)?;
6730 let declared_type = self.consume_any_ident_or_kw()?.value.clone();
6731 node.columns.push(crate::ast::DataspaceColumn {
6732 name: col_name,
6733 declared_type,
6734 loc: Loc {
6735 line: col_tok.line,
6736 column: col_tok.column,
6737 },
6738 });
6739 }
6740 self.consume(TokenType::RBrace)?;
6741 }
6742 Ok(node)
6743 }
6744
6745 fn parse_ots(&mut self) -> Result<OtsDefinition, ParseError> {
6746 let tok = self.consume(TokenType::Ots)?;
6747 let name = self.consume(TokenType::Identifier)?.value;
6748 let mut node = OtsDefinition {
6749 name,
6750 teleology: String::new(),
6751 homotopy_search: String::new(),
6752 loss_function: String::new(),
6753 loc: Loc {
6754 line: tok.line,
6755 column: tok.column,
6756 },
6757 leading_trivia: Vec::new(),
6758 trailing_trivia: Vec::new(),
6759 };
6760 if self.check(TokenType::Lt) {
6762 while !self.check(TokenType::Gt) && !self.check(TokenType::Eof) {
6763 self.advance();
6764 }
6765 if self.check(TokenType::Gt) {
6766 self.advance();
6767 }
6768 }
6769 self.consume(TokenType::LBrace)?;
6770 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6771 let field_name = self.current().value.clone();
6772 self.advance();
6773 if self.check(TokenType::Colon) {
6774 self.advance();
6775 match field_name.as_str() {
6776 "teleology" => {
6777 node.teleology = self.consume(TokenType::StringLit)?.value.clone()
6778 }
6779 "homotopy_search" => {
6780 node.homotopy_search = self.consume_any_ident_or_kw()?.value.clone()
6781 }
6782 "loss_function" => {
6788 node.loss_function = if self.check(TokenType::StringLit) {
6789 self.consume(TokenType::StringLit)?.value.clone()
6790 } else {
6791 self.consume_any_ident_or_kw()?.value.clone()
6792 }
6793 }
6794 _ => self.skip_value(),
6795 }
6796 } else if self.check(TokenType::LBrace) {
6797 self.skip_braced_block()?;
6798 }
6799 }
6800 self.consume(TokenType::RBrace)?;
6801 Ok(node)
6802 }
6803
6804 fn parse_mandate(&mut self) -> Result<MandateDefinition, ParseError> {
6805 let tok = self.consume(TokenType::Mandate)?;
6806 let name = self.consume(TokenType::Identifier)?.value;
6807 let mut node = MandateDefinition {
6808 name,
6809 constraint: String::new(),
6810 kp: None,
6811 ki: None,
6812 kd: None,
6813 tolerance: None,
6814 max_steps: None,
6815 drift_bound: None,
6816 lipschitz: None,
6817 on_violation: String::new(),
6818 loc: Loc {
6819 line: tok.line,
6820 column: tok.column,
6821 },
6822 leading_trivia: Vec::new(),
6823 trailing_trivia: Vec::new(),
6824 };
6825 self.consume(TokenType::LBrace)?;
6826 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6827 let field_name = self.current().value.clone();
6828 self.advance();
6829 if self.check(TokenType::Colon) {
6830 self.advance();
6831 match field_name.as_str() {
6832 "constraint" => {
6833 node.constraint = self.consume(TokenType::StringLit)?.value.clone()
6834 }
6835 "kp" | "Kp" => node.kp = self.parse_optional_float(),
6836 "ki" | "Ki" => node.ki = self.parse_optional_float(),
6837 "kd" | "Kd" => node.kd = self.parse_optional_float(),
6838 "max_steps" => node.max_steps = self.parse_optional_int(),
6839 "tolerance" | "epsilon" => node.tolerance = self.parse_optional_float(),
6844 "on_violation" => {
6845 node.on_violation = self.consume_any_ident_or_kw()?.value.clone()
6846 }
6847 _ => self.skip_value(),
6848 }
6849 } else if self.check(TokenType::LBrace) {
6850 if field_name == "pid" {
6860 self.parse_pid_block(&mut node)?;
6861 } else if field_name == "stability" {
6862 self.parse_stability_block(&mut node)?;
6863 } else {
6864 self.skip_braced_block()?;
6865 }
6866 }
6867 }
6868 self.consume(TokenType::RBrace)?;
6869 Ok(node)
6870 }
6871
6872 fn parse_pid_block(&mut self, node: &mut MandateDefinition) -> Result<(), ParseError> {
6895 self.consume(TokenType::LBrace)?;
6896 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6897 let key_token = self.current().clone();
6898 let key = key_token.value.clone();
6899 self.advance();
6900 if self.check(TokenType::Colon) {
6901 self.advance();
6902 match key.as_str() {
6903 "kp" | "Kp" => node.kp = self.parse_optional_float(),
6904 "ki" | "Ki" => node.ki = self.parse_optional_float(),
6905 "kd" | "Kd" => node.kd = self.parse_optional_float(),
6906 _ => {
6907 return Err(ParseError {
6908 message: format!(
6909 "`{key}` is not a gain of the PID controller. The block accepts \
6910 exactly `Kp`, `Ki` and `Kd` (lower-case spellings too). \
6911 Skipping what it does not recognise would let a typo drop a \
6912 gain, and the stability band is computed from all three."
6913 ),
6914 line: key_token.line,
6915 column: key_token.column,
6916 ..Default::default()
6917 });
6918 }
6919 }
6920 }
6921 if self.check(TokenType::Comma) {
6922 self.advance();
6923 }
6924 }
6925 self.consume(TokenType::RBrace)?;
6926 Ok(())
6927 }
6928
6929 fn parse_stability_block(
6945 &mut self,
6946 node: &mut MandateDefinition,
6947 ) -> Result<(), ParseError> {
6948 let open = self.consume(TokenType::LBrace)?;
6949 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6950 let key_token = self.current().clone();
6951 let key = key_token.value.clone();
6952 self.advance();
6953 if self.check(TokenType::Colon) {
6954 self.advance();
6955 match key.as_str() {
6956 "D" | "d" | "drift_bound" => {
6957 node.drift_bound = self.parse_optional_float()
6958 }
6959 "L" | "l" | "lipschitz" => node.lipschitz = self.parse_optional_float(),
6960 _ => {
6964 return Err(ParseError {
6965 message: format!(
6966 "`{key}` is not a hypothesis of the stability theorem. The block \
6967 accepts exactly `D` (the drift bound, also spelled `d` or \
6968 `drift_bound`) and `L` (the Lipschitz constant, also `l` or \
6969 `lipschitz`). This is an error rather than a skipped key \
6970 because a bound that fails to parse is a bound that is not \
6971 declared, and the compiler would then verify the band it can \
6972 see — the sign conditions — and admit the mandate as if the \
6973 rest had been checked."
6974 ),
6975 line: key_token.line,
6976 column: key_token.column,
6977 ..Default::default()
6978 });
6979 }
6980 }
6981 }
6982 if self.check(TokenType::Comma) {
6983 self.advance();
6984 }
6985 }
6986 self.consume(TokenType::RBrace)?;
6987 if node.drift_bound.is_none() && node.lipschitz.is_none() {
6988 return Err(ParseError {
6989 message: "the `stability { }` block declares neither `D` nor `L` — it asserts nothing and can discharge nothing. Declare the drift bound (`D:`), the Lipschitz constant (`L:`), or both; or remove the block."
6990 .to_string(),
6991 line: open.line,
6992 column: open.column,
6993 ..Default::default()
6994 });
6995 }
6996 Ok(())
6997 }
6998
6999 fn parse_compute(&mut self) -> Result<ComputeDefinition, ParseError> {
7028 let tok = self.consume(TokenType::Compute)?;
7029 let name = self.consume(TokenType::Identifier)?.value;
7030 let mut node = ComputeDefinition {
7031 name,
7032 shield_ref: String::new(),
7033 parameters: Vec::new(),
7034 return_type: String::new(),
7035 body: None,
7036 loc: Loc {
7037 line: tok.line,
7038 column: tok.column,
7039 },
7040 leading_trivia: Vec::new(),
7041 trailing_trivia: Vec::new(),
7042 };
7043
7044 if self.check(TokenType::LParen) {
7046 self.advance();
7047 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
7048 let ptok = self.current().clone();
7049 let pname = self.consume_any_ident_or_kw()?.value.clone();
7050 self.consume(TokenType::Colon)?;
7051 let ptype = self.parse_type_expr()?;
7052 node.parameters.push(Parameter {
7053 name: pname,
7054 type_expr: ptype,
7055 loc: self.loc_of(&ptok),
7056 });
7057 if self.check(TokenType::Comma) {
7058 self.advance();
7059 }
7060 }
7061 self.consume(TokenType::RParen)?;
7062 }
7063
7064 if self.check(TokenType::Arrow) {
7066 self.advance();
7067 node.return_type = self.consume_any_ident_or_kw()?.value.clone();
7068 }
7069
7070 self.consume(TokenType::LBrace)?;
7071 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7072 let is_field = self
7082 .tokens
7083 .get(self.pos + 1)
7084 .map(|t| t.ttype == TokenType::Colon)
7085 .unwrap_or(false);
7086 if is_field {
7087 let field_name = self.current().value.clone();
7088 self.advance();
7089 self.consume(TokenType::Colon)?;
7090 match field_name.as_str() {
7091 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
7092 _ => self.skip_value(),
7093 }
7094 } else {
7095 node.body = Some(self.parse_expr()?);
7096 }
7097 }
7098 self.consume(TokenType::RBrace)?;
7099 Ok(node)
7100 }
7101
7102 fn parse_daemon(&mut self) -> Result<DaemonDefinition, ParseError> {
7103 let tok = self.consume(TokenType::Daemon)?;
7104 let name = self.consume(TokenType::Identifier)?.value;
7105 let mut node = DaemonDefinition {
7106 name,
7107 goal: String::new(),
7108 tools: Vec::new(),
7109 memory_ref: String::new(),
7110 strategy: String::new(),
7111 on_stuck: String::new(),
7112 shield_ref: String::new(),
7113 window_ref: String::new(),
7114 budget: None,
7115 max_tokens: None,
7116 max_time: String::new(),
7117 max_cost: None,
7118 listeners: Vec::new(),
7119 requires_capabilities: Vec::new(),
7120 loc: Loc {
7121 line: tok.line,
7122 column: tok.column,
7123 },
7124 leading_trivia: Vec::new(),
7125 trailing_trivia: Vec::new(),
7126 };
7127 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
7129 self.advance();
7130 }
7131 self.consume(TokenType::LBrace)?;
7132 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7133 let field = self.current().clone();
7134 let field_name = field.value.clone();
7135 self.advance();
7136 if self.check(TokenType::Colon) {
7137 self.advance();
7138 match field_name.as_str() {
7139 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
7140 "tools" => node.tools = self.parse_bracketed_identifiers()?,
7141 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
7142 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
7143 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
7144 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
7145 "window" => node.window_ref = self.consume_any_ident_or_kw()?.value.clone(),
7147 "max_tokens" => node.max_tokens = self.parse_optional_int(),
7148 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
7149 "max_cost" => node.max_cost = self.parse_optional_float(),
7150 "requires" => {
7155 let bracket_tok = self.current().clone();
7156 let items = self.parse_bracketed_dot_identifiers()?;
7157 for slug in &items {
7158 if !is_valid_capability_slug(slug) {
7159 return Err(ParseError {
7160 message: format!(
7161 "Invalid capability slug '{slug}' in daemon '{}' \
7162 `requires:`. Capability slugs must match \
7163 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
7164 lowercase identifiers. Examples: `daemon.run`, \
7165 `memory.write`, `flow.execute`.",
7166 node.name
7167 ),
7168 line: bracket_tok.line,
7169 column: bracket_tok.column,
7170 ..Default::default()
7171 });
7172 }
7173 }
7174 node.requires_capabilities = items;
7175 }
7176 _ => self.skip_value(),
7177 }
7178 } else if field.ttype == TokenType::Listen {
7179 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
7185 (self.consume(TokenType::StringLit)?.value.clone(), false)
7186 } else {
7187 (self.consume_any_ident_or_kw()?.value.clone(), true)
7188 };
7189 let mut alias = String::new();
7190 if !self.at_declaration_start()
7191 && !self.check(TokenType::RBrace)
7192 && !self.check(TokenType::LBrace)
7193 {
7194 let next = self.current().clone();
7195 if next.value == "as" || next.ttype == TokenType::As {
7196 self.advance();
7197 alias = self.consume_any_ident_or_kw()?.value.clone();
7198 }
7199 }
7200 let listen_loc = Loc {
7201 line: field.line,
7202 column: field.column,
7203 };
7204 let body = self.parse_listener_body()?;
7208 node.listeners.push(ListenStep {
7209 channel,
7210 channel_is_ref,
7211 event_alias: alias,
7212 body,
7213 loc: listen_loc,
7214 });
7215 } else if field_name == "budget" && self.check(TokenType::LBrace) {
7216 node.budget = Some(self.parse_budget_block(field.line, field.column)?);
7218 } else if self.check(TokenType::LBrace) {
7219 self.skip_braced_block()?;
7220 }
7221 }
7222 self.consume(TokenType::RBrace)?;
7223 Ok(node)
7224 }
7225
7226 fn parse_top_level_budget(&mut self) -> Result<BudgetBlock, ParseError> {
7235 let kw = self.consume(TokenType::Budget)?; let name = self.consume(TokenType::Identifier)?.value;
7237 let mut block = self.parse_budget_block(kw.line, kw.column)?;
7238 block.name = name;
7239 Ok(block)
7240 }
7241
7242 fn parse_budget_block(&mut self, line: u32, column: u32) -> Result<BudgetBlock, ParseError> {
7244 self.consume(TokenType::LBrace)?;
7245 let mut quotas = Vec::new();
7246 let mut on_exhausted = String::new();
7247 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7248 let field = self.current().clone();
7249 let field_name = self.consume_any_ident_or_kw()?.value;
7250 match field_name.as_str() {
7251 "rate" | "max" => {
7252 quotas.push(self.parse_budget_quota(field_name, field.line, field.column)?);
7253 }
7254 "on_exhausted" => {
7255 self.consume(TokenType::Colon)?;
7256 on_exhausted = self.consume_any_ident_or_kw()?.value;
7257 }
7258 _ => self.skip_value(),
7259 }
7260 }
7261 self.consume(TokenType::RBrace)?;
7262 Ok(BudgetBlock {
7263 name: String::new(),
7264 quotas,
7265 on_exhausted,
7266 loc: Loc { line, column },
7267 leading_trivia: Vec::new(),
7268 trailing_trivia: Vec::new(),
7269 })
7270 }
7271
7272 fn parse_budget_quota(
7275 &mut self,
7276 kind: String,
7277 line: u32,
7278 column: u32,
7279 ) -> Result<BudgetQuota, ParseError> {
7280 self.consume(TokenType::Colon)?;
7281 let limit = self.consume_number()? as i64;
7282 let _per = self.consume_any_ident_or_kw()?; let period = self.consume_any_ident_or_kw()?.value;
7285 let _on = self.consume_any_ident_or_kw()?; let _tool = self.consume_any_ident_or_kw()?; self.consume(TokenType::LParen)?;
7289 let effect = self.consume_any_ident_or_kw()?.value;
7290 self.consume(TokenType::RParen)?;
7291 Ok(BudgetQuota {
7292 kind,
7293 limit,
7294 period,
7295 effect,
7296 loc: Loc { line, column },
7297 })
7298 }
7299
7300 fn parse_axonstore(&mut self) -> Result<AxonStoreDefinition, ParseError> {
7301 let tok = self.consume(TokenType::AxonStore)?;
7302 let name = self.consume(TokenType::Identifier)?.value;
7303 let mut node = AxonStoreDefinition {
7304 name,
7305 backend: String::new(),
7306 connection: String::new(),
7307 resource_ref: String::new(),
7308 confidence_floor: None,
7309 isolation: String::new(),
7310 on_breach: String::new(),
7311 capability: String::new(),
7312 class: String::new(),
7313 column_schema: None,
7314 loc: Loc {
7315 line: tok.line,
7316 column: tok.column,
7317 },
7318 leading_trivia: Vec::new(),
7319 trailing_trivia: Vec::new(),
7320 };
7321 self.consume(TokenType::LBrace)?;
7322 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7323 let field = self.current().clone();
7324 let field_name = field.value.clone();
7325 if field.ttype == TokenType::Schema {
7331 self.advance();
7332 let parsed = self.parse_store_schema_declaration(&node.name, field.line, field.column)?;
7333 node.column_schema = Some(parsed);
7334 continue;
7335 }
7336 self.advance();
7337 if self.check(TokenType::Colon) {
7338 self.advance();
7339 match field_name.as_str() {
7340 "backend" => node.backend = self.consume_any_ident_or_kw()?.value.clone(),
7341 "class" => node.class = self.parse_dotted_identifier()?,
7349 "connection" => node.connection = self.parse_config_key()?,
7350 "resource" => {
7356 node.resource_ref = self.consume_any_ident_or_kw()?.value.clone()
7357 }
7358 "confidence_floor" => node.confidence_floor = self.parse_optional_float(),
7359 "isolation" => node.isolation = self.consume_any_ident_or_kw()?.value.clone(),
7360 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
7361 "capability" => {
7365 let slug_tok = self.consume(TokenType::StringLit)?.clone();
7366 if !is_valid_capability_slug(&slug_tok.value) {
7367 return Err(ParseError {
7368 message: format!(
7369 "Invalid capability slug '{}' in axonstore '{}' \
7370 `capability:`. Capability slugs must match \
7371 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
7372 lowercase identifiers starting with a letter. Examples: \
7373 `admin`, `tenant.read`, `hipaa.phi.read`.",
7374 slug_tok.value, node.name
7375 ),
7376 line: slug_tok.line,
7377 column: slug_tok.column,
7378 ..Default::default()
7379 });
7380 }
7381 node.capability = slug_tok.value.clone();
7382 }
7383 _ => self.skip_value(),
7384 }
7385 } else if self.check(TokenType::LBrace) {
7386 self.skip_braced_block()?;
7387 }
7388 }
7389 self.consume(TokenType::RBrace)?;
7390 Ok(node)
7391 }
7392
7393 fn parse_store_schema_declaration(
7405 &mut self,
7406 store_name: &str,
7407 sch_line: u32,
7408 sch_col: u32,
7409 ) -> Result<crate::store_schema::StoreColumnSchema, ParseError> {
7410 use crate::store_schema::{StoreColumn, StoreColumnSchema, StoreColumnType};
7411
7412 if self.check(TokenType::LBrace) {
7414 self.consume(TokenType::LBrace)?;
7415 let mut columns: Vec<StoreColumn> = Vec::new();
7416 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7417 let col_tok = self.current().clone();
7418 let col_name = self.consume_any_ident_or_kw()?.value.clone();
7419 self.consume(TokenType::Colon)?;
7420 let type_tok = self.consume_any_ident_or_kw()?.clone();
7421 let col_type = StoreColumnType::from_token(&type_tok.value).ok_or_else(|| {
7422 let names = StoreColumnType::all_canonical_names();
7423 let suggestion =
7424 crate::smart_suggest::suggest_for(&type_tok.value, &names);
7425 let suggest_suffix = if suggestion.is_empty() {
7426 String::new()
7427 } else {
7428 format!(" {suggestion}")
7429 };
7430 let known = names.join(", ");
7431 ParseError {
7432 message: format!(
7433 "Unknown column type `{}` for column `{}` in \
7434 axonstore `{}` `schema:` block. The closed \
7435 v1.38.0 column-type catalog (Fase 38.b D1) \
7436 is {{{known}}} (plus common lowercase \
7437 aliases — `int`/`integer`/`int4` for \
7438 `Int`, `bool`/`boolean` for `Bool`, etc.).\
7439 {suggest_suffix}",
7440 type_tok.value, col_name, store_name
7441 ),
7442 line: type_tok.line,
7443 column: type_tok.column,
7444 ..Default::default()
7445 }
7446 })?;
7447
7448 let mut json_shape: Option<String> = None;
7458 if self.check(TokenType::Lt) {
7459 self.advance();
7460 let shape_tok = self.consume_any_ident_or_kw()?.clone();
7461 self.consume(TokenType::Gt)?;
7462 if matches!(col_type, StoreColumnType::Json | StoreColumnType::Jsonb) {
7463 json_shape = Some(shape_tok.value.clone());
7464 } else {
7465 return Err(ParseError {
7466 message: format!(
7467 "axon-T841 a shape lens `<{shape}>` may refine \
7468 only a `Json` / `Jsonb` column, but column \
7469 `{col}` in axonstore `{store}` is `{ty}`. Drop \
7470 the `<{shape}>` (a rigid column already has a \
7471 fixed shape), or change the column type to \
7472 `Json<{shape}>` if it carries open documents.",
7473 shape = shape_tok.value,
7474 col = col_name,
7475 store = store_name,
7476 ty = col_type.canonical_name(),
7477 ),
7478 line: shape_tok.line,
7479 column: shape_tok.column,
7480 ..Default::default()
7481 });
7482 }
7483 }
7484
7485 let mut col = StoreColumn {
7486 name: col_name,
7487 col_type,
7488 json_shape,
7489 primary_key: false,
7490 auto_increment: false,
7491 not_null: false,
7492 unique: false,
7493 indexed: false,
7494 default_value: String::new(),
7495 identity: false,
7500 line: col_tok.line,
7501 column: col_tok.column,
7502 };
7503
7504 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7507 if self.current().ttype != TokenType::Identifier {
7508 break;
7511 }
7512 let constraint = self.current().value.clone();
7513 match constraint.as_str() {
7514 "primary_key" => {
7515 col.primary_key = true;
7516 self.advance();
7517 }
7518 "auto_increment" => {
7519 col.auto_increment = true;
7520 self.advance();
7521 }
7522 "not_null" => {
7523 col.not_null = true;
7524 self.advance();
7525 }
7526 "unique" => {
7527 col.unique = true;
7528 self.advance();
7529 }
7530 "index" => {
7536 col.indexed = true;
7537 self.advance();
7538 }
7539 "identity" => {
7550 col.identity = true;
7551 self.advance();
7552 }
7553 "default" => {
7554 self.advance();
7555 let dv = self.current().clone();
7556 if matches!(
7557 dv.ttype,
7558 TokenType::StringLit
7559 | TokenType::Integer
7560 | TokenType::Float
7561 ) {
7562 col.default_value = dv.value.clone();
7563 self.advance();
7564 } else {
7565 col.default_value =
7566 self.consume_any_ident_or_kw()?.value.clone();
7567 }
7568 }
7569 _ => break,
7570 }
7571 }
7572
7573 columns.push(col);
7574 }
7575 self.consume(TokenType::RBrace)?;
7576 return Ok(StoreColumnSchema::Inline {
7577 columns,
7578 leading_trivia: Vec::new(),
7579 line: sch_line,
7580 column: sch_col,
7581 });
7582 }
7583
7584 if !self.check(TokenType::Colon) {
7586 let cur = self.current().clone();
7587 return Err(ParseError {
7588 message: format!(
7589 "axonstore `{store_name}` `schema:` declaration expects \
7590 `{{ … }}` (inline columns), `: \"manifest.ref\"` \
7591 (manifest reference), or `: env:VAR` (per-tenant schema \
7592 namespace). Got `{}` instead.",
7593 cur.value
7594 ),
7595 line: cur.line,
7596 column: cur.column,
7597 ..Default::default()
7598 });
7599 }
7600 self.consume(TokenType::Colon)?;
7601
7602 if self.check(TokenType::StringLit) {
7604 let lit = self.consume(TokenType::StringLit)?.clone();
7605 let value = lit.value.clone();
7606 if let Some(var) = value.strip_prefix("env:") {
7607 let var = var.trim();
7608 if var.is_empty() {
7609 return Err(ParseError {
7610 message: format!(
7611 "axonstore `{store_name}` `schema: \"env:\"` is \
7612 missing the variable name after the `env:` \
7613 prefix."
7614 ),
7615 line: lit.line,
7616 column: lit.column,
7617 ..Default::default()
7618 });
7619 }
7620 return Ok(StoreColumnSchema::EnvVar {
7621 var_name: var.to_string(),
7622 line: sch_line,
7623 column: sch_col,
7624 });
7625 }
7626 if value.trim().is_empty() {
7628 return Err(ParseError {
7629 message: format!(
7630 "axonstore `{store_name}` `schema:` manifest reference \
7631 is empty. Expected `\"qualified.name\"` — e.g. \
7632 `\"public.tenants\"`."
7633 ),
7634 line: lit.line,
7635 column: lit.column,
7636 ..Default::default()
7637 });
7638 }
7639 return Ok(StoreColumnSchema::ManifestRef {
7640 qualified_name: value,
7641 line: sch_line,
7642 column: sch_col,
7643 });
7644 }
7645
7646 let env_tok = self.current().clone();
7649 if env_tok.value == "env" {
7650 self.advance();
7651 if !self.check(TokenType::Colon) {
7652 return Err(ParseError {
7653 message: format!(
7654 "axonstore `{store_name}` `schema: env` is missing the \
7655 `:` separator. Expected `schema: env:VAR`."
7656 ),
7657 line: env_tok.line,
7658 column: env_tok.column,
7659 ..Default::default()
7660 });
7661 }
7662 self.advance(); let var_tok = self.consume_any_ident_or_kw()?.clone();
7664 if var_tok.value.trim().is_empty() {
7665 return Err(ParseError {
7666 message: format!(
7667 "axonstore `{store_name}` `schema: env:` is missing \
7668 the variable name."
7669 ),
7670 line: var_tok.line,
7671 column: var_tok.column,
7672 ..Default::default()
7673 });
7674 }
7675 return Ok(StoreColumnSchema::EnvVar {
7676 var_name: var_tok.value.clone(),
7677 line: sch_line,
7678 column: sch_col,
7679 });
7680 }
7681
7682 Err(ParseError {
7683 message: format!(
7684 "axonstore `{store_name}` `schema:` declaration expects \
7685 `{{ … }}` (inline columns), `\"manifest.ref\"` (manifest \
7686 reference), or `env:VAR` (per-tenant schema namespace). \
7687 Got `{}` instead.",
7688 env_tok.value
7689 ),
7690 line: env_tok.line,
7691 column: env_tok.column,
7692 ..Default::default()
7693 })
7694 }
7695
7696 fn parse_resource(&mut self) -> Result<ResourceDefinition, ParseError> {
7703 let tok = self.consume(TokenType::Resource)?;
7704 let name = self.consume(TokenType::Identifier)?.value;
7705 let mut node = ResourceDefinition {
7706 name,
7707 kind: String::new(),
7708 endpoint: String::new(),
7709 capacity: None,
7710 lifetime: "affine".to_string(),
7711 certainty_floor: None,
7712 shield_ref: String::new(),
7713 within: String::new(),
7714 loc: Loc {
7715 line: tok.line,
7716 column: tok.column,
7717 },
7718 leading_trivia: Vec::new(),
7719 trailing_trivia: Vec::new(),
7720 };
7721 self.consume(TokenType::LBrace)?;
7722 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7723 let field_tok = self.current().clone();
7724 let field_name = field_tok.value.clone();
7725 self.advance();
7726 if !self.check(TokenType::Colon) {
7727 if self.check(TokenType::LBrace) {
7729 self.skip_braced_block()?;
7730 }
7731 continue;
7732 }
7733 self.advance(); match field_name.as_str() {
7735 "kind" => node.kind = self.consume_any_ident_or_kw()?.value,
7736 "endpoint" => {
7749 node.endpoint = if self.check(TokenType::StringLit) {
7750 self.consume(TokenType::StringLit)?.value
7751 } else {
7752 self.parse_dotted_identifier()?
7753 };
7754 }
7755 "capacity" => {
7756 node.capacity = self.parse_optional_int();
7757 }
7758 "lifetime" => {
7759 let lt_tok = self.consume_any_ident_or_kw()?;
7760 let lt = lt_tok.value;
7761 if !matches!(lt.as_str(), "linear" | "affine" | "persistent") {
7762 return Err(ParseError {
7763 message: format!(
7764 "Invalid lifetime '{lt}' in resource '{}' — \
7765 expected linear | affine | persistent",
7766 node.name
7767 ),
7768 line: lt_tok.line,
7769 column: lt_tok.column,
7770 ..Default::default()
7771 });
7772 }
7773 node.lifetime = lt;
7774 }
7775 "certainty_floor" => {
7776 node.certainty_floor = self.parse_optional_float();
7777 }
7778 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7779 "within" => node.within = self.consume_any_ident_or_kw()?.value,
7783 unknown => {
7793 return Err(ParseError {
7794 message: format!(
7795 "Unknown field '{unknown}' in resource '{}' — expected one of: \
7796 kind, endpoint, capacity, lifetime, certainty_floor, shield, within",
7797 node.name
7798 ),
7799 line: field_tok.line,
7800 column: field_tok.column,
7801 ..Default::default()
7802 });
7803 }
7804 }
7805 }
7806 self.consume(TokenType::RBrace)?;
7807 Ok(node)
7808 }
7809
7810 fn parse_fabric(&mut self) -> Result<FabricDefinition, ParseError> {
7812 let tok = self.consume(TokenType::Fabric)?;
7813 let name = self.consume(TokenType::Identifier)?.value;
7814 let mut node = FabricDefinition {
7815 name,
7816 provider: String::new(),
7817 region: String::new(),
7818 zones: None,
7819 ephemeral: None,
7820 shield_ref: String::new(),
7821 loc: Loc {
7822 line: tok.line,
7823 column: tok.column,
7824 },
7825 leading_trivia: Vec::new(),
7826 trailing_trivia: Vec::new(),
7827 };
7828 self.consume(TokenType::LBrace)?;
7829 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7830 let field_name = self.current().value.clone();
7831 self.advance();
7832 if !self.check(TokenType::Colon) {
7833 if self.check(TokenType::LBrace) {
7834 self.skip_braced_block()?;
7835 }
7836 continue;
7837 }
7838 self.advance(); match field_name.as_str() {
7840 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
7841 "region" => node.region = self.consume(TokenType::StringLit)?.value,
7842 "zones" => node.zones = self.parse_optional_int(),
7843 "ephemeral" => {
7844 let b = self.parse_bool()?;
7845 node.ephemeral = Some(b);
7846 }
7847 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7848 _ => self.skip_value(),
7849 }
7850 }
7851 self.consume(TokenType::RBrace)?;
7852 Ok(node)
7853 }
7854
7855 fn parse_manifest(&mut self) -> Result<ManifestDefinition, ParseError> {
7857 let tok = self.consume(TokenType::Manifest)?;
7858 let name = self.consume(TokenType::Identifier)?.value;
7859 let mut node = ManifestDefinition {
7860 name,
7861 resources: Vec::new(),
7862 fabric_ref: String::new(),
7863 region: String::new(),
7864 zones: None,
7865 compliance: Vec::new(),
7866 loc: Loc {
7867 line: tok.line,
7868 column: tok.column,
7869 },
7870 leading_trivia: Vec::new(),
7871 trailing_trivia: Vec::new(),
7872 };
7873 self.consume(TokenType::LBrace)?;
7874 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7875 let field_name = self.current().value.clone();
7876 self.advance();
7877 if !self.check(TokenType::Colon) {
7878 if self.check(TokenType::LBrace) {
7879 self.skip_braced_block()?;
7880 }
7881 continue;
7882 }
7883 self.advance();
7884 match field_name.as_str() {
7885 "resources" => node.resources = self.parse_bracketed_identifiers()?,
7886 "fabric" => node.fabric_ref = self.consume_any_ident_or_kw()?.value,
7887 "region" => node.region = self.consume(TokenType::StringLit)?.value,
7888 "zones" => node.zones = self.parse_optional_int(),
7889 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
7890 _ => self.skip_value(),
7891 }
7892 }
7893 self.consume(TokenType::RBrace)?;
7894 Ok(node)
7895 }
7896
7897 fn parse_observe(&mut self) -> Result<ObserveDefinition, ParseError> {
7899 let tok = self.consume(TokenType::Observe)?;
7900 let name = self.consume(TokenType::Identifier)?.value;
7901 self.consume(TokenType::From)?;
7903 let target = self.consume(TokenType::Identifier)?.value;
7904 let mut node = ObserveDefinition {
7905 name,
7906 target,
7907 sources: Vec::new(),
7908 quorum: None,
7909 timeout: String::new(),
7910 on_partition: "fail".to_string(),
7911 certainty_floor: None,
7912 loc: Loc {
7913 line: tok.line,
7914 column: tok.column,
7915 },
7916 leading_trivia: Vec::new(),
7917 trailing_trivia: Vec::new(),
7918 };
7919 self.consume(TokenType::LBrace)?;
7920 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7921 let field_name = self.current().value.clone();
7922 self.advance();
7923 if !self.check(TokenType::Colon) {
7924 if self.check(TokenType::LBrace) {
7925 self.skip_braced_block()?;
7926 }
7927 continue;
7928 }
7929 self.advance();
7930 match field_name.as_str() {
7931 "sources" => node.sources = self.parse_bracketed_identifiers()?,
7932 "quorum" => node.quorum = self.parse_optional_int(),
7933 "timeout" => {
7934 let t = self.current().clone();
7935 match t.ttype {
7936 TokenType::Duration | TokenType::StringLit => {
7937 self.advance();
7938 node.timeout = t.value;
7939 }
7940 _ => node.timeout = self.consume_any_ident_or_kw()?.value,
7941 }
7942 }
7943 "on_partition" => {
7944 let p_tok = self.consume_any_ident_or_kw()?;
7945 let p = p_tok.value;
7946 if !matches!(p.as_str(), "fail" | "shield_quarantine") {
7947 return Err(ParseError {
7948 message: format!(
7949 "Invalid on_partition '{p}' in observe '{}' — \
7950 expected fail | shield_quarantine",
7951 node.name
7952 ),
7953 line: p_tok.line,
7954 column: p_tok.column,
7955 ..Default::default()
7956 });
7957 }
7958 node.on_partition = p;
7959 }
7960 "certainty_floor" => node.certainty_floor = self.parse_optional_float(),
7961 _ => self.skip_value(),
7962 }
7963 }
7964 self.consume(TokenType::RBrace)?;
7965 Ok(node)
7966 }
7967
7968 fn parse_reconcile(&mut self) -> Result<ReconcileDefinition, ParseError> {
7972 let tok = self.consume(TokenType::Reconcile)?;
7973 let name = self.consume(TokenType::Identifier)?.value;
7974 let mut node = ReconcileDefinition {
7975 name,
7976 observe_ref: String::new(),
7977 threshold: None,
7978 tolerance: None,
7979 on_drift: "provision".to_string(),
7980 shield_ref: String::new(),
7981 mandate_ref: String::new(),
7982 max_retries: 3,
7983 loc: Loc {
7984 line: tok.line,
7985 column: tok.column,
7986 },
7987 leading_trivia: Vec::new(),
7988 trailing_trivia: Vec::new(),
7989 };
7990 self.consume(TokenType::LBrace)?;
7991 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7992 let field_name = self.current().value.clone();
7993 self.advance();
7994 if !self.check(TokenType::Colon) {
7995 if self.check(TokenType::LBrace) {
7996 self.skip_braced_block()?;
7997 }
7998 continue;
7999 }
8000 self.advance();
8001 match field_name.as_str() {
8002 "observe" => node.observe_ref = self.consume_any_ident_or_kw()?.value,
8003 "threshold" => node.threshold = self.parse_optional_float(),
8004 "tolerance" => node.tolerance = self.parse_optional_float(),
8005 "on_drift" => {
8006 let d_tok = self.consume_any_ident_or_kw()?;
8007 let d = d_tok.value;
8008 if !matches!(d.as_str(), "provision" | "alert" | "refine") {
8009 return Err(ParseError {
8010 message: format!(
8011 "Invalid on_drift '{d}' in reconcile '{}' — \
8012 expected provision | alert | refine",
8013 node.name
8014 ),
8015 line: d_tok.line,
8016 column: d_tok.column,
8017 ..Default::default()
8018 });
8019 }
8020 node.on_drift = d;
8021 }
8022 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
8023 "mandate" => node.mandate_ref = self.consume_any_ident_or_kw()?.value,
8024 "max_retries" => {
8025 if let Some(v) = self.parse_optional_int() {
8026 node.max_retries = v;
8027 }
8028 }
8029 _ => self.skip_value(),
8030 }
8031 }
8032 self.consume(TokenType::RBrace)?;
8033 Ok(node)
8034 }
8035
8036 fn parse_lease(&mut self) -> Result<LeaseDefinition, ParseError> {
8038 let tok = self.consume(TokenType::Lease)?;
8039 let name = self.consume(TokenType::Identifier)?.value;
8040 let mut node = LeaseDefinition {
8041 name,
8042 resource_ref: String::new(),
8043 duration: String::new(),
8044 acquire: "on_start".to_string(),
8045 on_expire: "anchor_breach".to_string(),
8046 loc: Loc {
8047 line: tok.line,
8048 column: tok.column,
8049 },
8050 leading_trivia: Vec::new(),
8051 trailing_trivia: Vec::new(),
8052 };
8053 self.consume(TokenType::LBrace)?;
8054 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8055 let field_name = self.current().value.clone();
8056 self.advance();
8057 if !self.check(TokenType::Colon) {
8058 if self.check(TokenType::LBrace) {
8059 self.skip_braced_block()?;
8060 }
8061 continue;
8062 }
8063 self.advance();
8064 match field_name.as_str() {
8065 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
8066 "duration" => {
8067 let t = self.current().clone();
8068 match t.ttype {
8069 TokenType::Duration | TokenType::StringLit => {
8070 self.advance();
8071 node.duration = t.value;
8072 }
8073 _ => node.duration = self.consume_any_ident_or_kw()?.value,
8074 }
8075 }
8076 "acquire" => {
8077 let a_tok = self.consume_any_ident_or_kw()?;
8078 let a = a_tok.value;
8079 if !matches!(a.as_str(), "on_start" | "on_demand") {
8080 return Err(ParseError {
8081 message: format!(
8082 "Invalid acquire '{a}' in lease '{}' — \
8083 expected on_start | on_demand",
8084 node.name
8085 ),
8086 line: a_tok.line,
8087 column: a_tok.column,
8088 ..Default::default()
8089 });
8090 }
8091 node.acquire = a;
8092 }
8093 "on_expire" => {
8094 let e_tok = self.consume_any_ident_or_kw()?;
8095 let e = e_tok.value;
8096 if !matches!(e.as_str(), "anchor_breach" | "release" | "extend") {
8097 return Err(ParseError {
8098 message: format!(
8099 "Invalid on_expire '{e}' in lease '{}' — \
8100 expected anchor_breach | release | extend",
8101 node.name
8102 ),
8103 line: e_tok.line,
8104 column: e_tok.column,
8105 ..Default::default()
8106 });
8107 }
8108 node.on_expire = e;
8109 }
8110 _ => self.skip_value(),
8111 }
8112 }
8113 self.consume(TokenType::RBrace)?;
8114 Ok(node)
8115 }
8116
8117 fn parse_ensemble(&mut self) -> Result<EnsembleDefinition, ParseError> {
8119 let tok = self.consume(TokenType::Ensemble)?;
8120 let name = self.consume(TokenType::Identifier)?.value;
8121 let mut node = EnsembleDefinition {
8122 name,
8123 observations: Vec::new(),
8124 quorum: None,
8125 aggregation: "majority".to_string(),
8126 certainty_mode: "min".to_string(),
8127 loc: Loc {
8128 line: tok.line,
8129 column: tok.column,
8130 },
8131 leading_trivia: Vec::new(),
8132 trailing_trivia: Vec::new(),
8133 };
8134 self.consume(TokenType::LBrace)?;
8135 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8136 let field_name = self.current().value.clone();
8137 self.advance();
8138 if !self.check(TokenType::Colon) {
8139 if self.check(TokenType::LBrace) {
8140 self.skip_braced_block()?;
8141 }
8142 continue;
8143 }
8144 self.advance();
8145 match field_name.as_str() {
8146 "observations" => node.observations = self.parse_bracketed_identifiers()?,
8147 "quorum" => node.quorum = self.parse_optional_int(),
8148 "aggregation" => {
8149 let a_tok = self.consume_any_ident_or_kw()?;
8150 let a = a_tok.value;
8151 if !matches!(a.as_str(), "majority" | "weighted" | "byzantine") {
8152 return Err(ParseError {
8153 message: format!(
8154 "Invalid aggregation '{a}' in ensemble '{}' — \
8155 expected majority | weighted | byzantine",
8156 node.name
8157 ),
8158 line: a_tok.line,
8159 column: a_tok.column,
8160 ..Default::default()
8161 });
8162 }
8163 node.aggregation = a;
8164 }
8165 "certainty_mode" => {
8166 let c_tok = self.consume_any_ident_or_kw()?;
8167 let c = c_tok.value;
8168 if !matches!(c.as_str(), "min" | "weighted" | "harmonic") {
8169 return Err(ParseError {
8170 message: format!(
8171 "Invalid certainty_mode '{c}' in ensemble '{}' — \
8172 expected min | weighted | harmonic",
8173 node.name
8174 ),
8175 line: c_tok.line,
8176 column: c_tok.column,
8177 ..Default::default()
8178 });
8179 }
8180 node.certainty_mode = c;
8181 }
8182 _ => self.skip_value(),
8183 }
8184 }
8185 self.consume(TokenType::RBrace)?;
8186 Ok(node)
8187 }
8188
8189 fn parse_session_definition(&mut self) -> Result<SessionDefinition, ParseError> {
8197 let tok = self.consume(TokenType::Session)?;
8198 let name = self.consume(TokenType::Identifier)?.value;
8199 let mut node = SessionDefinition {
8200 name,
8201 roles: Vec::new(),
8202 loc: Loc {
8203 line: tok.line,
8204 column: tok.column,
8205 },
8206 leading_trivia: Vec::new(),
8207 trailing_trivia: Vec::new(),
8208 };
8209 self.consume(TokenType::LBrace)?;
8210 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8211 let role_tok = self.consume_any_ident_or_kw()?;
8212 self.consume(TokenType::Colon)?;
8213 let steps = self.parse_session_steps()?;
8214 node.roles.push(SessionRole {
8215 name: role_tok.value,
8216 steps,
8217 loc: Loc {
8218 line: role_tok.line,
8219 column: role_tok.column,
8220 },
8221 });
8222 }
8223 self.consume(TokenType::RBrace)?;
8224 Ok(node)
8225 }
8226
8227 fn parse_observable(&mut self) -> Result<ObservableDefinition, ParseError> {
8240 let tok = self.consume(TokenType::Observable)?;
8241 let name = self.consume(TokenType::Identifier)?.value;
8242 let mut node = ObservableDefinition {
8243 name,
8244 qubits: None,
8245 terms: Vec::new(),
8246 loc: Loc {
8247 line: tok.line,
8248 column: tok.column,
8249 },
8250 leading_trivia: Vec::new(),
8251 trailing_trivia: Vec::new(),
8252 };
8253 self.consume(TokenType::LBrace)?;
8254 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8255 let key_tok = self.consume_any_ident_or_kw()?;
8256 self.consume(TokenType::Colon)?;
8257 match key_tok.value.as_str() {
8258 "qubits" => node.qubits = Some(self.consume_number()? as i64),
8259 "term" => {
8260 let term_loc = Loc {
8261 line: key_tok.line,
8262 column: key_tok.column,
8263 };
8264 let mut negative = false;
8266 if self.check(TokenType::Minus) {
8267 self.advance();
8268 negative = true;
8269 } else if self.check(TokenType::Plus) {
8270 self.advance();
8271 }
8272 let mag = self.consume_number()?;
8273 let coefficient = if negative { -mag } else { mag };
8274 self.consume(TokenType::Star)?;
8276 let pauli = self.consume(TokenType::StringLit)?.value;
8277 node.terms.push(PauliTerm {
8278 coefficient,
8279 pauli,
8280 loc: term_loc,
8281 });
8282 }
8283 _ => self.skip_value(),
8284 }
8285 }
8286 self.consume(TokenType::RBrace)?;
8287 Ok(node)
8288 }
8289
8290 fn parse_witness(&mut self) -> Result<WitnessDefinition, ParseError> {
8298 let tok = self.consume(TokenType::Witness)?;
8299 let name = self.consume(TokenType::Identifier)?.value;
8300 let mut node = WitnessDefinition {
8301 name,
8302 claim: String::new(),
8303 baseline: String::new(),
8304 metric: String::new(),
8305 threshold: 0.0,
8306 data: String::new(),
8307 loc: Loc {
8308 line: tok.line,
8309 column: tok.column,
8310 },
8311 leading_trivia: Vec::new(),
8312 trailing_trivia: Vec::new(),
8313 };
8314 self.consume(TokenType::LBrace)?;
8315 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8316 let key_tok = self.consume_any_ident_or_kw()?;
8317 self.consume(TokenType::Colon)?;
8318 match key_tok.value.as_str() {
8319 "claim" => node.claim = self.consume_any_ident_or_kw()?.value,
8320 "against" => node.baseline = self.consume_any_ident_or_kw()?.value,
8323 "metric" => node.metric = self.consume_any_ident_or_kw()?.value,
8324 "threshold" => node.threshold = self.consume_number()?,
8325 "data" => node.data = self.consume_any_ident_or_kw()?.value,
8326 _ => self.skip_value(),
8327 }
8328 }
8329 self.consume(TokenType::RBrace)?;
8330 Ok(node)
8331 }
8332
8333 fn parse_socket(&mut self) -> Result<SocketDefinition, ParseError> {
8338 let tok = self.consume(TokenType::Socket)?;
8339 let name = self.consume(TokenType::Identifier)?.value;
8340 let mut node = SocketDefinition {
8341 name,
8342 loc: Loc { line: tok.line, column: tok.column },
8343 ..Default::default()
8344 };
8345 self.consume(TokenType::LBrace)?;
8346 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8347 let key = self.consume_any_ident_or_kw()?.value;
8348 self.consume(TokenType::Colon)?;
8349 match key.as_str() {
8350 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
8351 "backpressure" => {
8352 let kind = self.consume_any_ident_or_kw()?.value;
8354 if kind != "credit" {
8355 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
8356 }
8357 self.consume(TokenType::LParen)?;
8358 let n = self
8359 .consume(TokenType::Integer)?
8360 .value
8361 .parse::<i64>()
8362 .map_err(|_| self.error("backpressure credit must be an integer"))?;
8363 self.consume(TokenType::RParen)?;
8364 node.backpressure_credit = Some(n);
8365 }
8366 "reconnect" => {
8367 let mode = self.consume_any_ident_or_kw()?.value;
8368 node.reconnect = mode == "cognitive_state";
8369 }
8370 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
8371 other => return Err(self.error(&format!("unknown socket field `{other}`"))),
8372 }
8373 if self.check(TokenType::Comma) {
8375 self.consume(TokenType::Comma)?;
8376 }
8377 }
8378 self.consume(TokenType::RBrace)?;
8379 Ok(node)
8380 }
8381
8382 fn parse_upstream(&mut self) -> Result<UpstreamDefinition, ParseError> {
8390 let tok = self.consume(TokenType::Upstream)?;
8391 let name = self.consume(TokenType::Identifier)?.value;
8392 let mut node = UpstreamDefinition {
8393 name,
8394 loc: Loc { line: tok.line, column: tok.column },
8395 ..Default::default()
8396 };
8397 if self.check(TokenType::From) {
8399 self.advance();
8400 let base = self.consume(TokenType::Identifier)?.value;
8401 self.consume(TokenType::At)?;
8402 let version = self.consume_any_ident_or_kw()?.value;
8403 node.preset = Some(format!("{base}@{version}"));
8404 }
8405 self.consume(TokenType::LBrace)?;
8406 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8407 let key = self.consume_any_ident_or_kw()?.value;
8408 self.consume(TokenType::Colon)?;
8409 match key.as_str() {
8410 "transport" => node.transport = self.consume_any_ident_or_kw()?.value,
8411 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
8412 "role" => node.role = self.consume_any_ident_or_kw()?.value,
8413 "resolve" => node.resolve = self.parse_dotted_identifier()?,
8414 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
8418 "secret" => node.secret = self.parse_dotted_identifier()?,
8419 "auth" => {
8420 node.auth_kind = self.consume_any_ident_or_kw()?.value;
8423 if self.check(TokenType::LParen) {
8424 self.consume(TokenType::LParen)?;
8425 node.auth_name = Some(self.consume(TokenType::StringLit)?.value);
8426 if self.check(TokenType::Comma) {
8427 self.consume(TokenType::Comma)?;
8428 node.auth_prefix = Some(self.consume(TokenType::StringLit)?.value);
8429 }
8430 self.consume(TokenType::RParen)?;
8431 }
8432 }
8433 "map" => node.map = self.parse_upstream_map()?,
8434 "reconnect" => node.reconnect = Some(self.parse_upstream_reconnect()?),
8435 "overflow" => node.overflow = Some(self.consume_any_ident_or_kw()?.value),
8436 "backpressure" => {
8437 let kind = self.consume_any_ident_or_kw()?.value;
8439 if kind != "credit" {
8440 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
8441 }
8442 self.consume(TokenType::LParen)?;
8443 let n = self
8444 .consume(TokenType::Integer)?
8445 .value
8446 .parse::<i64>()
8447 .map_err(|_| self.error("backpressure credit must be an integer"))?;
8448 self.consume(TokenType::RParen)?;
8449 node.backpressure_credit = Some(n);
8450 }
8451 other => return Err(self.error(&format!("unknown upstream field `{other}`"))),
8452 }
8453 if self.check(TokenType::Comma) {
8455 self.consume(TokenType::Comma)?;
8456 }
8457 }
8458 self.consume(TokenType::RBrace)?;
8459 Ok(node)
8460 }
8461
8462 fn parse_cors(&mut self) -> Result<CorsDefinition, ParseError> {
8471 let tok = self.consume(TokenType::Cors)?;
8472 let name = self.consume(TokenType::Identifier)?.value;
8473 let mut node = CorsDefinition {
8474 name,
8475 loc: Loc { line: tok.line, column: tok.column },
8476 ..Default::default()
8477 };
8478 self.consume(TokenType::LBrace)?;
8479 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8480 let key = self.consume_any_ident_or_kw()?.value;
8481 self.consume(TokenType::Colon)?;
8482 match key.as_str() {
8483 "allow_origins" => node.allow_origins = self.parse_bracketed_strings()?,
8484 "allow_methods" => node.allow_methods = self.parse_bracketed_identifiers()?,
8485 "allow_headers" => node.allow_headers = self.parse_bracketed_strings()?,
8486 "allow_credentials" => {
8487 node.allow_credentials = self.consume_any_ident_or_kw()?.value == "true"
8488 }
8489 "max_age" => node.max_age = Some(self.consume(TokenType::Duration)?.value),
8490 "expose_headers" => node.expose_headers = self.parse_bracketed_strings()?,
8491 other => return Err(self.error(&format!("unknown cors field `{other}`"))),
8492 }
8493 if self.check(TokenType::Comma) {
8495 self.consume(TokenType::Comma)?;
8496 }
8497 }
8498 self.consume(TokenType::RBrace)?;
8499 Ok(node)
8500 }
8501
8502 fn parse_credential(&mut self) -> Result<CredentialDefinition, ParseError> {
8510 let tok = self.consume(TokenType::Credential)?;
8511 let name = self.consume(TokenType::Identifier)?.value;
8512 let mut node = CredentialDefinition {
8513 name,
8514 loc: Loc { line: tok.line, column: tok.column },
8515 ..Default::default()
8516 };
8517 self.consume(TokenType::LBrace)?;
8518 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8519 let key = self.consume_any_ident_or_kw()?.value;
8520 self.consume(TokenType::Colon)?;
8521 match key.as_str() {
8522 "ttl" => node.ttl = self.consume(TokenType::Duration)?.value,
8523 "grants" => {
8524 let bracket_tok = self.current().clone();
8525 let items = self.parse_bracketed_dot_identifiers()?;
8526 for slug in &items {
8527 if !is_valid_capability_slug(slug) {
8528 return Err(ParseError {
8529 message: format!(
8530 "Invalid capability slug '{slug}' in credential '{}' \
8531 `grants:`. Capability slugs must match \
8532 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
8533 lowercase identifiers starting with a letter. Examples: \
8534 `chat.invoke`, `flow.execute`.",
8535 node.name
8536 ),
8537 line: bracket_tok.line,
8538 column: bracket_tok.column,
8539 ..Default::default()
8540 });
8541 }
8542 }
8543 node.grants = items;
8544 }
8545 other => return Err(self.error(&format!("unknown credential field `{other}`"))),
8546 }
8547 if self.check(TokenType::Comma) {
8549 self.consume(TokenType::Comma)?;
8550 }
8551 }
8552 self.consume(TokenType::RBrace)?;
8553 Ok(node)
8554 }
8555
8556 fn parse_cache(&mut self) -> Result<CacheDefinition, ParseError> {
8563 let tok = self.consume(TokenType::Cache)?;
8564 let name = self.consume(TokenType::Identifier)?.value;
8565 let mut node = CacheDefinition {
8566 name,
8567 loc: Loc { line: tok.line, column: tok.column },
8568 ..Default::default()
8569 };
8570 self.consume(TokenType::LBrace)?;
8571 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8572 let key = self.consume_any_ident_or_kw()?.value;
8573 self.consume(TokenType::Colon)?;
8574 match key.as_str() {
8575 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
8576 "ttl" => node.ttl = Some(self.consume(TokenType::Duration)?.value),
8577 "key" => node.key_params = self.parse_bracketed_identifiers()?,
8578 "default" => {
8579 node.default_policy = self.consume_any_ident_or_kw()?.value == "true"
8580 }
8581 "apply_to_effects" => {
8582 node.apply_to_effects = self.parse_bracketed_identifiers()?
8583 }
8584 "invalidate_on" => node.invalidate_on = self.parse_bracketed_identifiers()?,
8585 other => return Err(self.error(&format!("unknown cache field `{other}`"))),
8586 }
8587 if self.check(TokenType::Comma) {
8588 self.consume(TokenType::Comma)?;
8589 }
8590 }
8591 self.consume(TokenType::RBrace)?;
8592 Ok(node)
8593 }
8594
8595 fn parse_document(&mut self) -> Result<crate::ast::DocumentDefinition, ParseError> {
8603 let tok = self.consume(TokenType::Document)?;
8604 let name = self.consume(TokenType::Identifier)?.value;
8605 let mut node = crate::ast::DocumentDefinition {
8606 name,
8607 loc: Loc {
8608 line: tok.line,
8609 column: tok.column,
8610 },
8611 ..Default::default()
8612 };
8613 self.consume(TokenType::LBrace)?;
8614 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8615 let field = self.current().clone();
8616 let field_name = field.value.clone();
8617 self.advance();
8618 if self.check(TokenType::Colon) {
8619 self.advance();
8620 match field_name.as_str() {
8621 "target" => node.target = self.consume_any_ident_or_kw()?.value,
8622 "template" => node.template = self.parse_dotted_identifier()?,
8623 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
8624 "effects" => node.effects = Some(self.parse_effect_row()?),
8625 other => {
8626 return Err(self.error(&format!(
8627 "unknown document field `{other}` in document `{}` — expected \
8628 `target:` / `template:` / `provenance:` / `effects:`, or a body \
8629 block (`section {{ … }}` / `slide {{ … }}` / `sheet {{ … }}`)",
8630 node.name
8631 )))
8632 }
8633 }
8634 } else if self.check(TokenType::LBrace) {
8635 node.blocks
8636 .push(self.parse_doc_block_body(field_name, field.line, field.column)?);
8637 } else {
8638 return Err(self.error(&format!(
8639 "unexpected `{field_name}` in document `{}` body — expected a `field:` or a \
8640 body block `{field_name} {{ … }}`",
8641 node.name
8642 )));
8643 }
8644 if self.check(TokenType::Comma) {
8645 self.advance();
8646 }
8647 }
8648 self.consume(TokenType::RBrace)?;
8649 Ok(node)
8650 }
8651
8652 fn parse_doc_block_body(
8658 &mut self,
8659 kind: String,
8660 line: u32,
8661 column: u32,
8662 ) -> Result<crate::ast::DocBlock, ParseError> {
8663 let mut block = crate::ast::DocBlock {
8664 kind,
8665 loc: Loc { line, column },
8666 ..Default::default()
8667 };
8668 self.consume(TokenType::LBrace)?;
8669 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8670 let name_tok = self.current().clone();
8671 let name = self.consume_any_ident_or_kw()?.value;
8672 if self.check(TokenType::Colon) {
8673 self.advance();
8674 let value = self.parse_doc_scalar()?;
8675 block.fields.push((name, value));
8676 } else if self.check(TokenType::LBrace) {
8677 let child = self.parse_doc_block_body(name, name_tok.line, name_tok.column)?;
8678 block.children.push(child);
8679 } else {
8680 return Err(self.error(&format!(
8681 "in document block `{}`: `{name}` must be a `field:` value or open a nested \
8682 block `{name} {{ … }}`",
8683 block.kind
8684 )));
8685 }
8686 if self.check(TokenType::Comma) {
8687 self.advance();
8688 }
8689 }
8690 self.consume(TokenType::RBrace)?;
8691 Ok(block)
8692 }
8693
8694 fn parse_doc_scalar(&mut self) -> Result<crate::ast::DocScalar, ParseError> {
8699 let tok = self.current().clone();
8700 match tok.ttype {
8701 TokenType::StringLit => {
8702 self.advance();
8703 Ok(crate::ast::DocScalar::Text(tok.value))
8704 }
8705 TokenType::Integer => {
8706 self.advance();
8707 Ok(crate::ast::DocScalar::Int(tok.value.parse::<i64>().unwrap_or(0)))
8708 }
8709 TokenType::Bool => {
8710 self.advance();
8711 Ok(crate::ast::DocScalar::Bool(tok.value == "true"))
8712 }
8713 TokenType::LBracket => {
8714 let items = self.parse_bracketed_strings()?;
8715 Ok(crate::ast::DocScalar::List(items))
8716 }
8717 _ => {
8718 let name = self.consume_any_ident_or_kw()?.value;
8719 Ok(crate::ast::DocScalar::Ref(name))
8720 }
8721 }
8722 }
8723
8724 fn parse_notify(&mut self) -> Result<crate::ast::NotifyDefinition, ParseError> {
8749 let tok = self.consume(TokenType::Notify)?;
8750 let name = self.consume(TokenType::Identifier)?.value;
8751 let mut node = crate::ast::NotifyDefinition {
8752 name,
8753 loc: Loc {
8754 line: tok.line,
8755 column: tok.column,
8756 },
8757 ..Default::default()
8758 };
8759 self.consume(TokenType::LBrace)?;
8760 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8761 let field = self.current().clone();
8762 let field_name = field.value.clone();
8763 self.advance();
8764 if self.check(TokenType::Colon) {
8765 self.advance();
8766 match field_name.as_str() {
8767 "channel" => node.channel = self.consume_any_ident_or_kw()?.value,
8768 "to" => {
8769 if self.current().value == "secret" && self.peek_is_lparen() {
8773 self.advance(); self.consume(TokenType::LParen)?;
8775 node.to_secret = self.parse_dotted_identifier()?;
8776 self.consume(TokenType::RParen)?;
8777 node.to_is_secret = true;
8778 } else if self.check(TokenType::StringLit) {
8779 node.to_secret = self.consume(TokenType::StringLit)?.value.clone();
8780 node.to_is_secret = false;
8781 } else {
8782 node.to_secret = self.consume_any_ident_or_kw()?.value.clone();
8783 node.to_is_secret = false;
8784 }
8785 }
8786 "template" => {
8787 node.template = self.consume(TokenType::StringLit)?.value.clone()
8788 }
8789 "window" => {
8790 if self.check(TokenType::Integer) {
8793 let n = self.consume(TokenType::Integer)?.value.clone();
8794 let unit = self.consume_any_ident_or_kw()?.value.clone();
8795 node.window = format!("{n}{unit}");
8796 } else {
8797 node.window = self.consume_any_ident_or_kw()?.value.clone();
8798 }
8799 }
8800 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
8801 "effects" => node.effects = Some(self.parse_effect_row()?),
8802 other => {
8803 return Err(self.error(&format!(
8804 "unknown notify field `{other}` in notify `{}` — expected \
8805 `channel:` / `to:` / `template:` / `window:` / `provenance:` / \
8806 `effects:`",
8807 node.name
8808 )))
8809 }
8810 }
8811 }
8812 }
8813 self.consume(TokenType::RBrace)?;
8814 Ok(node)
8815 }
8816
8817 fn peek_is_identifier(&self) -> bool {
8821 self.tokens
8822 .get(self.pos + 1)
8823 .map(|t| t.ttype == TokenType::Identifier)
8824 .unwrap_or(false)
8825 }
8826
8827 fn peek_is_lparen(&self) -> bool {
8828 self.tokens
8829 .get(self.pos + 1)
8830 .map(|t| t.ttype == TokenType::LParen)
8831 .unwrap_or(false)
8832 }
8833
8834 fn parse_deliver(&mut self) -> Result<crate::ast::DeliverDefinition, ParseError> {
8836 let tok = self.consume(TokenType::Deliver)?;
8837 let name = self.consume(TokenType::Identifier)?.value;
8838 let mut node = crate::ast::DeliverDefinition {
8839 name,
8840 loc: Loc {
8841 line: tok.line,
8842 column: tok.column,
8843 },
8844 ..Default::default()
8845 };
8846 self.consume(TokenType::LBrace)?;
8847 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8848 let field = self.current().clone();
8849 let field_name = field.value.clone();
8850 self.advance();
8851 if self.check(TokenType::Colon) {
8852 self.advance();
8853 match field_name.as_str() {
8854 "target" => node.target = self.consume_any_ident_or_kw()?.value,
8855 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
8856 "secret" => node.secret = self.consume_any_ident_or_kw()?.value,
8857 "effects" => node.effects = Some(self.parse_effect_row()?),
8858 other => {
8859 return Err(self.error(&format!(
8860 "unknown deliver field `{other}` in deliver `{}` — expected \
8861 `target:` / `provenance:` / `secret:` / `effects:`, or an operation \
8862 block (`upsert_contact {{ … }}` / `create_deal {{ … }}` / \
8863 `add_note {{ … }}`)",
8864 node.name
8865 )))
8866 }
8867 }
8868 } else if self.check(TokenType::LBrace) {
8869 node.ops
8870 .push(self.parse_deliver_op(field_name, field.line, field.column)?);
8871 } else {
8872 return Err(self.error(&format!(
8873 "unexpected `{field_name}` in deliver `{}` body — expected a `field:` or an \
8874 operation block `{field_name} {{ … }}`",
8875 node.name
8876 )));
8877 }
8878 if self.check(TokenType::Comma) {
8879 self.advance();
8880 }
8881 }
8882 self.consume(TokenType::RBrace)?;
8883 Ok(node)
8884 }
8885
8886 fn parse_deliver_op(
8890 &mut self,
8891 kind: String,
8892 line: u32,
8893 column: u32,
8894 ) -> Result<crate::ast::DeliverOp, ParseError> {
8895 let mut op = crate::ast::DeliverOp {
8896 kind,
8897 loc: Loc { line, column },
8898 ..Default::default()
8899 };
8900 self.consume(TokenType::LBrace)?;
8901 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8902 let name = self.consume_any_ident_or_kw()?.value;
8903 self.consume(TokenType::Colon).map_err(|_| {
8904 self.error(&format!(
8905 "in deliver operation `{}`: `{name}` must be a `field: value` pair — an \
8906 operation binds scalar fields, it takes no nested blocks",
8907 op.kind
8908 ))
8909 })?;
8910 let value = self.parse_doc_scalar()?;
8911 op.fields.push((name, value));
8912 if self.check(TokenType::Comma) {
8913 self.advance();
8914 }
8915 }
8916 self.consume(TokenType::RBrace)?;
8917 Ok(op)
8918 }
8919
8920 fn parse_savant(&mut self) -> Result<SavantDefinition, ParseError> {
8926 let tok = self.consume(TokenType::Savant)?;
8927 let name = self.consume(TokenType::Identifier)?.value;
8928 let mut node = SavantDefinition {
8929 name,
8930 loc: Loc {
8931 line: tok.line,
8932 column: tok.column,
8933 },
8934 ..Default::default()
8935 };
8936 self.consume(TokenType::LBrace)?;
8937 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8938 let field = self.current().clone();
8939 let field_name = field.value.clone();
8940 self.advance();
8941 if self.check(TokenType::Colon) {
8942 self.advance();
8943 match field_name.as_str() {
8944 "domain" => node.domain = self.consume(TokenType::StringLit)?.value,
8945 other => {
8946 return Err(self.error(&format!(
8947 "unknown savant field `{other}` in savant `{}` — expected \
8948 `domain:` or a `cognition` / `memory` / `budget` / `mandate` block",
8949 node.name
8950 )))
8951 }
8952 }
8953 } else if field_name == "cognition" {
8954 node.cognition = Some(self.parse_savant_cognition(field.line, field.column)?);
8955 } else if field_name == "memory" {
8956 node.memory = Some(self.parse_savant_memory(field.line, field.column)?);
8957 } else if field_name == "budget" {
8958 node.budget = Some(self.parse_savant_budget(field.line, field.column)?);
8959 } else if field_name == "mandate" {
8960 node.mandates
8961 .push(self.parse_savant_mandate(field.line, field.column)?);
8962 } else {
8963 return Err(self.error(&format!(
8964 "unexpected `{field_name}` in savant `{}` body — expected `domain:` or a \
8965 `cognition` / `memory` / `budget` / `mandate` block",
8966 node.name
8967 )));
8968 }
8969 if self.check(TokenType::Comma) {
8970 self.advance();
8971 }
8972 }
8973 self.consume(TokenType::RBrace)?;
8974 Ok(node)
8975 }
8976
8977 fn parse_savant_cognition(
8980 &mut self,
8981 line: u32,
8982 column: u32,
8983 ) -> Result<SavantCognition, ParseError> {
8984 self.consume(TokenType::LBrace)?;
8985 let mut node = SavantCognition {
8986 loc: Loc { line, column },
8987 ..Default::default()
8988 };
8989 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8990 let key = self.consume_any_ident_or_kw()?.value;
8991 self.consume(TokenType::Colon)?;
8992 match key.as_str() {
8993 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
8994 "entropic_threshold" => node.entropic_threshold = self.parse_optional_float(),
8995 "divergence" => node.divergence = self.consume_any_ident_or_kw()?.value,
8996 other => {
8997 return Err(self.error(&format!(
8998 "unknown savant `cognition` field `{other}` — expected \
8999 `depth` / `entropic_threshold` / `divergence`"
9000 )))
9001 }
9002 }
9003 if self.check(TokenType::Comma) {
9004 self.advance();
9005 }
9006 }
9007 self.consume(TokenType::RBrace)?;
9008 Ok(node)
9009 }
9010
9011 fn parse_savant_memory(
9014 &mut self,
9015 line: u32,
9016 column: u32,
9017 ) -> Result<SavantMemory, ParseError> {
9018 self.consume(TokenType::LBrace)?;
9019 let mut node = SavantMemory {
9020 loc: Loc { line, column },
9021 ..Default::default()
9022 };
9023 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9024 let key = self.consume_any_ident_or_kw()?.value;
9025 self.consume(TokenType::Colon)?;
9026 match key.as_str() {
9027 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
9028 "corpus_graph" => {
9029 node.corpus_graph = self.consume_any_ident_or_kw()?.value == "true"
9030 }
9031 "isolation_level" => node.isolation_level = self.consume_any_ident_or_kw()?.value,
9032 other => {
9033 return Err(self.error(&format!(
9034 "unknown savant `memory` field `{other}` — expected \
9035 `backend` / `corpus_graph` / `isolation_level`"
9036 )))
9037 }
9038 }
9039 if self.check(TokenType::Comma) {
9040 self.advance();
9041 }
9042 }
9043 self.consume(TokenType::RBrace)?;
9044 Ok(node)
9045 }
9046
9047 fn parse_savant_budget(
9050 &mut self,
9051 line: u32,
9052 column: u32,
9053 ) -> Result<SavantBudget, ParseError> {
9054 self.consume(TokenType::LBrace)?;
9055 let mut node = SavantBudget {
9056 loc: Loc { line, column },
9057 ..Default::default()
9058 };
9059 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9060 let key = self.consume_any_ident_or_kw()?.value;
9061 self.consume(TokenType::Colon)?;
9062 match key.as_str() {
9063 "max_iterations" => node.max_iterations = self.parse_optional_int(),
9064 "max_tool_synth" => node.max_tool_synth = self.parse_optional_int(),
9065 other => {
9066 return Err(self.error(&format!(
9067 "unknown savant `budget` field `{other}` — expected \
9068 `max_iterations` / `max_tool_synth`"
9069 )))
9070 }
9071 }
9072 if self.check(TokenType::Comma) {
9073 self.advance();
9074 }
9075 }
9076 self.consume(TokenType::RBrace)?;
9077 Ok(node)
9078 }
9079
9080 fn parse_savant_mandate(
9083 &mut self,
9084 line: u32,
9085 column: u32,
9086 ) -> Result<SavantMandate, ParseError> {
9087 let name = self.consume(TokenType::Identifier)?.value;
9088 let mut node = SavantMandate {
9089 name,
9090 loc: Loc { line, column },
9091 ..Default::default()
9092 };
9093 self.consume(TokenType::LBrace)?;
9094 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9095 let key = self.consume_any_ident_or_kw()?.value;
9096 self.consume(TokenType::Colon)?;
9097 match key.as_str() {
9098 "objective" => node.objective = self.consume(TokenType::StringLit)?.value,
9099 "output" => node.output_type = self.consume_any_ident_or_kw()?.value,
9100 other => {
9101 return Err(self.error(&format!(
9102 "unknown savant `mandate` field `{other}` — expected `objective` / `output`"
9103 )))
9104 }
9105 }
9106 if self.check(TokenType::Comma) {
9107 self.advance();
9108 }
9109 }
9110 self.consume(TokenType::RBrace)?;
9111 Ok(node)
9112 }
9113
9114 fn parse_synth(&mut self) -> Result<SynthDefinition, ParseError> {
9119 let tok = self.consume(TokenType::Synth)?;
9120 let name = self.consume(TokenType::Identifier)?.value;
9121 let mut node = SynthDefinition {
9122 name,
9123 loc: Loc {
9124 line: tok.line,
9125 column: tok.column,
9126 },
9127 ..Default::default()
9128 };
9129 self.consume(TokenType::LBrace)?;
9130 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9131 let key = self.consume_any_ident_or_kw()?.value;
9132 self.consume(TokenType::Colon)?;
9133 match key.as_str() {
9134 "target" => node.target = self.consume(TokenType::StringLit)?.value,
9135 "risk" => node.risk = self.consume_any_ident_or_kw()?.value,
9136 "language" => node.language = self.consume_any_ident_or_kw()?.value,
9137 "sandbox" => node.sandbox = self.consume_any_ident_or_kw()?.value,
9138 "review" => node.review = self.consume_any_ident_or_kw()?.value,
9139 "max_lines" => node.max_lines = self.parse_optional_int(),
9140 other => {
9141 return Err(self.error(&format!(
9142 "unknown synth field `{other}` in synth `{}` — expected `target` / `risk` \
9143 / `language` / `sandbox` / `review` / `max_lines`",
9144 node.name
9145 )))
9146 }
9147 }
9148 if self.check(TokenType::Comma) {
9149 self.consume(TokenType::Comma)?;
9150 }
9151 }
9152 self.consume(TokenType::RBrace)?;
9153 Ok(node)
9154 }
9155
9156 fn parse_voice(&mut self) -> Result<VoiceDefinition, ParseError> {
9161 let tok = self.consume(TokenType::Voice)?;
9162 let name = self.consume(TokenType::Identifier)?.value;
9163 let mut node = VoiceDefinition {
9164 name,
9165 loc: Loc { line: tok.line, column: tok.column },
9166 ..Default::default()
9167 };
9168 self.consume(TokenType::LBrace)?;
9169 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9170 let key = self.consume_any_ident_or_kw()?.value;
9171 self.consume(TokenType::Colon)?;
9172 match key.as_str() {
9173 "stt" => node.stt = Some(self.parse_upstream_ref()?),
9175 "tts" => node.tts = Some(self.parse_upstream_ref()?),
9176 "realtime" => node.realtime = Some(self.parse_upstream_ref()?),
9177 "carrier" => node.carrier = self.consume_any_ident_or_kw()?.value,
9178 "interruptible" => {
9179 let v = self.consume_any_ident_or_kw()?.value;
9180 node.interruptible = v == "true";
9181 }
9182 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
9183 "persona" => node.persona = Some(self.consume(TokenType::Identifier)?.value),
9184 "context" => node.context = Some(self.consume(TokenType::Identifier)?.value),
9185 other => return Err(self.error(&format!("unknown voice field `{other}`"))),
9186 }
9187 if self.check(TokenType::Comma) {
9188 self.consume(TokenType::Comma)?;
9189 }
9190 }
9191 self.consume(TokenType::RBrace)?;
9192 Ok(node)
9193 }
9194
9195 fn parse_upstream_ref(&mut self) -> Result<String, ParseError> {
9198 let base = self.consume(TokenType::Identifier)?.value;
9199 if self.check(TokenType::At) {
9200 self.advance();
9201 let version = self.consume_any_ident_or_kw()?.value;
9202 Ok(format!("{base}@{version}"))
9203 } else {
9204 Ok(base)
9205 }
9206 }
9207
9208 fn parse_upstream_map(&mut self) -> Result<Vec<UpstreamMapRule>, ParseError> {
9214 self.consume(TokenType::LBracket)?;
9215 let mut rules = Vec::new();
9216 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
9217 let dir_tok = self.current().clone();
9218 let direction = match dir_tok.ttype {
9219 TokenType::Send => "send",
9220 TokenType::Receive => "receive",
9221 _ => {
9222 return Err(self.error(&format!(
9223 "upstream map rule must start with `send` or `receive`, got `{}`",
9224 dir_tok.value
9225 )))
9226 }
9227 };
9228 self.advance();
9229 let message = self.consume(TokenType::Identifier)?.value;
9230 self.consume(TokenType::As)?;
9231 let framing = self.consume_any_ident_or_kw()?.value;
9232 let mut rule = UpstreamMapRule {
9233 direction: direction.to_string(),
9234 message,
9235 framing,
9236 loc: Loc { line: dir_tok.line, column: dir_tok.column },
9237 ..Default::default()
9238 };
9239 if self.current().value == "tag" {
9241 self.advance();
9242 rule.tag = Some(self.consume(TokenType::StringLit)?.value);
9243 } else if self.current().value == "when" {
9244 self.advance();
9249 rule.when_field = Some(self.consume(TokenType::StringLit)?.value);
9250 if self.check(TokenType::Assign) {
9251 self.advance();
9252 rule.when_value = Some(self.consume(TokenType::StringLit)?.value);
9253 }
9254 }
9255 rules.push(rule);
9256 if self.check(TokenType::Comma) {
9257 self.advance();
9258 }
9259 }
9260 self.consume(TokenType::RBracket)?;
9261 Ok(rules)
9262 }
9263
9264 fn parse_upstream_reconnect(&mut self) -> Result<UpstreamReconnect, ParseError> {
9268 self.consume(TokenType::LBrace)?;
9269 let mut backoff_ms: Option<i64> = None;
9270 let mut max_attempts: Option<i64> = None;
9271 let mut on_exhausted: Option<String> = None;
9272 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9273 let key = self.consume_any_ident_or_kw()?.value;
9274 self.consume(TokenType::Colon)?;
9275 match key.as_str() {
9276 "backoff_ms" => {
9277 backoff_ms = Some(
9278 self.consume(TokenType::Integer)?
9279 .value
9280 .parse::<i64>()
9281 .map_err(|_| self.error("backoff_ms must be an integer"))?,
9282 )
9283 }
9284 "max_attempts" => {
9285 max_attempts = Some(
9286 self.consume(TokenType::Integer)?
9287 .value
9288 .parse::<i64>()
9289 .map_err(|_| self.error("max_attempts must be an integer"))?,
9290 )
9291 }
9292 "on_exhausted" => on_exhausted = Some(self.consume_any_ident_or_kw()?.value),
9293 other => return Err(self.error(&format!("unknown reconnect field `{other}`"))),
9294 }
9295 if self.check(TokenType::Comma) {
9296 self.consume(TokenType::Comma)?;
9297 }
9298 }
9299 self.consume(TokenType::RBrace)?;
9300 match (backoff_ms, max_attempts, on_exhausted) {
9301 (Some(b), Some(m), Some(o)) => Ok(UpstreamReconnect { backoff_ms: b, max_attempts: m, on_exhausted: o }),
9302 _ => Err(self.error(
9303 "reconnect requires all of `backoff_ms:`, `max_attempts:`, `on_exhausted:` — a reconnection policy with a hole is not a policy",
9304 )),
9305 }
9306 }
9307
9308 fn parse_session_steps(&mut self) -> Result<Vec<SessionStep>, ParseError> {
9310 self.consume(TokenType::LBracket)?;
9311 let mut steps = Vec::new();
9312 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
9313 steps.push(self.parse_session_step()?);
9314 if self.check(TokenType::Comma) {
9315 self.advance();
9316 }
9317 }
9318 self.consume(TokenType::RBracket)?;
9319 Ok(steps)
9320 }
9321
9322 fn parse_session_step_block(&mut self) -> Result<Vec<SessionStep>, ParseError> {
9326 self.consume(TokenType::LBrace)?;
9327 let mut steps = Vec::new();
9328 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9329 steps.push(self.parse_session_step()?);
9330 if self.check(TokenType::Comma) {
9331 self.advance();
9332 }
9333 }
9334 self.consume(TokenType::RBrace)?;
9335 Ok(steps)
9336 }
9337
9338 fn parse_session_step(&mut self) -> Result<SessionStep, ParseError> {
9339 let tok = self.current().clone();
9340 let loc = Loc { line: tok.line, column: tok.column };
9341 match tok.ttype {
9342 TokenType::Send => {
9343 self.advance();
9344 let msg = self.consume_any_ident_or_kw()?;
9345 Ok(SessionStep { op: "send".into(), message_type: msg.value, loc, ..Default::default() })
9346 }
9347 TokenType::Receive => {
9348 self.advance();
9349 let msg = self.consume_any_ident_or_kw()?;
9350 Ok(SessionStep { op: "receive".into(), message_type: msg.value, loc, ..Default::default() })
9351 }
9352 TokenType::Loop => {
9353 self.advance();
9354 Ok(SessionStep { op: "loop".into(), loc, ..Default::default() })
9355 }
9356 TokenType::End => {
9357 self.advance();
9358 Ok(SessionStep { op: "end".into(), loc, ..Default::default() })
9359 }
9360 TokenType::Identifier if tok.value == "select" || tok.value == "branch" => {
9363 self.parse_session_choice(&tok.value, loc)
9364 }
9365 TokenType::Identifier if tok.value == "interrupt" => {
9368 self.parse_session_interrupt(loc)
9369 }
9370 TokenType::Identifier if tok.value == "resume" => {
9374 self.advance();
9375 Ok(SessionStep { op: "resume".into(), loc, ..Default::default() })
9376 }
9377 _ => Err(ParseError {
9378 message: format!(
9379 "Invalid session step '{}' — expected send | receive | loop | end | select | branch | interrupt | resume",
9380 tok.value
9381 ),
9382 line: tok.line,
9383 column: tok.column,
9384 ..Default::default()
9385 }),
9386 }
9387 }
9388
9389 fn consume_contextual(&mut self, kw: &str) -> Result<(), ParseError> {
9394 let t = self.current().clone();
9395 if t.value != kw {
9396 return Err(ParseError {
9397 message: format!("expected `{kw}` in interrupt step, got `{}`", t.value),
9398 line: t.line,
9399 column: t.column,
9400 ..Default::default()
9401 });
9402 }
9403 self.advance();
9404 Ok(())
9405 }
9406
9407 fn parse_session_interrupt(&mut self, loc: Loc) -> Result<SessionStep, ParseError> {
9415 self.advance(); let body = self.parse_session_step_block()?;
9419 self.consume_contextual("on")?;
9421 let signal = self.consume_any_ident_or_kw()?;
9422 self.consume_contextual("as")?;
9424 let binder = self.consume_any_ident_or_kw()?;
9425 self.consume_contextual("resumable")?;
9427 let handler = self.parse_session_step_block()?;
9428 Ok(SessionStep {
9429 op: "interrupt".into(),
9430 message_type: signal.value,
9431 branches: vec![
9432 SessionBranch { label: "body".into(), steps: body, loc: loc.clone() },
9433 SessionBranch { label: "handler".into(), steps: handler, loc: loc.clone() },
9434 ],
9435 binder: binder.value,
9436 resumable: true,
9437 loc,
9438 })
9439 }
9440
9441 fn parse_session_choice(&mut self, op: &str, loc: Loc) -> Result<SessionStep, ParseError> {
9444 self.advance(); self.consume(TokenType::LBrace)?;
9446 let mut branches = Vec::new();
9447 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9448 let label_tok = self.consume_any_ident_or_kw()?;
9449 self.consume(TokenType::Colon)?;
9450 let steps = self.parse_session_steps()?;
9451 branches.push(SessionBranch {
9452 label: label_tok.value,
9453 steps,
9454 loc: Loc { line: label_tok.line, column: label_tok.column },
9455 });
9456 if self.check(TokenType::Comma) {
9457 self.advance();
9458 }
9459 }
9460 self.consume(TokenType::RBrace)?;
9461 Ok(SessionStep { op: op.to_string(), branches, loc, ..Default::default() })
9462 }
9463
9464 fn parse_topology(&mut self) -> Result<TopologyDefinition, ParseError> {
9466 let tok = self.consume(TokenType::Topology)?;
9467 let name = self.consume(TokenType::Identifier)?.value;
9468 let mut node = TopologyDefinition {
9469 name,
9470 nodes: Vec::new(),
9471 edges: Vec::new(),
9472 loc: Loc {
9473 line: tok.line,
9474 column: tok.column,
9475 },
9476 leading_trivia: Vec::new(),
9477 trailing_trivia: Vec::new(),
9478 };
9479 self.consume(TokenType::LBrace)?;
9480 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9481 let field_name = self.current().value.clone();
9482 self.advance();
9483 if !self.check(TokenType::Colon) {
9484 if self.check(TokenType::LBrace) {
9485 self.skip_braced_block()?;
9486 }
9487 continue;
9488 }
9489 self.advance();
9490 match field_name.as_str() {
9491 "nodes" => node.nodes = self.parse_bracketed_identifiers()?,
9492 "edges" => node.edges = self.parse_topology_edges()?,
9493 _ => self.skip_value(),
9494 }
9495 }
9496 self.consume(TokenType::RBrace)?;
9497 Ok(node)
9498 }
9499
9500 fn parse_topology_edges(&mut self) -> Result<Vec<TopologyEdge>, ParseError> {
9501 self.consume(TokenType::LBracket)?;
9502 let mut edges = Vec::new();
9503 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
9504 edges.push(self.parse_topology_edge()?);
9505 if self.check(TokenType::Comma) {
9506 self.advance();
9507 }
9508 }
9509 self.consume(TokenType::RBracket)?;
9510 Ok(edges)
9511 }
9512
9513 fn parse_topology_edge(&mut self) -> Result<TopologyEdge, ParseError> {
9514 let src_tok = self.consume_any_ident_or_kw()?;
9515 self.consume(TokenType::Arrow)?;
9516 let tgt_tok = self.consume_any_ident_or_kw()?;
9517 self.consume(TokenType::Colon)?;
9518 let sess_tok = self.consume_any_ident_or_kw()?;
9519 Ok(TopologyEdge {
9520 source: src_tok.value,
9521 target: tgt_tok.value,
9522 session_ref: sess_tok.value,
9523 loc: Loc {
9524 line: src_tok.line,
9525 column: src_tok.column,
9526 },
9527 })
9528 }
9529
9530 fn parse_immune(&mut self) -> Result<ImmuneDefinition, ParseError> {
9534 let tok = self.consume(TokenType::Immune)?;
9535 let name = self.consume(TokenType::Identifier)?.value;
9536 let mut node = ImmuneDefinition {
9537 name,
9538 watch: Vec::new(),
9539 sensitivity: None,
9540 baseline: "learned".to_string(),
9541 window: 100,
9542 scope: String::new(),
9543 tau: String::new(),
9544 decay: "exponential".to_string(),
9545 loc: Loc {
9546 line: tok.line,
9547 column: tok.column,
9548 },
9549 leading_trivia: Vec::new(),
9550 trailing_trivia: Vec::new(),
9551 };
9552 self.consume(TokenType::LBrace)?;
9553 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9554 let field_name = self.current().value.clone();
9555 self.advance();
9556 if !self.check(TokenType::Colon) {
9557 if self.check(TokenType::LBrace) {
9558 self.skip_braced_block()?;
9559 }
9560 continue;
9561 }
9562 self.advance();
9563 match field_name.as_str() {
9564 "watch" => node.watch = self.parse_bracketed_identifiers()?,
9565 "sensitivity" => node.sensitivity = self.parse_optional_float(),
9566 "baseline" => node.baseline = self.consume_any_ident_or_kw()?.value,
9567 "window" => {
9568 if let Some(v) = self.parse_optional_int() {
9569 node.window = v;
9570 }
9571 }
9572 "scope" => {
9573 let s_tok = self.consume_any_ident_or_kw()?;
9574 let s = s_tok.value;
9575 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
9576 return Err(ParseError {
9577 message: format!(
9578 "Invalid scope '{s}' in immune '{}' — \
9579 expected tenant | flow | global",
9580 node.name
9581 ),
9582 line: s_tok.line,
9583 column: s_tok.column,
9584 ..Default::default()
9585 });
9586 }
9587 node.scope = s;
9588 }
9589 "tau" => {
9590 let t = self.current().clone();
9591 match t.ttype {
9592 TokenType::Duration | TokenType::StringLit => {
9593 self.advance();
9594 node.tau = t.value;
9595 }
9596 _ => node.tau = self.consume_any_ident_or_kw()?.value,
9597 }
9598 }
9599 "decay" => {
9600 let d_tok = self.consume_any_ident_or_kw()?;
9601 let d = d_tok.value;
9602 if !matches!(d.as_str(), "exponential" | "linear" | "none") {
9603 return Err(ParseError {
9604 message: format!(
9605 "Invalid decay '{d}' in immune '{}' — \
9606 expected exponential | linear | none",
9607 node.name
9608 ),
9609 line: d_tok.line,
9610 column: d_tok.column,
9611 ..Default::default()
9612 });
9613 }
9614 node.decay = d;
9615 }
9616 _ => self.skip_value(),
9617 }
9618 }
9619 self.consume(TokenType::RBrace)?;
9620 Ok(node)
9621 }
9622
9623 fn parse_reflex(&mut self) -> Result<ReflexDefinition, ParseError> {
9625 let tok = self.consume(TokenType::Reflex)?;
9626 let name = self.consume(TokenType::Identifier)?.value;
9627 let mut node = ReflexDefinition {
9628 name,
9629 trigger: String::new(),
9630 on_level: "doubt".to_string(),
9631 action: String::new(),
9632 scope: String::new(),
9633 sla: String::new(),
9634 loc: Loc {
9635 line: tok.line,
9636 column: tok.column,
9637 },
9638 leading_trivia: Vec::new(),
9639 trailing_trivia: Vec::new(),
9640 };
9641 self.consume(TokenType::LBrace)?;
9642 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9643 let field_name = self.current().value.clone();
9644 self.advance();
9645 if !self.check(TokenType::Colon) {
9646 if self.check(TokenType::LBrace) {
9647 self.skip_braced_block()?;
9648 }
9649 continue;
9650 }
9651 self.advance();
9652 match field_name.as_str() {
9653 "trigger" => node.trigger = self.consume_any_ident_or_kw()?.value,
9654 "on_level" => {
9655 let l_tok = self.consume_any_ident_or_kw()?;
9656 let l = l_tok.value;
9657 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
9658 return Err(ParseError {
9659 message: format!(
9660 "Invalid on_level '{l}' in reflex '{}' — \
9661 expected know | believe | speculate | doubt",
9662 node.name
9663 ),
9664 line: l_tok.line,
9665 column: l_tok.column,
9666 ..Default::default()
9667 });
9668 }
9669 node.on_level = l;
9670 }
9671 "action" => {
9672 let a_tok = self.consume_any_ident_or_kw()?;
9673 let a = a_tok.value;
9674 if !matches!(
9675 a.as_str(),
9676 "drop"
9677 | "revoke"
9678 | "emit"
9679 | "redact"
9680 | "quarantine"
9681 | "terminate"
9682 | "alert"
9683 ) {
9684 return Err(ParseError {
9685 message: format!(
9686 "Invalid action '{a}' in reflex '{}' — \
9687 expected drop | revoke | emit | redact | \
9688 quarantine | terminate | alert",
9689 node.name
9690 ),
9691 line: a_tok.line,
9692 column: a_tok.column,
9693 ..Default::default()
9694 });
9695 }
9696 node.action = a;
9697 }
9698 "scope" => {
9699 let s_tok = self.consume_any_ident_or_kw()?;
9700 let s = s_tok.value;
9701 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
9702 return Err(ParseError {
9703 message: format!(
9704 "Invalid scope '{s}' in reflex '{}' — \
9705 expected tenant | flow | global",
9706 node.name
9707 ),
9708 line: s_tok.line,
9709 column: s_tok.column,
9710 ..Default::default()
9711 });
9712 }
9713 node.scope = s;
9714 }
9715 "sla" => {
9716 let t = self.current().clone();
9717 match t.ttype {
9718 TokenType::Duration | TokenType::StringLit => {
9719 self.advance();
9720 node.sla = t.value;
9721 }
9722 _ => node.sla = self.consume_any_ident_or_kw()?.value,
9723 }
9724 }
9725 _ => self.skip_value(),
9726 }
9727 }
9728 self.consume(TokenType::RBrace)?;
9729 Ok(node)
9730 }
9731
9732 fn parse_heal(&mut self) -> Result<HealDefinition, ParseError> {
9734 let tok = self.consume(TokenType::Heal)?;
9735 let name = self.consume(TokenType::Identifier)?.value;
9736 let mut node = HealDefinition {
9737 name,
9738 source: String::new(),
9739 on_level: "doubt".to_string(),
9740 mode: "human_in_loop".to_string(),
9741 scope: String::new(),
9742 review_sla: String::new(),
9743 shield_ref: String::new(),
9744 max_patches: 3,
9745 loc: Loc {
9746 line: tok.line,
9747 column: tok.column,
9748 },
9749 leading_trivia: Vec::new(),
9750 trailing_trivia: Vec::new(),
9751 };
9752 self.consume(TokenType::LBrace)?;
9753 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9754 let field_name = self.current().value.clone();
9755 self.advance();
9756 if !self.check(TokenType::Colon) {
9757 if self.check(TokenType::LBrace) {
9758 self.skip_braced_block()?;
9759 }
9760 continue;
9761 }
9762 self.advance();
9763 match field_name.as_str() {
9764 "source" => node.source = self.consume_any_ident_or_kw()?.value,
9765 "on_level" => {
9766 let l_tok = self.consume_any_ident_or_kw()?;
9767 let l = l_tok.value;
9768 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
9769 return Err(ParseError {
9770 message: format!(
9771 "Invalid on_level '{l}' in heal '{}' — \
9772 expected know | believe | speculate | doubt",
9773 node.name
9774 ),
9775 line: l_tok.line,
9776 column: l_tok.column,
9777 ..Default::default()
9778 });
9779 }
9780 node.on_level = l;
9781 }
9782 "mode" => {
9783 let m_tok = self.consume_any_ident_or_kw()?;
9784 let m = m_tok.value;
9785 if !matches!(m.as_str(), "audit_only" | "human_in_loop" | "adversarial") {
9786 return Err(ParseError {
9787 message: format!(
9788 "Invalid mode '{m}' in heal '{}' — \
9789 expected audit_only | human_in_loop | adversarial",
9790 node.name
9791 ),
9792 line: m_tok.line,
9793 column: m_tok.column,
9794 ..Default::default()
9795 });
9796 }
9797 node.mode = m;
9798 }
9799 "scope" => {
9800 let s_tok = self.consume_any_ident_or_kw()?;
9801 let s = s_tok.value;
9802 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
9803 return Err(ParseError {
9804 message: format!(
9805 "Invalid scope '{s}' in heal '{}' — \
9806 expected tenant | flow | global",
9807 node.name
9808 ),
9809 line: s_tok.line,
9810 column: s_tok.column,
9811 ..Default::default()
9812 });
9813 }
9814 node.scope = s;
9815 }
9816 "review_sla" => {
9817 let t = self.current().clone();
9818 match t.ttype {
9819 TokenType::Duration | TokenType::StringLit => {
9820 self.advance();
9821 node.review_sla = t.value;
9822 }
9823 _ => node.review_sla = self.consume_any_ident_or_kw()?.value,
9824 }
9825 }
9826 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
9827 "max_patches" => {
9828 if let Some(v) = self.parse_optional_int() {
9829 node.max_patches = v;
9830 }
9831 }
9832 _ => self.skip_value(),
9833 }
9834 }
9835 self.consume(TokenType::RBrace)?;
9836 Ok(node)
9837 }
9838
9839 fn parse_component(&mut self) -> Result<ComponentDefinition, ParseError> {
9843 let tok = self.consume(TokenType::Component)?;
9844 let name = self.consume(TokenType::Identifier)?.value;
9845 let mut node = ComponentDefinition {
9846 name,
9847 renders: String::new(),
9848 via_shield: String::new(),
9849 on_interact: String::new(),
9850 render_hint: "custom".to_string(),
9851 loc: Loc {
9852 line: tok.line,
9853 column: tok.column,
9854 },
9855 leading_trivia: Vec::new(),
9856 trailing_trivia: Vec::new(),
9857 };
9858 self.consume(TokenType::LBrace)?;
9859 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9860 let field_name = self.current().value.clone();
9861 self.advance();
9862 if !self.check(TokenType::Colon) {
9863 if self.check(TokenType::LBrace) {
9864 self.skip_braced_block()?;
9865 }
9866 continue;
9867 }
9868 self.advance();
9869 match field_name.as_str() {
9870 "renders" => node.renders = self.consume_any_ident_or_kw()?.value,
9871 "via_shield" => node.via_shield = self.consume_any_ident_or_kw()?.value,
9872 "on_interact" => node.on_interact = self.consume_any_ident_or_kw()?.value,
9873 "render_hint" => {
9874 let h_tok = self.consume_any_ident_or_kw()?;
9875 let h = h_tok.value;
9876 if !matches!(h.as_str(), "card" | "list" | "form" | "chart" | "custom") {
9877 return Err(ParseError {
9878 message: format!(
9879 "Invalid render_hint '{h}' in component '{}' — \
9880 expected card | list | form | chart | custom",
9881 node.name
9882 ),
9883 line: h_tok.line,
9884 column: h_tok.column,
9885 ..Default::default()
9886 });
9887 }
9888 node.render_hint = h;
9889 }
9890 _ => self.skip_value(),
9891 }
9892 }
9893 self.consume(TokenType::RBrace)?;
9894 Ok(node)
9895 }
9896
9897 fn parse_view(&mut self) -> Result<ViewDefinition, ParseError> {
9899 let tok = self.consume(TokenType::View)?;
9900 let name = self.consume(TokenType::Identifier)?.value;
9901 let mut node = ViewDefinition {
9902 name,
9903 title: String::new(),
9904 components: Vec::new(),
9905 route: String::new(),
9906 loc: Loc {
9907 line: tok.line,
9908 column: tok.column,
9909 },
9910 leading_trivia: Vec::new(),
9911 trailing_trivia: Vec::new(),
9912 };
9913 self.consume(TokenType::LBrace)?;
9914 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9915 let field_name = self.current().value.clone();
9916 self.advance();
9917 if !self.check(TokenType::Colon) {
9918 if self.check(TokenType::LBrace) {
9919 self.skip_braced_block()?;
9920 }
9921 continue;
9922 }
9923 self.advance();
9924 match field_name.as_str() {
9925 "title" => node.title = self.consume(TokenType::StringLit)?.value,
9926 "components" => node.components = self.parse_bracketed_identifiers()?,
9927 "route" => node.route = self.consume(TokenType::StringLit)?.value,
9928 _ => self.skip_value(),
9929 }
9930 }
9931 self.consume(TokenType::RBrace)?;
9932 Ok(node)
9933 }
9934
9935 fn parse_axonendpoint(&mut self) -> Result<AxonEndpointDefinition, ParseError> {
9936 let tok = self.consume(TokenType::AxonEndpoint)?;
9937 let name = self.consume(TokenType::Identifier)?.value;
9938 let mut node = AxonEndpointDefinition {
9939 name,
9940 method: String::new(),
9941 path: String::new(),
9942 body_type: String::new(),
9943 execute_flow: String::new(),
9944 output_type: String::new(),
9945 shield_ref: String::new(),
9946 cors_ref: String::new(),
9949 retries: None,
9950 timeout: String::new(),
9951 compliance: Vec::new(),
9952 transport: "json".to_string(),
9954 keepalive: String::new(),
9955 transport_explicit: false,
9961 implicit_transport: String::new(),
9962 requires_capabilities: Vec::new(),
9964 public: false,
9967 replay_explicit: false,
9971 replay: false,
9972 transport_dialect: String::new(),
9977 has_algebraic_stream_effect: false,
9983 backend: String::new(),
9988 path_params: Vec::new(),
9993 query_params: Vec::new(),
9998 loc: Loc {
9999 line: tok.line,
10000 column: tok.column,
10001 },
10002 leading_trivia: Vec::new(),
10003 trailing_trivia: Vec::new(),
10004 };
10005 self.consume(TokenType::LBrace)?;
10006 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10007 let field_name = self.current().value.clone();
10008 self.advance();
10009 if self.check(TokenType::Colon) {
10010 self.advance();
10011 match field_name.as_str() {
10012 "method" => {
10013 let value_tok = self.consume_any_ident_or_kw()?;
10020 let value_upper = value_tok.value.to_uppercase();
10021 if !axonendpoint_is_valid_method(&value_upper) {
10022 let hint = crate::smart_suggest::suggest_for(
10023 &value_upper,
10024 AXONENDPOINT_METHOD_VALUES,
10025 );
10026 let base = format!(
10027 "Invalid method '{}' in axonendpoint '{}'.",
10028 value_tok.value, node.name
10029 );
10030 let message = if hint.is_empty() {
10031 format!(
10032 "{base} expected GET | POST | PUT | DELETE | PATCH, found {}",
10033 value_tok.value
10034 )
10035 } else {
10036 format!(
10037 "{base} {hint} (expected GET | POST | PUT | DELETE | PATCH, found {})",
10038 value_tok.value
10039 )
10040 };
10041 return Err(ParseError {
10042 message,
10043 line: value_tok.line,
10044 column: value_tok.column,
10045 ..Default::default()
10046 });
10047 }
10048 node.method = value_upper;
10049 }
10050 "path" => {
10051 node.path = self.consume(TokenType::StringLit)?.value.clone();
10052 match extract_path_param_names(&node.path) {
10060 Ok(names) => node.path_params = names,
10061 Err(dup) => {
10062 let cur = self.current().clone();
10063 return Err(ParseError {
10064 message: format!(
10065 "axonendpoint '{}' declares path '{}' \
10066 containing duplicate placeholder '{{{}}}'. \
10067 Each `{{name}}` in a `path:` must be \
10068 unique — the runtime cannot bind two \
10069 path segments to the same name (Fase 37.y D1).",
10070 node.name, node.path, dup,
10071 ),
10072 line: cur.line,
10073 column: cur.column,
10074 ..Default::default()
10075 });
10076 }
10077 }
10078 },
10079 "body" => node.body_type = self.consume_any_ident_or_kw()?.value.clone(),
10080 "query" => {
10081 let lbrace_tok = self.consume(TokenType::LBrace)?;
10100 let block_line = lbrace_tok.line;
10101 if !node.query_params.is_empty() {
10102 return Err(ParseError {
10103 message: format!(
10104 "axonendpoint '{}' declares `query: {{ … }}` \
10105 more than once. The query-parameter block \
10106 is unique per endpoint; combine all params \
10107 into a single block (Fase 37.y D2).",
10108 node.name,
10109 ),
10110 line: lbrace_tok.line,
10111 column: lbrace_tok.column,
10112 ..Default::default()
10113 });
10114 }
10115 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10116 let name_tok = self.consume(TokenType::Identifier)?;
10117 let field_name = name_tok.value.clone();
10118 if node
10120 .query_params
10121 .iter()
10122 .any(|f| f.name == field_name)
10123 {
10124 return Err(ParseError {
10125 message: format!(
10126 "axonendpoint '{}' declares duplicate \
10127 query param '{}' inside `query: {{ … }}`. \
10128 Each name must appear at most once \
10129 (Fase 37.y D2).",
10130 node.name, field_name,
10131 ),
10132 line: name_tok.line,
10133 column: name_tok.column,
10134 ..Default::default()
10135 });
10136 }
10137 self.consume(TokenType::Colon)?;
10138 let type_expr = self.parse_type_expr()?;
10139 if !type_expr.generic_param.is_empty() {
10151 let canonical_hint = if type_expr.name == "Optional" {
10152 format!(
10153 " Use `{}?` (the `?` suffix) for an \
10154 optional query param instead of \
10155 `Optional<{}>`.",
10156 type_expr.generic_param,
10157 type_expr.generic_param,
10158 )
10159 } else if type_expr.name == "List" {
10160 " Multi-value query params (e.g. `?tag=a&tag=b`) \
10161 are honest-deferred from v1.38.5; bind a \
10162 single-value `Text` query param and parse \
10163 the value inside the flow."
10164 .to_string()
10165 } else {
10166 String::new()
10167 };
10168 return Err(ParseError {
10169 message: format!(
10170 "axonendpoint '{}' query param '{}' uses \
10171 a generic type `{}<{}>`. Query params \
10172 take a primitive type from the closed \
10173 catalog ({}); the `?` suffix marks \
10174 optional.{} (Fase 37.y D2).",
10175 node.name,
10176 field_name,
10177 type_expr.name,
10178 type_expr.generic_param,
10179 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | "),
10180 canonical_hint,
10181 ),
10182 line: type_expr.loc.line,
10183 column: type_expr.loc.column,
10184 ..Default::default()
10185 });
10186 }
10187 if !axonendpoint_is_valid_query_param_type(&type_expr.name) {
10191 let hint = crate::smart_suggest::suggest_for(
10198 &type_expr.name,
10199 AXONENDPOINT_QUERY_PARAM_TYPES,
10200 );
10201 let hint_text = if hint.is_empty() {
10202 format!(
10203 " Expected one of: {}.",
10204 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
10205 )
10206 } else {
10207 format!(
10208 " {} Expected one of: {}.",
10209 hint,
10210 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
10211 )
10212 };
10213 return Err(ParseError {
10214 message: format!(
10215 "axonendpoint '{}' query param '{}' has \
10216 unsupported type '{}'.{} (Fase 37.y D2).",
10217 node.name, field_name, type_expr.name,
10218 hint_text,
10219 ),
10220 line: type_expr.loc.line,
10221 column: type_expr.loc.column,
10222 ..Default::default()
10223 });
10224 }
10225 node.query_params.push(TypeField {
10226 name: field_name,
10227 type_expr,
10228 loc: Loc {
10229 line: name_tok.line,
10230 column: name_tok.column,
10231 },
10232 });
10233 if self.check(TokenType::Comma) {
10239 self.advance();
10240 }
10241 let _ = block_line; }
10243 self.consume(TokenType::RBrace)?;
10244 },
10245 "execute" => node.execute_flow = self.consume_any_ident_or_kw()?.value.clone(),
10246 "output" => {
10247 node.output_type = self.parse_output_type_string()?;
10265 }
10266 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
10267 "cors" => node.cors_ref = self.consume_any_ident_or_kw()?.value.clone(),
10269 "retries" => node.retries = self.parse_optional_int(),
10270 "timeout" => {
10271 let t = self.current().clone();
10272 self.advance();
10273 node.timeout = t.value.clone();
10274 }
10275 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
10276 "replay" => {
10277 let value_tok = self.consume(TokenType::Bool)?;
10284 node.replay = value_tok.value.eq_ignore_ascii_case("true");
10285 node.replay_explicit = true;
10286 }
10287 "public" => {
10293 let value_tok = self.consume(TokenType::Bool)?;
10294 node.public = value_tok.value.eq_ignore_ascii_case("true");
10295 }
10296 "requires" => {
10297 let bracket_tok = self.current().clone();
10304 let items = self.parse_bracketed_dot_identifiers()?;
10305 for slug in &items {
10306 if !is_valid_capability_slug(slug) {
10307 return Err(ParseError {
10308 message: format!(
10309 "Invalid capability slug '{slug}' in axonendpoint '{}' \
10310 `requires:`. Capability slugs must match \
10311 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
10312 lowercase identifiers starting with a letter. Examples: \
10313 `admin`, `legal.read`, `hipaa.phi.read`.",
10314 node.name
10315 ),
10316 line: bracket_tok.line,
10317 column: bracket_tok.column,
10318 ..Default::default()
10319 });
10320 }
10321 }
10322 node.requires_capabilities = items;
10323 }
10324 "transport" => {
10328 let value_tok = self.consume_any_ident_or_kw()?;
10329 let value = &value_tok.value;
10330 if !axonendpoint_is_valid_transport(value) {
10331 let hint = crate::smart_suggest::suggest_for(
10332 value,
10333 AXONENDPOINT_TRANSPORT_VALUES,
10334 );
10335 let base = format!(
10336 "Invalid transport '{}' in axonendpoint '{}'.",
10337 value, node.name
10338 );
10339 let message = if hint.is_empty() {
10340 format!("{base} expected json | sse | ndjson, found {value}")
10341 } else {
10342 format!(
10343 "{base} {hint} (expected json | sse | ndjson, found {value})"
10344 )
10345 };
10346 return Err(ParseError {
10347 message,
10348 line: value_tok.line,
10349 column: value_tok.column,
10350 ..Default::default()
10351 });
10352 }
10353 node.transport = value.clone();
10354 node.transport_explicit = true;
10359 if self.check(TokenType::LParen) {
10366 if value != "sse" {
10367 let tok = self.current().clone();
10368 return Err(ParseError {
10369 message: format!(
10370 "Dialect parametrization \
10371 `transport: {value}(<dialect>)` is \
10372 only valid for `sse`; got \
10373 `{value}` in axonendpoint '{}'.",
10374 node.name
10375 ),
10376 line: tok.line,
10377 column: tok.column,
10378 ..Default::default()
10379 });
10380 }
10381 self.advance(); let dialect_tok = self.consume_any_ident_or_kw()?;
10383 let dialect = dialect_tok.value.clone();
10384 if !AXONENDPOINT_TRANSPORT_DIALECTS
10385 .iter()
10386 .any(|&d| d == dialect)
10387 {
10388 let hint = crate::smart_suggest::suggest_for(
10389 &dialect,
10390 AXONENDPOINT_TRANSPORT_DIALECTS,
10391 );
10392 let base = format!(
10393 "Invalid SSE dialect '{dialect}' in axonendpoint '{}'.",
10394 node.name
10395 );
10396 let message = if hint.is_empty() {
10397 format!(
10398 "{base} expected axon | openai | kimi | glm | anthropic, found {dialect}"
10399 )
10400 } else {
10401 format!(
10402 "{base} {hint} (expected axon | openai | kimi | glm | anthropic, found {dialect})"
10403 )
10404 };
10405 return Err(ParseError {
10406 message,
10407 line: dialect_tok.line,
10408 column: dialect_tok.column,
10409 ..Default::default()
10410 });
10411 }
10412 let rparen_tok = self.current().clone();
10414 if !self.check(TokenType::RParen) {
10415 return Err(ParseError {
10416 message: format!(
10417 "Expected `)` after dialect name \
10418 in axonendpoint '{}' \
10419 (transport: sse(<dialect>) grammar).",
10420 node.name
10421 ),
10422 line: rparen_tok.line,
10423 column: rparen_tok.column,
10424 ..Default::default()
10425 });
10426 }
10427 self.advance(); node.transport_dialect = dialect;
10429 }
10430 }
10431 "keepalive" => {
10432 let value_tok = self.current().clone();
10436 self.advance();
10437 let value = &value_tok.value;
10438 if !axonendpoint_is_valid_keepalive(value) {
10439 let hint = crate::smart_suggest::suggest_for(
10440 value,
10441 AXONENDPOINT_KEEPALIVE_VALUES,
10442 );
10443 let base = format!(
10444 "Invalid keepalive '{}' in axonendpoint '{}'.",
10445 value, node.name
10446 );
10447 let message = if hint.is_empty() {
10448 format!("{base} expected 5s | 15s | 30s | 60s, found {value}")
10449 } else {
10450 format!(
10451 "{base} {hint} (expected 5s | 15s | 30s | 60s, found {value})"
10452 )
10453 };
10454 return Err(ParseError {
10455 message,
10456 line: value_tok.line,
10457 column: value_tok.column,
10458 ..Default::default()
10459 });
10460 }
10461 node.keepalive = value.clone();
10462 }
10463 "backend" => {
10464 let value_tok = self.consume_any_ident_or_kw()?;
10472 let value = &value_tok.value;
10473 if !axonendpoint_is_valid_backend(value) {
10474 let hint = crate::smart_suggest::suggest_for(
10475 value,
10476 AXONENDPOINT_BACKEND_VALUES,
10477 );
10478 let expected = AXONENDPOINT_BACKEND_VALUES.join(" | ");
10479 let base = format!(
10480 "Invalid backend '{}' in axonendpoint '{}'.",
10481 value, node.name
10482 );
10483 let message = if hint.is_empty() {
10484 format!("{base} expected {expected}, found {value}")
10485 } else {
10486 format!(
10487 "{base} {hint} (expected {expected}, found {value})"
10488 )
10489 };
10490 return Err(ParseError {
10491 message,
10492 line: value_tok.line,
10493 column: value_tok.column,
10494 ..Default::default()
10495 });
10496 }
10497 node.backend = value.clone();
10498 }
10499 _ => self.skip_value(),
10500 }
10501 } else if self.check(TokenType::LBrace) {
10502 self.skip_braced_block()?;
10503 }
10504 }
10505 self.consume(TokenType::RBrace)?;
10506 Ok(node)
10507 }
10508
10509 fn parse_optional_int(&mut self) -> Option<i64> {
10512 let tok = self.current().clone();
10513 match tok.ttype {
10514 TokenType::Integer => {
10515 self.advance();
10516 tok.value.parse::<i64>().ok()
10517 }
10518 _ => {
10519 self.advance();
10520 None
10521 }
10522 }
10523 }
10524
10525 fn parse_optional_float(&mut self) -> Option<f64> {
10526 let tok = self.current().clone();
10527 match tok.ttype {
10528 TokenType::Float | TokenType::Integer => {
10529 self.advance();
10530 tok.value.parse::<f64>().ok()
10531 }
10532 _ => {
10533 self.advance();
10534 None
10535 }
10536 }
10537 }
10538
10539 fn parse_lambda_data(&mut self) -> Result<LambdaDataDefinition, ParseError> {
10542 let tok = self.consume(TokenType::Lambda)?;
10543 let name = self.consume(TokenType::Identifier)?;
10544 self.consume(TokenType::LBrace)?;
10545
10546 let mut node = LambdaDataDefinition {
10547 name: name.value.clone(),
10548 ontology: String::new(),
10549 certainty: 1.0,
10550 temporal_frame_start: String::new(),
10551 temporal_frame_end: String::new(),
10552 provenance: String::new(),
10553 derivation: String::new(),
10554 loc: Loc {
10555 line: tok.line,
10556 column: tok.column,
10557 },
10558 leading_trivia: Vec::new(),
10559 trailing_trivia: Vec::new(),
10560 };
10561
10562 while !self.check(TokenType::RBrace) {
10563 let field = self.current().clone();
10564 match field.ttype {
10565 TokenType::Ontology => {
10566 self.advance();
10567 self.consume(TokenType::Colon)?;
10568 node.ontology = self.consume(TokenType::StringLit)?.value.clone();
10569 }
10570 TokenType::Certainty => {
10571 self.advance();
10572 self.consume(TokenType::Colon)?;
10573 let val = self.current().clone();
10574 match val.ttype {
10575 TokenType::Float => {
10576 self.advance();
10577 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
10578 }
10579 TokenType::Integer => {
10580 self.advance();
10581 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
10582 }
10583 _ => {
10584 return Err(ParseError {
10585 message: format!(
10586 "Expected number for certainty, got '{}'",
10587 val.value
10588 ),
10589 line: val.line,
10590 column: val.column,
10591 ..Default::default()
10592 });
10593 }
10594 }
10595 }
10596 TokenType::TemporalFrame => {
10597 self.advance();
10598 self.consume(TokenType::Colon)?;
10599 node.temporal_frame_start = self.consume(TokenType::StringLit)?.value.clone();
10600 if self.check(TokenType::StringLit) {
10602 node.temporal_frame_end = self.consume(TokenType::StringLit)?.value.clone();
10603 }
10604 }
10605 TokenType::Provenance => {
10606 self.advance();
10607 self.consume(TokenType::Colon)?;
10608 node.provenance = self.consume(TokenType::StringLit)?.value.clone();
10609 }
10610 TokenType::Derivation => {
10611 self.advance();
10612 self.consume(TokenType::Colon)?;
10613 let d = self.current().clone();
10614 self.advance();
10615 node.derivation = d.value.clone();
10616 }
10617 _ => {
10618 self.advance();
10620 if self.check(TokenType::Colon) {
10621 self.advance();
10622 self.skip_value();
10623 }
10624 }
10625 }
10626 }
10627
10628 self.consume(TokenType::RBrace)?;
10629 Ok(node)
10630 }
10631
10632 fn parse_lambda_data_apply(&mut self) -> Result<LambdaDataApplyNode, ParseError> {
10633 let tok = self.consume(TokenType::Lambda)?;
10634 let lambda_name = self.consume(TokenType::Identifier)?;
10635
10636 let on_tok = self.current().clone();
10638 self.advance();
10639 if on_tok.value != "on" {
10640 return Err(ParseError {
10641 message: format!(
10642 "Expected 'on' after lambda data name in flow step, got '{}'",
10643 on_tok.value
10644 ),
10645 line: on_tok.line,
10646 column: on_tok.column,
10647 ..Default::default()
10648 });
10649 }
10650
10651 let target = self.current().clone();
10652 self.advance();
10653
10654 let mut output_type = String::new();
10655 if self.check(TokenType::Arrow) {
10656 self.advance();
10657 output_type = self.consume(TokenType::Identifier)?.value.clone();
10658 }
10659
10660 Ok(LambdaDataApplyNode {
10661 lambda_data_name: lambda_name.value.clone(),
10662 target: target.value.clone(),
10663 output_type,
10664 loc: Loc {
10665 line: tok.line,
10666 column: tok.column,
10667 },
10668 })
10669 }
10670
10671 fn parse_generic_declaration(&mut self) -> Result<Declaration, ParseError> {
10674 let kw_tok = self.current().clone();
10675 self.advance(); let name = if self.current().ttype == TokenType::Identifier {
10679 let n = self.current().value.clone();
10680 self.advance();
10681 n
10682 } else if !self.check(TokenType::LBrace)
10683 && !self.check(TokenType::LParen)
10684 && !self.check(TokenType::Eof)
10685 && self
10686 .current()
10687 .value
10688 .chars()
10689 .all(|c| c.is_alphanumeric() || c == '_')
10690 {
10691 let n = self.current().value.clone();
10692 self.advance();
10693 n
10694 } else {
10695 String::new()
10696 };
10697
10698 if self.check(TokenType::LParen) {
10700 self.advance();
10701 let mut depth = 1u32;
10702 while depth > 0 && !self.check(TokenType::Eof) {
10703 if self.check(TokenType::LParen) {
10704 depth += 1;
10705 } else if self.check(TokenType::RParen) {
10706 depth -= 1;
10707 }
10708 self.advance();
10709 }
10710 }
10711
10712 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
10714 if self.check(TokenType::Eof) {
10715 break;
10716 }
10717 self.advance();
10718 }
10719
10720 if self.check(TokenType::LBrace) {
10722 self.skip_braced_block()?;
10723 }
10724
10725 Ok(Declaration::Generic(GenericDeclaration {
10726 keyword: kw_tok.value,
10727 name,
10728 loc: Loc {
10729 line: kw_tok.line,
10730 column: kw_tok.column,
10731 },
10732 leading_trivia: Vec::new(),
10733 trailing_trivia: Vec::new(),
10734 }))
10735 }
10736
10737 fn parse_channel(&mut self) -> Result<ChannelDefinition, ParseError> {
10745 let tok = self.consume(TokenType::Channel)?;
10746 let name = self.consume(TokenType::Identifier)?.value;
10747 let mut node = ChannelDefinition {
10748 name: name.clone(),
10749 message: String::new(),
10750 qos: "at_least_once".to_string(),
10751 lifetime: "affine".to_string(),
10752 persistence: "ephemeral".to_string(),
10753 shield_ref: String::new(),
10754 loc: Loc {
10755 line: tok.line,
10756 column: tok.column,
10757 },
10758 leading_trivia: Vec::new(),
10759 trailing_trivia: Vec::new(),
10760 };
10761 self.consume(TokenType::LBrace)?;
10762 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10763 let field_tok = self.current().clone();
10764 let field_name = field_tok.value.clone();
10765 self.advance();
10766 if !self.check(TokenType::Colon) {
10767 if self.check(TokenType::LBrace) {
10768 self.skip_braced_block()?;
10769 }
10770 continue;
10771 }
10772 self.advance();
10773 match field_name.as_str() {
10774 "message" => node.message = self.parse_channel_message_type()?,
10775 "qos" => {
10776 let q_tok = self.consume_any_ident_or_kw()?;
10777 if !matches!(
10778 q_tok.value.as_str(),
10779 "at_most_once" | "at_least_once" | "exactly_once" | "broadcast" | "queue"
10780 ) {
10781 return Err(ParseError {
10782 message: format!(
10783 "Invalid qos '{}' in channel '{}' — \
10784 expected at_most_once | at_least_once | \
10785 exactly_once | broadcast | queue",
10786 q_tok.value, name
10787 ),
10788 line: q_tok.line,
10789 column: q_tok.column,
10790 ..Default::default()
10791 });
10792 }
10793 node.qos = q_tok.value;
10794 }
10795 "lifetime" => {
10796 let lt_tok = self.consume_any_ident_or_kw()?;
10797 if !matches!(lt_tok.value.as_str(), "linear" | "affine" | "persistent") {
10798 return Err(ParseError {
10799 message: format!(
10800 "Invalid lifetime '{}' in channel '{}' — \
10801 expected linear | affine | persistent",
10802 lt_tok.value, name
10803 ),
10804 line: lt_tok.line,
10805 column: lt_tok.column,
10806 ..Default::default()
10807 });
10808 }
10809 node.lifetime = lt_tok.value;
10810 }
10811 "persistence" => {
10812 let p_tok = self.consume_any_ident_or_kw()?;
10813 if !matches!(p_tok.value.as_str(), "ephemeral" | "persistent_axonstore") {
10814 return Err(ParseError {
10815 message: format!(
10816 "Invalid persistence '{}' in channel '{}' — \
10817 expected ephemeral | persistent_axonstore",
10818 p_tok.value, name
10819 ),
10820 line: p_tok.line,
10821 column: p_tok.column,
10822 ..Default::default()
10823 });
10824 }
10825 node.persistence = p_tok.value;
10826 }
10827 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
10828 _ => self.skip_value(),
10829 }
10830 }
10831 self.consume(TokenType::RBrace)?;
10832 Ok(node)
10833 }
10834
10835 fn parse_channel_message_type(&mut self) -> Result<String, ParseError> {
10838 let head = self.consume(TokenType::Identifier)?;
10839 let mut spelling = head.value;
10840 if self.check(TokenType::Lt) {
10841 self.advance();
10842 let inner = self.parse_channel_message_type()?;
10843 self.consume(TokenType::Gt)?;
10844 spelling = format!("{}<{}>", spelling, inner);
10845 }
10846 Ok(spelling)
10847 }
10848
10849 fn parse_emit_step(&mut self) -> Result<FlowStep, ParseError> {
10855 let tok = self.consume(TokenType::Emit)?;
10856 let channel = self.consume(TokenType::Identifier)?.value;
10857 self.consume(TokenType::LParen)?;
10858 let value = self.parse_emit_value_ref()?;
10859 self.consume(TokenType::RParen)?;
10860 Ok(FlowStep::Emit(EmitStatement {
10861 channel_ref: channel,
10862 value_ref: value,
10863 loc: Loc {
10864 line: tok.line,
10865 column: tok.column,
10866 },
10867 }))
10868 }
10869
10870 fn parse_mint_step(&mut self) -> Result<FlowStep, ParseError> {
10876 let tok = self.consume(TokenType::Mint)?;
10877 let credential_ref = self.consume(TokenType::Identifier)?.value;
10878 self.consume(TokenType::As)?;
10879 let binding = self.consume(TokenType::Identifier)?.value;
10880 Ok(FlowStep::Mint(MintStep {
10881 credential_ref,
10882 binding,
10883 loc: Loc {
10884 line: tok.line,
10885 column: tok.column,
10886 },
10887 }))
10888 }
10889
10890 fn parse_rotate_step(&mut self) -> Result<FlowStep, ParseError> {
10905 let tok = self.consume(TokenType::Rotate)?;
10906 let store_ref = self.consume(TokenType::Identifier)?.value;
10907 let mut where_expr = String::new();
10908 if self.check(TokenType::Where) {
10909 self.advance();
10910 where_expr = self.consume(TokenType::StringLit)?.value.clone();
10911 }
10912 let with_tok = self.current().clone();
10913 if with_tok.value != "with" {
10914 return Err(ParseError {
10915 message: format!(
10916 "Expected `with <Tool>` after `rotate {store_ref}{}`, found '{}'. \
10917 A rotation names the tool that performs the renewal exchange: \
10918 `rotate {store_ref} [where \"<filter>\"] with <Tool> as <binding>`.",
10919 if where_expr.is_empty() { "" } else { " where …" },
10920 with_tok.value
10921 ),
10922 line: with_tok.line,
10923 column: with_tok.column,
10924 ..Default::default()
10925 });
10926 }
10927 self.advance();
10928 let tool_ref = self.consume(TokenType::Identifier)?.value;
10929 self.consume(TokenType::As)?;
10930 let binding = self.consume(TokenType::Identifier)?.value;
10931 Ok(FlowStep::Rotate(RotateStep {
10932 store_ref,
10933 where_expr,
10934 tool_ref,
10935 binding,
10936 loc: Loc {
10937 line: tok.line,
10938 column: tok.column,
10939 },
10940 }))
10941 }
10942
10943 fn parse_emit_value_ref(&mut self) -> Result<String, ParseError> {
10959 let head = self.consume(TokenType::Identifier)?.value;
10960 let mut parts = vec![head];
10961 while self.check(TokenType::Dot) {
10962 self.advance(); let next_tok = self.current().clone();
10964 let valid = !next_tok.value.is_empty()
10965 && next_tok.value.as_bytes()[0].is_ascii_alphabetic()
10966 || next_tok.value.starts_with('_');
10967 if !valid {
10968 return Err(ParseError {
10969 message: format!(
10970 "Expected identifier or keyword after '.' in dotted \
10971 access, found {:?}",
10972 next_tok.value
10973 ),
10974 line: next_tok.line,
10975 column: next_tok.column,
10976 ..Default::default()
10977 });
10978 }
10979 self.advance();
10980 parts.push(next_tok.value);
10981 }
10982 Ok(parts.join("."))
10983 }
10984
10985 fn parse_publish_step(&mut self) -> Result<FlowStep, ParseError> {
10987 let tok = self.consume(TokenType::Publish)?;
10988 let channel = self.consume(TokenType::Identifier)?.value;
10989 self.consume(TokenType::Within)?;
10990 let shield = self.consume(TokenType::Identifier)?.value;
10991 Ok(FlowStep::Publish(PublishStatement {
10992 channel_ref: channel,
10993 shield_ref: shield,
10994 loc: Loc {
10995 line: tok.line,
10996 column: tok.column,
10997 },
10998 }))
10999 }
11000
11001 fn parse_discover_step(&mut self) -> Result<FlowStep, ParseError> {
11003 let tok = self.consume(TokenType::Discover)?;
11004 let cap = self.consume(TokenType::Identifier)?.value;
11005 self.consume(TokenType::As)?;
11006 let alias = self.consume(TokenType::Identifier)?.value;
11007 Ok(FlowStep::Discover(DiscoverStatement {
11008 capability_ref: cap,
11009 alias,
11010 loc: Loc {
11011 line: tok.line,
11012 column: tok.column,
11013 },
11014 }))
11015 }
11016}
11017
11018#[cfg(test)]
11021mod fase13_parser_tests {
11022 use super::*;
11023 use crate::lexer::Lexer;
11024
11025 fn parse(src: &str) -> Result<Program, ParseError> {
11026 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
11027 Parser::new(tokens).parse()
11028 }
11029
11030 #[test]
11031 fn channel_full_parses() {
11032 let src = r#"channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }"#;
11033 let prog = parse(src).expect("parse");
11034 match &prog.declarations[0] {
11035 Declaration::Channel(c) => {
11036 assert_eq!(c.name, "C");
11037 assert_eq!(c.message, "Order");
11038 assert_eq!(c.qos, "at_least_once");
11039 assert_eq!(c.lifetime, "affine");
11040 assert_eq!(c.persistence, "ephemeral");
11041 assert_eq!(c.shield_ref, "Gate");
11042 }
11043 _ => panic!("expected ChannelDefinition"),
11044 }
11045 }
11046
11047 #[test]
11048 fn channel_defaults_match_paper_d1() {
11049 let prog = parse("channel C { message: Order }").expect("parse");
11050 if let Declaration::Channel(c) = &prog.declarations[0] {
11051 assert_eq!(c.qos, "at_least_once"); assert_eq!(c.lifetime, "affine"); assert_eq!(c.persistence, "ephemeral");
11054 assert_eq!(c.shield_ref, "");
11055 } else {
11056 panic!("expected ChannelDefinition");
11057 }
11058 }
11059
11060 #[test]
11061 fn channel_second_order_message_type_parses() {
11062 let prog = parse("channel C { message: Channel<Order> }").expect("parse");
11063 if let Declaration::Channel(c) = &prog.declarations[0] {
11064 assert_eq!(c.message, "Channel<Order>");
11065 } else {
11066 panic!("expected ChannelDefinition");
11067 }
11068 }
11069
11070 #[test]
11071 fn channel_nested_channel_message_type_parses() {
11072 let prog = parse("channel C { message: Channel<Channel<Order>> }").expect("parse");
11073 if let Declaration::Channel(c) = &prog.declarations[0] {
11074 assert_eq!(c.message, "Channel<Channel<Order>>");
11075 } else {
11076 panic!("expected ChannelDefinition");
11077 }
11078 }
11079
11080 #[test]
11081 fn channel_invalid_qos_rejected() {
11082 let err = parse("channel C { message: T qos: bogus }").unwrap_err();
11083 assert!(err.message.contains("Invalid qos"), "got {}", err.message);
11084 }
11085
11086 #[test]
11087 fn channel_invalid_lifetime_rejected() {
11088 let err = parse("channel C { message: T lifetime: eternal }").unwrap_err();
11089 assert!(
11090 err.message.contains("Invalid lifetime"),
11091 "got {}",
11092 err.message
11093 );
11094 }
11095
11096 #[test]
11097 fn channel_invalid_persistence_rejected() {
11098 let err = parse("channel C { message: T persistence: forever }").unwrap_err();
11099 assert!(
11100 err.message.contains("Invalid persistence"),
11101 "got {}",
11102 err.message
11103 );
11104 }
11105
11106 #[test]
11107 fn emit_value_parses() {
11108 let src = "flow f() -> Out { emit C(payload) }";
11109 let prog = parse(src).expect("parse");
11110 if let Declaration::Flow(f) = &prog.declarations[0] {
11111 match &f.body[0] {
11112 FlowStep::Emit(e) => {
11113 assert_eq!(e.channel_ref, "C");
11114 assert_eq!(e.value_ref, "payload");
11115 }
11116 other => panic!("expected Emit, got {:?}", other),
11117 }
11118 } else {
11119 panic!("expected Flow");
11120 }
11121 }
11122
11123 #[test]
11124 fn publish_within_shield_parses() {
11125 let src = "flow f() -> Cap { publish C within Gate }";
11126 let prog = parse(src).expect("parse");
11127 if let Declaration::Flow(f) = &prog.declarations[0] {
11128 match &f.body[0] {
11129 FlowStep::Publish(p) => {
11130 assert_eq!(p.channel_ref, "C");
11131 assert_eq!(p.shield_ref, "Gate");
11132 }
11133 other => panic!("expected Publish, got {:?}", other),
11134 }
11135 } else {
11136 panic!("expected Flow");
11137 }
11138 }
11139
11140 #[test]
11141 fn discover_with_alias_parses() {
11142 let src = "flow f() -> Out { discover C as ch }";
11143 let prog = parse(src).expect("parse");
11144 if let Declaration::Flow(f) = &prog.declarations[0] {
11145 match &f.body[0] {
11146 FlowStep::Discover(d) => {
11147 assert_eq!(d.capability_ref, "C");
11148 assert_eq!(d.alias, "ch");
11149 }
11150 other => panic!("expected Discover, got {:?}", other),
11151 }
11152 } else {
11153 panic!("expected Flow");
11154 }
11155 }
11156
11157 #[test]
11158 fn listen_typed_ref_sets_flag_true() {
11159 let src = "daemon D() { goal: \"x\" listen C as ev { } }";
11160 let prog = parse(src).expect("parse");
11161 if let Declaration::Daemon(d) = &prog.declarations[0] {
11162 assert_eq!(d.listeners.len(), 1);
11163 assert_eq!(d.listeners[0].channel, "C");
11164 assert!(d.listeners[0].channel_is_ref, "typed ref ⇒ true");
11165 } else {
11166 panic!("expected Daemon");
11167 }
11168 }
11169
11170 #[test]
11171 fn listen_string_topic_legacy_flag_false() {
11172 let src = "daemon D() { goal: \"x\" listen \"orders\" as ev { } }";
11173 let prog = parse(src).expect("parse");
11174 if let Declaration::Daemon(d) = &prog.declarations[0] {
11175 assert_eq!(d.listeners.len(), 1);
11176 assert_eq!(d.listeners[0].channel, "orders");
11177 assert!(!d.listeners[0].channel_is_ref, "string topic ⇒ false");
11178 } else {
11179 panic!("expected Daemon");
11180 }
11181 }
11182
11183 fn extract_first_emit(prog: &Program) -> &EmitStatement {
11186 if let Declaration::Flow(f) = &prog.declarations[0] {
11187 if let FlowStep::Emit(e) = &f.body[0] {
11188 return e;
11189 }
11190 }
11191 panic!("expected emit statement at flow body[0]");
11192 }
11193
11194 #[test]
11195 fn emit_accepts_bare_identifier_value_ref() {
11196 let prog = parse("flow f() -> Out { emit Hello(payload) }").expect("parse");
11198 let emit = extract_first_emit(&prog);
11199 assert_eq!(emit.channel_ref, "Hello");
11200 assert_eq!(emit.value_ref, "payload");
11201 }
11202
11203 #[test]
11204 fn emit_accepts_two_segment_dotted_value_ref() {
11205 let prog = parse("flow f() -> Out { emit Hello(Build.output) }").expect("parse");
11207 let emit = extract_first_emit(&prog);
11208 assert_eq!(emit.value_ref, "Build.output");
11209 }
11210
11211 #[test]
11212 fn emit_accepts_three_segment_nested_dotted_value_ref() {
11213 let prog = parse("flow f() -> Out { emit Score(Analyze.result.score) }").expect("parse");
11214 let emit = extract_first_emit(&prog);
11215 assert_eq!(emit.value_ref, "Analyze.result.score");
11216 }
11217
11218 #[test]
11219 fn emit_dotted_with_trailing_dot_fails() {
11220 let result = parse("flow f() -> Out { emit Hello(Build.) }");
11222 assert!(result.is_err(), "expected parse error for trailing dot");
11223 }
11224}
11225
11226#[cfg(test)]
11229mod fase14a_declaration_trivia_tests {
11230 use super::*;
11231 use crate::lexer::Lexer;
11232 use crate::tokens::TriviaKind;
11233
11234 fn parse(src: &str) -> Program {
11235 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
11236 Parser::new(toks).parse().expect("parse")
11237 }
11238
11239 #[test]
11240 fn no_comments_means_empty_trivia_per_decl() {
11241 let prog = parse("flow F() -> Out { }");
11242 assert_eq!(prog.declarations.len(), 1);
11243 assert_eq!(prog.declaration_trivia.len(), 1);
11244 assert!(prog.declaration_trivia[0].leading.is_empty());
11245 assert!(prog.declaration_trivia[0].trailing.is_empty());
11246 }
11247
11248 #[test]
11249 fn doc_line_comment_attaches_as_leading() {
11250 let prog = parse("/// Documents F\nflow F() -> Out { }");
11251 let triv = &prog.declaration_trivia[0];
11252 assert_eq!(triv.leading.len(), 1);
11253 assert_eq!(triv.leading[0].kind, TriviaKind::DocLine);
11254 assert!(triv.leading[0].is_doc());
11255 assert_eq!(triv.leading[0].text, "/// Documents F");
11256 }
11257
11258 #[test]
11259 fn regular_line_comment_attaches_as_leading() {
11260 let prog = parse("// header\nflow F() -> Out { }");
11261 let triv = &prog.declaration_trivia[0];
11262 assert_eq!(triv.leading.len(), 1);
11263 assert_eq!(triv.leading[0].kind, TriviaKind::Line);
11264 assert!(!triv.leading[0].is_doc());
11265 }
11266
11267 #[test]
11268 fn block_doc_comment_attaches_as_leading() {
11269 let prog = parse("/** Doc block */\nflow F() -> Out { }");
11270 let triv = &prog.declaration_trivia[0];
11271 assert_eq!(triv.leading[0].kind, TriviaKind::DocBlock);
11272 assert!(triv.leading[0].is_doc());
11273 }
11274
11275 #[test]
11276 fn multiple_comments_collected_in_source_order() {
11277 let src = "/// First\n/// Second\nflow F() -> Out { }";
11278 let prog = parse(src);
11279 let triv = &prog.declaration_trivia[0];
11280 assert_eq!(triv.leading.len(), 2);
11281 assert_eq!(triv.leading[0].text, "/// First");
11282 assert_eq!(triv.leading[1].text, "/// Second");
11283 }
11284
11285 #[test]
11286 fn three_decls_each_get_own_leading() {
11287 let src = "/// for A\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }\n/// for C\nflow C() -> Out { }";
11288 let prog = parse(src);
11289 assert_eq!(prog.declarations.len(), 3);
11290 assert_eq!(prog.declaration_trivia.len(), 3);
11291 for (idx, name) in ["A", "B", "C"].iter().enumerate() {
11292 let triv = &prog.declaration_trivia[idx];
11293 assert_eq!(triv.leading.len(), 1);
11294 assert_eq!(triv.leading[0].text, format!("/// for {name}"));
11295 }
11296 }
11297
11298 #[test]
11299 fn trailing_comment_attaches_to_last_token_of_decl() {
11300 let prog = parse("flow F() -> Out { } // tail");
11302 let triv = &prog.declaration_trivia[0];
11303 assert_eq!(triv.trailing.len(), 1);
11304 assert_eq!(triv.trailing[0].text, "// tail");
11305 }
11306
11307 #[test]
11308 fn mixed_doc_and_regular_preserve_order_between_decls() {
11309 let src = "/// doc for A\nflow A() -> Out { }\n\n// header line\n/// doc for B\nflow B() -> Out { }";
11310 let prog = parse(src);
11311 assert_eq!(prog.declarations.len(), 2);
11312 assert_eq!(prog.declaration_trivia[0].leading.len(), 1);
11314 assert_eq!(prog.declaration_trivia[1].leading.len(), 2);
11316 assert_eq!(prog.declaration_trivia[1].leading[0].text, "// header line");
11317 assert_eq!(prog.declaration_trivia[1].leading[1].text, "/// doc for B");
11318 }
11319
11320 #[test]
11321 fn parser_unaffected_by_comments_in_grammar_path() {
11322 let src =
11327 "// before flow\nflow /* between flow and name */ F() -> Out {\n // body comment\n}";
11328 let prog = parse(src);
11329 assert_eq!(prog.declarations.len(), 1);
11330 if let Declaration::Flow(f) = &prog.declarations[0] {
11331 assert_eq!(f.name, "F");
11332 } else {
11333 panic!("expected Flow declaration");
11334 }
11335 }
11336}
11337
11338#[cfg(test)]
11347mod fase14b_per_struct_trivia_tests {
11348 use super::*;
11349 use crate::lexer::Lexer;
11350 use crate::tokens::TriviaKind;
11351
11352 fn parse(src: &str) -> Program {
11353 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
11354 Parser::new(toks).parse().expect("parse")
11355 }
11356
11357 #[test]
11358 fn flow_definition_carries_leading_trivia_directly() {
11359 let prog = parse("/// documents F\nflow F() -> Out { }");
11360 if let Declaration::Flow(f) = &prog.declarations[0] {
11361 assert_eq!(f.leading_trivia.len(), 1);
11362 assert_eq!(f.leading_trivia[0].kind, TriviaKind::DocLine);
11363 assert_eq!(f.leading_trivia[0].text, "/// documents F");
11364 assert!(f.trailing_trivia.is_empty());
11365 } else {
11366 panic!("expected Flow declaration");
11367 }
11368 }
11369
11370 #[test]
11371 fn flow_definition_carries_trailing_trivia_directly() {
11372 let prog = parse("flow F() -> Out { } // tail comment");
11373 if let Declaration::Flow(f) = &prog.declarations[0] {
11374 assert_eq!(f.trailing_trivia.len(), 1);
11375 assert_eq!(f.trailing_trivia[0].text, "// tail comment");
11376 } else {
11377 panic!("expected Flow declaration");
11378 }
11379 }
11380
11381 #[test]
11382 fn channel_definition_carries_trivia_directly() {
11383 let src = concat!(
11386 "/// inbound order events\n",
11387 "channel Orders {\n",
11388 " message: Order\n",
11389 " qos: at_least_once\n",
11390 " lifetime: affine\n",
11391 " persistence: ephemeral\n",
11392 " shield: Broker\n",
11393 "}",
11394 );
11395 let prog = parse(src);
11396 if let Declaration::Channel(ch) = &prog.declarations[0] {
11397 assert_eq!(ch.leading_trivia.len(), 1);
11398 assert!(ch.leading_trivia[0].is_doc());
11399 assert_eq!(ch.leading_trivia[0].text, "/// inbound order events");
11400 } else {
11401 panic!("expected Channel declaration");
11402 }
11403 }
11404
11405 #[test]
11406 fn per_struct_fields_match_side_channel() {
11407 let src = "/// for A\n// header for B\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }";
11410 let prog = parse(src);
11411 for (idx, decl) in prog.declarations.iter().enumerate() {
11412 let side = &prog.declaration_trivia[idx];
11413 let (per_lead, per_trail) = match decl {
11414 Declaration::Flow(f) => (&f.leading_trivia, &f.trailing_trivia),
11415 _ => panic!("unexpected variant"),
11416 };
11417 assert_eq!(per_lead.len(), side.leading.len());
11418 assert_eq!(per_trail.len(), side.trailing.len());
11419 for (a, b) in per_lead.iter().zip(side.leading.iter()) {
11420 assert_eq!(a.text, b.text);
11421 assert_eq!(a.kind, b.kind);
11422 }
11423 }
11424 }
11425
11426 #[test]
11427 fn comment_free_program_yields_empty_per_struct_fields() {
11428 let prog = parse("flow F() -> Out { }");
11429 if let Declaration::Flow(f) = &prog.declarations[0] {
11430 assert!(f.leading_trivia.is_empty());
11431 assert!(f.trailing_trivia.is_empty());
11432 } else {
11433 panic!("expected Flow declaration");
11434 }
11435 }
11436}
11437
11438#[cfg(test)]
11447mod fase14c_inner_doc_tests {
11448 use super::*;
11449 use crate::lexer::Lexer;
11450 use crate::tokens::TriviaKind;
11451
11452 fn parse(src: &str) -> Program {
11453 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
11454 Parser::new(toks).parse().expect("parse")
11455 }
11456
11457 #[test]
11458 fn inner_doc_line_reaches_leading_trivia() {
11459 let src = "//! file-level docs\nflow F() -> Out { }";
11460 let prog = parse(src);
11461 let triv = &prog.declaration_trivia[0];
11462 assert_eq!(triv.leading.len(), 1);
11463 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocLine);
11464 assert!(triv.leading[0].is_doc());
11465 assert!(triv.leading[0].is_inner_doc());
11466 assert_eq!(triv.leading[0].text, "//! file-level docs");
11467 assert_eq!(triv.leading[0].stripped_text(), " file-level docs");
11468 }
11469
11470 #[test]
11471 fn inner_doc_block_reaches_leading_trivia() {
11472 let src = "/*! module-level docs */\nflow F() -> Out { }";
11473 let prog = parse(src);
11474 let triv = &prog.declaration_trivia[0];
11475 assert_eq!(triv.leading.len(), 1);
11476 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocBlock);
11477 assert!(triv.leading[0].is_inner_doc());
11478 assert_eq!(triv.leading[0].stripped_text(), " module-level docs ");
11479 }
11480
11481 #[test]
11482 fn outer_and_inner_doc_can_coexist() {
11483 let src = "//! file docs\n/// docs F\nflow F() -> Out { }";
11487 let prog = parse(src);
11488 let triv = &prog.declaration_trivia[0];
11489 assert_eq!(triv.leading.len(), 2);
11490 assert!(triv.leading[0].is_inner_doc());
11491 assert!(triv.leading[1].is_doc());
11492 assert!(!triv.leading[1].is_inner_doc());
11493 }
11494
11495 #[test]
11496 fn inner_doc_reaches_per_struct_fields() {
11497 let src = "//! intro\nflow F() -> Out { }";
11499 let prog = parse(src);
11500 if let Declaration::Flow(f) = &prog.declarations[0] {
11501 assert_eq!(f.leading_trivia.len(), 1);
11502 assert!(f.leading_trivia[0].is_inner_doc());
11503 } else {
11504 panic!("expected Flow declaration");
11505 }
11506 }
11507}
11508
11509#[cfg(test)]
11527mod fase28_recovery_tests {
11528 use super::*;
11529 use crate::lexer::Lexer;
11530
11531 fn lex(src: &str) -> Vec<Token> {
11534 Lexer::new(src, "<test>").tokenize().expect("lex")
11535 }
11536
11537 fn recover(src: &str) -> ParseResult {
11540 Parser::new(lex(src)).parse_with_recovery()
11541 }
11542
11543 fn strict(src: &str) -> Result<Program, ParseError> {
11545 Parser::new(lex(src)).parse()
11546 }
11547
11548 #[test]
11551 fn strict_parse_unchanged_for_clean_source() {
11552 let src = "intent I {}";
11555 let prog = strict(src).expect("clean parse");
11556 assert_eq!(prog.declarations.len(), 1);
11557 }
11558
11559 #[test]
11560 fn strict_parse_still_raises_on_first_error() {
11561 let src = "flow F() { } not_a_keyword flow G() { }";
11566 let _ = strict(src).expect_err("must error fast in strict mode");
11567 }
11568
11569 #[test]
11570 fn recovery_clean_source_yields_no_errors() {
11571 let src = "flow F() { } flow G() { }";
11572 let pr = recover(src);
11573 assert!(pr.is_clean(), "errors: {:?}", pr.errors);
11574 assert_eq!(pr.program.declarations.len(), 2);
11575 }
11576
11577 #[test]
11580 fn single_unknown_top_level_token_recovers() {
11581 let src = "garbage_token flow F() { } flow G() { }";
11583 let pr = recover(src);
11584 assert_eq!(pr.errors.len(), 1, "errors: {:?}", pr.errors);
11585 assert_eq!(pr.program.declarations.len(), 2);
11586 }
11587
11588 #[test]
11589 fn error_in_first_decl_does_not_block_second() {
11590 let src = "flow F() { not_a_step nope } flow G() { }";
11593 let pr = recover(src);
11594 assert!(pr.has_errors(), "expected at least one error");
11595 let names: Vec<&str> = pr
11597 .program
11598 .declarations
11599 .iter()
11600 .filter_map(|d| match d {
11601 Declaration::Flow(f) => Some(f.name.as_str()),
11602 _ => None,
11603 })
11604 .collect();
11605 assert!(names.contains(&"G"), "G not found among {names:?}");
11606 }
11607
11608 #[test]
11609 fn malformed_declaration_then_clean_intent_recovers() {
11610 let src = "flow @ () { } intent I {}";
11611 let pr = recover(src);
11612 assert!(pr.has_errors());
11613 let kinds: Vec<&str> = pr
11614 .program
11615 .declarations
11616 .iter()
11617 .map(|d| match d {
11618 Declaration::Intent(_) => "intent",
11619 Declaration::Flow(_) => "flow",
11620 _ => "other",
11621 })
11622 .collect();
11623 assert!(kinds.contains(&"intent"), "kinds: {kinds:?}");
11624 }
11625
11626 #[test]
11627 fn recovery_does_not_double_count_a_single_error() {
11628 let src = "flow F() { not_a_step }";
11636 let pr = recover(src);
11637 let outer_ghosts = pr
11638 .errors
11639 .iter()
11640 .filter(|e| e.message.contains("at top level"))
11641 .count();
11642 assert_eq!(outer_ghosts, 0, "ghost errors: {:?}", pr.errors);
11643 }
11644
11645 #[test]
11648 fn three_independent_errors_yield_three_entries() {
11649 let src =
11650 "garbage1 flow F() { } garbage2 flow G() { } garbage3 flow H() { }";
11651 let pr = recover(src);
11652 assert_eq!(pr.errors.len(), 3, "errors: {:?}", pr.errors);
11653 assert_eq!(pr.program.declarations.len(), 3);
11654 }
11655
11656 #[test]
11657 fn all_errors_no_valid_declarations() {
11658 let src = "foo bar baz qux";
11659 let pr = recover(src);
11660 assert!(pr.has_errors());
11661 assert!(pr.program.declarations.is_empty());
11662 }
11663
11664 #[test]
11665 fn errors_recorded_in_source_order() {
11666 let src = "x flow A() { } y flow B() { } z flow C() { }";
11667 let pr = recover(src);
11668 assert_eq!(pr.errors.len(), 3);
11669 let lines: Vec<u32> = pr.errors.iter().map(|e| e.line).collect();
11670 assert!(
11673 lines.windows(2).all(|w| w[0] <= w[1]),
11674 "errors out of order: {lines:?}"
11675 );
11676 }
11677
11678 #[test]
11681 fn sync_to_flow_keyword() {
11682 let src = "garbage flow F() { }";
11683 let pr = recover(src);
11684 assert_eq!(pr.program.declarations.len(), 1);
11685 }
11686
11687 #[test]
11688 fn sync_to_intent_keyword() {
11689 let src = "garbage intent I {}";
11690 let pr = recover(src);
11691 assert_eq!(pr.program.declarations.len(), 1);
11692 }
11693
11694 #[test]
11695 fn sync_to_persona_keyword() {
11696 let src = "garbage persona P { name: \"P\" role: \"R\" }";
11697 let pr = recover(src);
11698 assert!(
11699 pr.program
11700 .declarations
11701 .iter()
11702 .any(|d| matches!(d, Declaration::Persona(_))),
11703 "persona not recovered: decls = {:?}",
11704 pr.program.declarations.len()
11705 );
11706 }
11707
11708 #[test]
11709 fn sync_to_run_keyword() {
11710 let src = "garbage run R { input: { user_message: \"hi\" } }";
11711 let pr = recover(src);
11712 assert!(pr.has_errors());
11714 }
11715
11716 #[test]
11719 fn parse_result_has_errors_and_is_clean_invert() {
11720 let pr_clean = recover("flow F() { }");
11721 assert!(pr_clean.is_clean());
11722 assert!(!pr_clean.has_errors());
11723
11724 let pr_err = recover("garbage");
11725 assert!(!pr_err.is_clean());
11726 assert!(pr_err.has_errors());
11727 }
11728
11729 #[test]
11730 fn parse_result_program_field_holds_partial_program() {
11731 let pr = recover("garbage flow F() { }");
11732 assert!(!pr.program.declarations.is_empty());
11733 }
11734
11735 #[test]
11736 fn parse_result_errors_carry_line_and_column() {
11737 let pr = recover("garbage");
11738 assert!(!pr.errors.is_empty());
11739 let e = &pr.errors[0];
11740 assert!(e.line >= 1);
11741 let _ = e.column;
11744 assert!(!e.message.is_empty());
11745 }
11746
11747 #[test]
11748 fn parse_result_debug_renders() {
11749 let pr = recover("flow F() { }");
11750 let s = format!("{pr:?}");
11751 assert!(s.contains("ParseResult"));
11752 }
11753
11754 #[test]
11757 fn empty_source_is_clean() {
11758 let pr = recover("");
11759 assert!(pr.is_clean());
11760 assert!(pr.program.declarations.is_empty());
11761 }
11762
11763 #[test]
11764 fn whitespace_only_source_is_clean() {
11765 let pr = recover(" \n\n\t \n");
11766 assert!(pr.is_clean());
11767 assert!(pr.program.declarations.is_empty());
11768 }
11769
11770 #[test]
11771 fn only_garbage_does_not_crash() {
11772 let pr = recover("foo bar baz { qux quux } corge { grault }");
11774 assert!(pr.has_errors());
11775 }
11776
11777 #[test]
11778 fn unbalanced_close_brace_does_not_crash() {
11779 let pr = recover("} flow F() { }");
11780 let names: Vec<&str> = pr
11782 .program
11783 .declarations
11784 .iter()
11785 .filter_map(|d| match d {
11786 Declaration::Flow(f) => Some(f.name.as_str()),
11787 _ => None,
11788 })
11789 .collect();
11790 assert!(names.contains(&"F"), "F not recovered: {names:?}");
11791 }
11792
11793 #[test]
11794 fn error_at_eof_does_not_loop() {
11795 let pr = recover("flow F() { ");
11797 let _ = pr.errors.len();
11799 }
11800
11801 #[test]
11802 fn nested_braces_inside_error_still_balance() {
11803 let src = "flow F() { not_a_step { inner } } flow G() { }";
11806 let pr = recover(src);
11807 let names: Vec<&str> = pr
11808 .program
11809 .declarations
11810 .iter()
11811 .filter_map(|d| match d {
11812 Declaration::Flow(f) => Some(f.name.as_str()),
11813 _ => None,
11814 })
11815 .collect();
11816 assert!(names.contains(&"G"), "G not recovered: {names:?}");
11817 }
11818
11819 #[derive(Clone, Copy)]
11828 struct Xorshift(u64);
11829 impl Xorshift {
11830 fn next(&mut self) -> u64 {
11831 let mut x = self.0;
11832 x ^= x << 13;
11833 x ^= x >> 7;
11834 x ^= x << 17;
11835 self.0 = x;
11836 x
11837 }
11838 fn pick<T: Copy>(&mut self, slice: &[T]) -> T {
11839 slice[(self.next() as usize) % slice.len()]
11840 }
11841 }
11842
11843 fn mutate(src: &str, rng: &mut Xorshift) -> String {
11844 let mut bytes: Vec<u8> = src.bytes().collect();
11845 if bytes.is_empty() {
11846 return src.to_string();
11847 }
11848 let op = rng.next() % 4;
11849 let pos = (rng.next() as usize) % bytes.len();
11850 let safe: &[u8] = b"abcdefghijklmnopqrstuvwxyz {}();:,_0123456789";
11854 match op {
11855 0 => {
11856 bytes.remove(pos);
11857 }
11858 1 => {
11859 let b = rng.pick(safe);
11860 bytes.insert(pos, b);
11861 }
11862 2 if pos + 1 < bytes.len() => {
11863 bytes.swap(pos, pos + 1);
11864 }
11865 _ => {
11866 let b = rng.pick(safe);
11867 bytes[pos] = b;
11868 }
11869 }
11870 bytes.retain(|b| b.is_ascii());
11873 String::from_utf8_lossy(&bytes).into_owned()
11874 }
11875
11876 #[test]
11877 fn fuzz_recovery_never_crashes() {
11878 let seed_bases = [
11879 "flow F() { }",
11880 "intent I { }",
11881 "persona P { name: \"P\" role: \"R\" }",
11882 "intent J { ask: \"a\" }",
11883 "type T = String",
11884 ];
11885 for (bucket, base) in (0..100u64).zip(seed_bases.iter().cycle()) {
11887 let mut rng = Xorshift(0x1234_5678_9abc_def0_u64.wrapping_add(bucket));
11888 let mut current = (*base).to_string();
11889 for _ in 0..10 {
11890 current = mutate(¤t, &mut rng);
11891 let toks = match Lexer::new(¤t, "<fuzz>").tokenize() {
11894 Ok(t) => t,
11895 Err(_) => continue,
11896 };
11897 let _pr = Parser::new(toks).parse_with_recovery();
11899 }
11900 }
11901 }
11902
11903 #[test]
11906 fn missing_colon_hint_preserved_under_recovery() {
11907 let src = "flow F() { run R { input { user_message: \"hi\" } } }";
11911 let pr = recover(src);
11912 if !pr.errors.is_empty() {
11916 let any_msg = pr.errors.iter().any(|e| !e.message.is_empty());
11917 assert!(any_msg);
11918 }
11919 }
11920
11921 #[test]
11924 fn recovered_declarations_appear_in_source_order() {
11925 let src = "flow A() { } garbage flow B() { } garbage flow C() { }";
11926 let pr = recover(src);
11927 let names: Vec<&str> = pr
11928 .program
11929 .declarations
11930 .iter()
11931 .filter_map(|d| match d {
11932 Declaration::Flow(f) => Some(f.name.as_str()),
11933 _ => None,
11934 })
11935 .collect();
11936 assert_eq!(names, vec!["A", "B", "C"]);
11937 }
11938}
11939
11940#[cfg(test)]
11948mod fase28_source_context_tests {
11949 use super::*;
11950 use crate::lexer::Lexer;
11951
11952 fn snippet(source: &str, line: u32, column: u32, filename: &str) -> String {
11953 SourceSnippet::new(
11954 source.to_string(),
11955 line,
11956 column,
11957 filename.to_string(),
11958 )
11959 .render()
11960 }
11961
11962 #[test]
11965 fn rustc_style_block_for_middle_line() {
11966 let src = "line one\nline two\nline three\nline four\nline five";
11967 let out = snippet(src, 3, 6, "x.axon");
11968 assert!(out.contains("--> x.axon:3:6"));
11969 assert!(out.contains("1 | line one"));
11970 assert!(out.contains("2 | line two"));
11971 assert!(out.contains("3 | line three"));
11972 assert!(out.contains("4 | line four"));
11973 assert!(out.contains("5 | line five"));
11974 assert!(out.contains("\n | ^"), "out:\n{out}");
11976 }
11977
11978 #[test]
11979 fn caret_column_one_renders_correctly() {
11980 let out = snippet("abc\n", 1, 1, "<source>");
11981 assert!(out.contains("\n | ^"));
11982 }
11983
11984 #[test]
11985 fn first_line_clamps_context_before_to_zero() {
11986 let src = "first\nsecond\nthird\nfourth\nfifth";
11987 let out = snippet(src, 1, 1, "<source>");
11988 assert!(out.contains("1 | first"));
11989 assert!(out.contains("2 | second"));
11990 assert!(out.contains("3 | third"));
11991 assert!(!out.contains("4 | fourth"));
11992 }
11993
11994 #[test]
11995 fn last_line_clamps_context_after_to_eof() {
11996 let src = "first\nsecond\nthird\nfourth\nfifth";
11997 let out = snippet(src, 5, 2, "<source>");
11998 assert!(out.contains("5 | fifth"));
11999 assert!(out.contains("3 | third"));
12000 assert!(out.contains("4 | fourth"));
12001 assert!(!out.contains("2 | second"));
12002 }
12003
12004 #[test]
12005 fn gutter_width_grows_with_line_count() {
12006 let src: String = (1..=12).map(|i| format!("line{i}")).collect::<Vec<_>>().join("\n");
12007 let out = snippet(&src, 12, 1, "<source>");
12008 assert!(out.contains("12 | line12"));
12009 assert!(out.contains("10 | line10"));
12010 }
12011
12012 #[test]
12015 fn empty_source_returns_empty() {
12016 assert_eq!(snippet("", 1, 1, "<source>"), "");
12017 }
12018
12019 #[test]
12020 fn zero_line_returns_empty() {
12021 assert_eq!(snippet("hi", 0, 1, "<source>"), "");
12022 }
12023
12024 #[test]
12025 fn out_of_range_line_returns_empty() {
12026 assert_eq!(snippet("hi", 99, 1, "<source>"), "");
12027 }
12028
12029 #[test]
12030 fn caret_clamps_past_eol() {
12031 let out = snippet("hello", 1, 50, "<source>");
12032 assert!(out.contains("\n | ^"), "out:\n{out}");
12033 }
12034
12035 #[test]
12036 fn unicode_codepoint_count_for_caret_clamp() {
12037 let out = snippet("héllo", 1, 99, "<source>");
12039 assert!(out.contains("\n | ^"), "out:\n{out}");
12040 }
12041
12042 #[test]
12043 fn trailing_newline_does_not_create_phantom_last_line() {
12044 let out = snippet("first\nsecond\n", 2, 1, "<source>");
12045 assert!(!out.contains("3 |"));
12046 assert!(out.contains("2 | second"));
12047 }
12048
12049 fn lex(src: &str) -> Vec<Token> {
12052 Lexer::new(src, "<test>").tokenize().expect("lex")
12053 }
12054
12055 #[test]
12056 fn strict_parse_attaches_snippet_when_source_given() {
12057 let src = "garbage_token\nflow F() { }";
12058 let err = Parser::new(lex(src))
12059 .with_source(src, "x.axon")
12060 .parse()
12061 .expect_err("must error");
12062 assert!(err.source_snippet.is_some());
12063 let display = format!("{err}");
12064 assert!(display.contains("--> x.axon:"), "display: {display}");
12065 }
12066
12067 #[test]
12068 fn strict_parse_no_snippet_when_no_source() {
12069 let src = "garbage_token";
12070 let err = Parser::new(lex(src)).parse().expect_err("must error");
12071 assert!(err.source_snippet.is_none());
12072 let display = format!("{err}");
12073 assert!(!display.contains("\n -->"));
12074 }
12075
12076 #[test]
12077 fn every_recovered_error_has_snippet() {
12078 let src = "garbage1\nflow F() { }\ngarbage2\nflow G() { }";
12079 let result = Parser::new(lex(src))
12080 .with_source(src, "multi.axon")
12081 .parse_with_recovery();
12082 assert!(!result.errors.is_empty());
12083 for err in &result.errors {
12084 assert!(err.source_snippet.is_some());
12085 let display = format!("{err}");
12086 assert!(
12087 display.contains("--> multi.axon:"),
12088 "display: {display}"
12089 );
12090 }
12091 }
12092
12093 #[test]
12094 fn recovery_no_snippet_when_no_source() {
12095 let src = "garbage1 garbage2";
12096 let result = Parser::new(lex(src)).parse_with_recovery();
12097 for err in &result.errors {
12098 assert!(err.source_snippet.is_none());
12099 }
12100 }
12101
12102 #[test]
12103 fn snippet_points_at_correct_line_for_each_error() {
12104 let src = "garbage_a\nflow F() { }\ngarbage_b\nflow G() { }";
12105 let result = Parser::new(lex(src))
12106 .with_source(src, "x")
12107 .parse_with_recovery();
12108 for err in &result.errors {
12109 let sn = err.source_snippet.as_ref().expect("snippet");
12110 assert_eq!(sn.line, err.line);
12111 }
12112 }
12113
12114 #[test]
12117 fn legacy_constructor_still_works() {
12118 let src = "flow F() { }";
12119 let prog = Parser::new(lex(src)).parse().expect("clean");
12120 assert_eq!(prog.declarations.len(), 1);
12121 }
12122
12123 #[test]
12124 fn attach_source_idempotent() {
12125 let err = ParseError {
12126 message: "bad".to_string(),
12127 line: 2,
12128 column: 3,
12129 ..Default::default()
12130 };
12131 let err2 = err.clone().attach_source("a\nb\nc\n", "f.axon");
12132 let first = format!("{err2}");
12133 let err3 = err.attach_source("a\nb\nc\n", "f.axon");
12134 let second = format!("{err3}");
12135 assert_eq!(first, second);
12136 }
12137
12138 #[test]
12139 fn attach_source_noop_when_line_zero() {
12140 let err = ParseError {
12141 message: "bad".to_string(),
12142 line: 0,
12143 column: 0,
12144 ..Default::default()
12145 };
12146 let err = err.attach_source("a\nb\nc\n", "f.axon");
12147 assert!(err.source_snippet.is_none());
12148 }
12149
12150 #[test]
12156 fn golden_simple_three_line_block() {
12157 let src = "alpha\nbeta\ngamma";
12158 let out = snippet(src, 2, 3, "g.axon");
12159 let expected = concat!(
12163 " --> g.axon:2:3\n",
12164 " |\n",
12165 "1 | alpha\n",
12166 "2 | beta\n",
12167 " | ^\n",
12168 "3 | gamma",
12169 );
12170 assert_eq!(out, expected);
12171 }
12172
12173 #[test]
12174 fn golden_first_line_caret() {
12175 let src = "abc\ndef\n";
12176 let out = snippet(src, 1, 1, "x");
12177 let expected = concat!(
12178 " --> x:1:1\n",
12179 " |\n",
12180 "1 | abc\n",
12181 " | ^\n",
12182 "2 | def",
12183 );
12184 assert_eq!(out, expected);
12185 }
12186
12187 #[test]
12188 fn golden_two_digit_gutter() {
12189 let src: String = (1..=11)
12190 .map(|i| format!("L{i}"))
12191 .collect::<Vec<_>>()
12192 .join("\n");
12193 let out = snippet(&src, 10, 2, "big");
12194 let expected = concat!(
12195 " --> big:10:2\n",
12196 " |\n",
12197 " 8 | L8\n",
12198 " 9 | L9\n",
12199 "10 | L10\n",
12200 " | ^\n",
12201 "11 | L11",
12202 );
12203 assert_eq!(out, expected);
12204 }
12205}
12206
12207#[cfg(test)]
12214mod fase28_smart_suggest_parser_tests {
12215 use super::*;
12216 use crate::lexer::Lexer;
12217
12218 fn lex(src: &str) -> Vec<Token> {
12219 Lexer::new(src, "<test>").tokenize().expect("lex")
12220 }
12221
12222 #[test]
12223 fn top_level_typo_suggests_flow() {
12224 let src = "flwo F() { }";
12225 let err = Parser::new(lex(src)).parse().expect_err("must error");
12226 assert!(
12227 err.message.contains("Did you mean `flow`?"),
12228 "msg: {}",
12229 err.message
12230 );
12231 }
12232
12233 #[test]
12234 fn top_level_unknown_far_no_suggestion() {
12235 let src = "qwerty F() { }";
12236 let err = Parser::new(lex(src)).parse().expect_err("must error");
12237 assert!(
12238 !err.message.contains("Did you mean"),
12239 "msg: {}",
12240 err.message
12241 );
12242 }
12243
12244 #[test]
12245 fn flow_body_typo_suggests_step() {
12246 let src = "flow F() { stepp S {} }";
12247 let err = Parser::new(lex(src)).parse().expect_err("must error");
12248 assert!(
12249 err.message.contains("Did you mean `step`"),
12250 "msg: {}",
12251 err.message
12252 );
12253 }
12254
12255 #[test]
12256 fn flow_body_typo_suggests_reason() {
12257 let src = "flow F() { reasn R {} }";
12258 let err = Parser::new(lex(src)).parse().expect_err("must error");
12259 assert!(
12260 err.message.contains("Did you mean `reason`?"),
12261 "msg: {}",
12262 err.message
12263 );
12264 }
12265
12266 #[test]
12267 fn recovery_mode_carries_hint() {
12268 let src = "flwo F() { }";
12269 let result = Parser::new(lex(src)).parse_with_recovery();
12270 assert!(
12271 result
12272 .errors
12273 .iter()
12274 .any(|e| e.message.contains("Did you mean `flow`?")),
12275 "errors: {:?}",
12276 result.errors
12277 );
12278 }
12279}
12280
12281#[cfg(test)]
12284mod fase35m_mutate_purge_where_tests {
12285 use super::*;
12286
12287 fn parse(src: &str) -> Program {
12288 let tokens = crate::lexer::Lexer::new(src, "<test>")
12289 .tokenize()
12290 .expect("lex");
12291 Parser::new(tokens).parse().expect("parse")
12292 }
12293
12294 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
12295 for d in &prog.declarations {
12296 if let Declaration::Flow(f) = d {
12297 if f.name == flow {
12298 return f.body.first().expect("flow has at least one step");
12299 }
12300 }
12301 }
12302 panic!("flow `{flow}` not found");
12303 }
12304
12305 #[test]
12306 fn mutate_captures_its_where_clause() {
12307 let prog =
12310 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
12311 match first_step(&prog, "F") {
12312 FlowStep::Mutate(m) => {
12313 assert_eq!(m.store_name, "accounts");
12314 assert_eq!(m.where_expr, "id = 1");
12315 }
12316 other => panic!("expected Mutate, got {other:?}"),
12317 }
12318 }
12319
12320 #[test]
12321 fn purge_captures_its_where_clause() {
12322 let prog =
12323 parse("flow F() -> Unit { purge logs { where: \"ts < 100\" } }");
12324 match first_step(&prog, "F") {
12325 FlowStep::Purge(p) => {
12326 assert_eq!(p.store_name, "logs");
12327 assert_eq!(p.where_expr, "ts < 100");
12328 }
12329 other => panic!("expected Purge, got {other:?}"),
12330 }
12331 }
12332
12333 #[test]
12334 fn mutate_without_a_where_block_is_a_whole_store_op() {
12335 let prog = parse("flow F() -> Unit { mutate accounts }");
12338 match first_step(&prog, "F") {
12339 FlowStep::Mutate(m) => {
12340 assert_eq!(m.store_name, "accounts");
12341 assert_eq!(m.where_expr, "");
12342 }
12343 other => panic!("expected Mutate, got {other:?}"),
12344 }
12345 }
12346}
12347
12348#[cfg(test)]
12351mod fase35o_persist_fields_tests {
12352 use super::*;
12353
12354 fn parse(src: &str) -> Program {
12355 let tokens = crate::lexer::Lexer::new(src, "<test>")
12356 .tokenize()
12357 .expect("lex");
12358 Parser::new(tokens).parse().expect("parse")
12359 }
12360
12361 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
12362 for d in &prog.declarations {
12363 if let Declaration::Flow(f) = d {
12364 if f.name == flow {
12365 return f.body.first().expect("flow has at least one step");
12366 }
12367 }
12368 }
12369 panic!("flow `{flow}` not found");
12370 }
12371
12372 #[test]
12373 fn persist_captures_its_field_block() {
12374 let prog = parse(
12378 "flow F() -> Unit { persist into chat_history { \
12379 session_id: \"${session_id}\" sender: \"user\" \
12380 content: \"${message}\" } }",
12381 );
12382 match first_step(&prog, "F") {
12383 FlowStep::Persist(p) => {
12384 assert_eq!(p.store_name, "chat_history");
12385 assert_eq!(
12386 p.fields,
12387 vec![
12388 ("session_id".to_string(), "${session_id}".to_string()),
12389 ("sender".to_string(), "user".to_string()),
12390 ("content".to_string(), "${message}".to_string()),
12391 ]
12392 );
12393 }
12394 other => panic!("expected Persist, got {other:?}"),
12395 }
12396 }
12397
12398 #[test]
12399 fn persist_without_a_block_keeps_the_user_bindings_fallback() {
12400 let prog = parse("flow F() -> Unit { persist events }");
12403 match first_step(&prog, "F") {
12404 FlowStep::Persist(p) => {
12405 assert_eq!(p.store_name, "events");
12406 assert!(p.fields.is_empty());
12407 }
12408 other => panic!("expected Persist, got {other:?}"),
12409 }
12410 }
12411
12412 #[test]
12413 fn persist_accepts_the_optional_into_connector() {
12414 let with =
12417 parse("flow F() -> Unit { persist into accounts { id: \"1\" } }");
12418 let without =
12419 parse("flow F() -> Unit { persist accounts { id: \"1\" } }");
12420 for prog in [&with, &without] {
12421 match first_step(prog, "F") {
12422 FlowStep::Persist(p) => assert_eq!(p.store_name, "accounts"),
12423 other => panic!("expected Persist, got {other:?}"),
12424 }
12425 }
12426 }
12427
12428 #[test]
12429 fn persist_into_without_a_block_resolves_the_store_name() {
12430 let prog = parse("flow F() -> Unit { persist into events }");
12433 match first_step(&prog, "F") {
12434 FlowStep::Persist(p) => {
12435 assert_eq!(p.store_name, "events");
12436 assert!(p.fields.is_empty());
12437 }
12438 other => panic!("expected Persist, got {other:?}"),
12439 }
12440 }
12441
12442 #[test]
12443 fn persist_fields_lower_into_the_ir() {
12444 let prog = parse(
12447 "flow F() -> Unit { persist into chat { content: \"${msg}\" } }",
12448 );
12449 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
12450 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
12451 match flow.steps.first().expect("one step") {
12452 crate::ir_nodes::IRFlowNode::Persist(p) => {
12453 assert_eq!(p.store_name, "chat");
12454 assert_eq!(
12455 p.fields,
12456 vec![("content".to_string(), "${msg}".to_string())]
12457 );
12458 }
12459 other => panic!("expected IRFlowNode::Persist, got {other:?}"),
12460 }
12461 }
12462}
12463
12464#[cfg(test)]
12467mod fase35p_mutate_fields_tests {
12468 use super::*;
12469
12470 fn parse(src: &str) -> Program {
12471 let tokens = crate::lexer::Lexer::new(src, "<test>")
12472 .tokenize()
12473 .expect("lex");
12474 Parser::new(tokens).parse().expect("parse")
12475 }
12476
12477 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
12478 for d in &prog.declarations {
12479 if let Declaration::Flow(f) = d {
12480 if f.name == flow {
12481 return f.body.first().expect("flow has at least one step");
12482 }
12483 }
12484 }
12485 panic!("flow `{flow}` not found");
12486 }
12487
12488 #[test]
12489 fn mutate_captures_its_set_field_block() {
12490 let prog = parse(
12494 "flow F() -> Unit { mutate accounts { where: \"id = ${id}\" \
12495 balance: \"${new_balance}\" status: \"active\" } }",
12496 );
12497 match first_step(&prog, "F") {
12498 FlowStep::Mutate(m) => {
12499 assert_eq!(m.store_name, "accounts");
12500 assert_eq!(m.where_expr, "id = ${id}");
12501 assert_eq!(
12502 m.fields,
12503 vec![
12504 ("balance".to_string(), "${new_balance}".to_string()),
12505 ("status".to_string(), "active".to_string()),
12506 ]
12507 );
12508 }
12509 other => panic!("expected Mutate, got {other:?}"),
12510 }
12511 }
12512
12513 #[test]
12514 fn mutate_where_only_block_has_no_set_fields() {
12515 let prog =
12518 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
12519 match first_step(&prog, "F") {
12520 FlowStep::Mutate(m) => {
12521 assert_eq!(m.where_expr, "id = 1");
12522 assert!(m.fields.is_empty());
12523 }
12524 other => panic!("expected Mutate, got {other:?}"),
12525 }
12526 }
12527
12528 #[test]
12529 fn mutate_with_no_block_is_a_whole_store_op() {
12530 let prog = parse("flow F() -> Unit { mutate accounts }");
12533 match first_step(&prog, "F") {
12534 FlowStep::Mutate(m) => {
12535 assert_eq!(m.store_name, "accounts");
12536 assert_eq!(m.where_expr, "");
12537 assert!(m.fields.is_empty());
12538 }
12539 other => panic!("expected Mutate, got {other:?}"),
12540 }
12541 }
12542
12543 #[test]
12544 fn mutate_fields_lower_into_the_ir() {
12545 let prog = parse(
12546 "flow F() -> Unit { mutate t { where: \"id = 1\" v: \"${x}\" } }",
12547 );
12548 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
12549 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
12550 match flow.steps.first().expect("one step") {
12551 crate::ir_nodes::IRFlowNode::Mutate(m) => {
12552 assert_eq!(m.where_expr, "id = 1");
12553 assert_eq!(
12554 m.fields,
12555 vec![("v".to_string(), "${x}".to_string())]
12556 );
12557 }
12558 other => panic!("expected IRFlowNode::Mutate, got {other:?}"),
12559 }
12560 }
12561}
12562