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 leading_trivia: Vec<Vec<Trivia>>,
1445 trailing_trivia: Vec<Vec<Trivia>>,
1449 last_let_value_kind: String,
1453 loop_depth: u32,
1458 source: Option<String>,
1464 filename: String,
1465}
1466
1467impl Parser {
1468 pub fn new(raw_tokens: Vec<Token>) -> Self {
1469 let mut effective: Vec<Token> = Vec::with_capacity(raw_tokens.len());
1479 let mut leading: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1480 let mut trailing: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1481
1482 let mut pending_leading: Vec<Trivia> = Vec::new();
1483 let mut last_effective_line: i64 = -1;
1484 for tok in raw_tokens {
1485 if is_comment_token(&tok.ttype) {
1486 let kind = token_to_trivia_kind(&tok.ttype)
1487 .expect("comment token must map to a trivia kind");
1488 let triv = Trivia {
1489 kind,
1490 text: tok.value,
1491 line: tok.line,
1492 column: tok.column,
1493 };
1494 if !effective.is_empty() && (tok.line as i64) == last_effective_line {
1495 trailing.last_mut().unwrap().push(triv);
1496 } else {
1497 pending_leading.push(triv);
1498 }
1499 } else {
1500 last_effective_line = tok.line as i64;
1501 effective.push(tok);
1502 leading.push(std::mem::take(&mut pending_leading));
1503 trailing.push(Vec::new());
1504 }
1505 }
1506
1507 Parser {
1508 tokens: effective,
1509 pos: 0,
1510 leading_trivia: leading,
1511 trailing_trivia: trailing,
1512 last_let_value_kind: "literal".to_string(),
1513 loop_depth: 0,
1514 source: None,
1515 filename: "<source>".to_string(),
1516 }
1517 }
1518
1519 #[must_use]
1531 pub fn with_source(mut self, source: &str, filename: &str) -> Self {
1532 self.source = Some(source.to_string());
1533 self.filename = filename.to_string();
1534 self
1535 }
1536
1537 pub fn parse(&mut self) -> Result<Program, ParseError> {
1540 let mut program = Program {
1541 declarations: Vec::new(),
1542 declaration_trivia: Vec::new(),
1543 loc: Loc { line: 1, column: 1 },
1544 };
1545 while !self.check(TokenType::Eof) {
1546 let start_pos = self.pos;
1552 let mut decl = match self.parse_declaration() {
1553 Ok(d) => d,
1554 Err(e) => return Err(self.attach_source_to_error(e)),
1555 };
1556 let end_pos = self.pos.saturating_sub(1);
1557 let leading = self
1558 .leading_trivia
1559 .get(start_pos)
1560 .cloned()
1561 .unwrap_or_default();
1562 let trailing = self
1563 .trailing_trivia
1564 .get(end_pos)
1565 .cloned()
1566 .unwrap_or_default();
1567 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1573 program.declarations.push(decl);
1574 program
1575 .declaration_trivia
1576 .push(DeclarationTrivia { leading, trailing });
1577 }
1578 crate::voice_desugar::expand(&mut program);
1585 crate::upstream_presets::expand(&mut program);
1586 Ok(program)
1587 }
1588
1589 pub fn parse_with_recovery(&mut self) -> ParseResult {
1622 let mut program = Program {
1623 declarations: Vec::new(),
1624 declaration_trivia: Vec::new(),
1625 loc: Loc { line: 1, column: 1 },
1626 };
1627 let mut errors: Vec<ParseError> = Vec::new();
1628
1629 while !self.check(TokenType::Eof) {
1630 let start_pos = self.pos;
1631 match self.parse_declaration() {
1632 Ok(mut decl) => {
1633 let end_pos = self.pos.saturating_sub(1);
1634 let leading = self
1635 .leading_trivia
1636 .get(start_pos)
1637 .cloned()
1638 .unwrap_or_default();
1639 let trailing = self
1640 .trailing_trivia
1641 .get(end_pos)
1642 .cloned()
1643 .unwrap_or_default();
1644 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1645 program.declarations.push(decl);
1646 program
1647 .declaration_trivia
1648 .push(DeclarationTrivia { leading, trailing });
1649 }
1650 Err(err) => {
1651 errors.push(self.attach_source_to_error(err));
1655 if self.pos == start_pos && !self.check(TokenType::Eof) {
1660 self.advance();
1661 }
1662 self.advance_to_sync_point();
1663 }
1664 }
1665 }
1666
1667 ParseResult { program, errors }
1668 }
1669
1670 fn attach_source_to_error(&self, err: ParseError) -> ParseError {
1675 match &self.source {
1676 Some(src) if err.line >= 1 => err.attach_source(src, &self.filename),
1677 _ => err,
1678 }
1679 }
1680
1681 fn advance_to_sync_point(&mut self) {
1686 let mut depth: i32 = 0;
1687 while !self.check(TokenType::Eof) {
1688 let tt = self.current().ttype.clone();
1689 if is_top_level_decl_kw_for_recovery(&tt) && depth <= 0 {
1693 return;
1694 }
1695 if matches!(tt, TokenType::LBrace) {
1696 depth += 1;
1697 } else if matches!(tt, TokenType::RBrace) {
1698 depth -= 1;
1699 }
1700 self.advance();
1701 }
1702 }
1703
1704 fn current(&self) -> &Token {
1707 if self.pos >= self.tokens.len() {
1708 self.tokens.last().unwrap() } else {
1710 &self.tokens[self.pos]
1711 }
1712 }
1713
1714 fn advance(&mut self) -> &Token {
1715 let idx = self.pos;
1716 if self.pos < self.tokens.len() {
1717 self.pos += 1;
1718 }
1719 &self.tokens[idx]
1720 }
1721
1722 fn check(&self, tt: TokenType) -> bool {
1723 self.current().ttype == tt
1724 }
1725
1726 fn consume(&mut self, expected: TokenType) -> Result<Token, ParseError> {
1727 let tok = self.current().clone();
1728 if tok.ttype != expected {
1729 return Err(ParseError {
1730 message: format!(
1731 "Expected {:?}, found {:?}('{}')",
1732 expected, tok.ttype, tok.value
1733 ),
1734 line: tok.line,
1735 column: tok.column,
1736 ..Default::default()
1737 });
1738 }
1739 self.pos += 1;
1740 Ok(tok)
1741 }
1742
1743 fn error(&self, message: &str) -> ParseError {
1745 let tok = self.current();
1746 ParseError { message: message.to_string(), line: tok.line, column: tok.column, ..Default::default() }
1747 }
1748
1749 fn consume_any_ident_or_kw(&mut self) -> Result<Token, ParseError> {
1751 let tok = self.current().clone();
1752 match tok.ttype {
1753 TokenType::Identifier
1754 | TokenType::Bool
1755 | TokenType::StringLit
1756 | TokenType::Integer
1757 | TokenType::Float => {
1758 self.pos += 1;
1759 Ok(tok)
1760 }
1761 _ => {
1762 if !tok.value.is_empty()
1764 && tok.value.chars().all(|c| c.is_alphanumeric() || c == '_')
1765 && tok.ttype != TokenType::Eof
1766 {
1767 self.pos += 1;
1768 Ok(tok)
1769 } else {
1770 Err(ParseError {
1771 message: format!(
1772 "Expected identifier or keyword value, found {:?}('{}')",
1773 tok.ttype, tok.value
1774 ),
1775 line: tok.line,
1776 column: tok.column,
1777 ..Default::default()
1778 })
1779 }
1780 }
1781 }
1782 }
1783
1784 fn consume_number(&mut self) -> Result<f64, ParseError> {
1785 let tok = self.current().clone();
1786 match tok.ttype {
1787 TokenType::Float | TokenType::Integer => {
1788 self.pos += 1;
1789 tok.value.parse::<f64>().map_err(|_| ParseError {
1790 message: format!("Invalid number '{}'", tok.value),
1791 line: tok.line,
1792 column: tok.column,
1793 ..Default::default()
1794 })
1795 }
1796 _ => Err(ParseError {
1797 message: format!("Expected number, found {:?}('{}')", tok.ttype, tok.value),
1798 line: tok.line,
1799 column: tok.column,
1800 ..Default::default()
1801 }),
1802 }
1803 }
1804
1805 fn parse_bool(&mut self) -> Result<bool, ParseError> {
1806 let tok = self.consume(TokenType::Bool)?;
1807 Ok(tok.value == "true")
1808 }
1809
1810 fn loc_of(&self, tok: &Token) -> Loc {
1811 Loc {
1812 line: tok.line,
1813 column: tok.column,
1814 }
1815 }
1816
1817 fn check_comparison(&self) -> bool {
1818 matches!(
1819 self.current().ttype,
1820 TokenType::Lt
1821 | TokenType::Gt
1822 | TokenType::Lte
1823 | TokenType::Gte
1824 | TokenType::Eq
1825 | TokenType::Neq
1826 )
1827 }
1828
1829 fn check_run_modifier(&self) -> bool {
1830 matches!(
1831 self.current().ttype,
1832 TokenType::As
1833 | TokenType::Within
1834 | TokenType::ConstrainedBy
1835 | TokenType::OnFailure
1836 | TokenType::OutputTo
1837 | TokenType::Effort
1838 )
1839 }
1840
1841 fn parse_string_list(&mut self) -> Result<Vec<String>, ParseError> {
1844 self.consume(TokenType::LBracket)?;
1845 let mut items = Vec::new();
1846 items.push(self.consume(TokenType::StringLit)?.value);
1847 while self.check(TokenType::Comma) {
1848 self.advance();
1849 items.push(self.consume(TokenType::StringLit)?.value);
1850 }
1851 self.consume(TokenType::RBracket)?;
1852 Ok(items)
1853 }
1854
1855 fn parse_bracketed_strings(&mut self) -> Result<Vec<String>, ParseError> {
1864 self.consume(TokenType::LBracket)?;
1865 let mut items = Vec::new();
1866 if !self.check(TokenType::RBracket) {
1867 items.push(self.consume(TokenType::StringLit)?.value);
1868 while self.check(TokenType::Comma) {
1869 self.advance();
1870 if self.check(TokenType::RBracket) {
1871 break; }
1873 items.push(self.consume(TokenType::StringLit)?.value);
1874 }
1875 }
1876 self.consume(TokenType::RBracket)?;
1877 Ok(items)
1878 }
1879
1880 fn parse_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1881 let mut names = Vec::new();
1882 names.push(self.consume(TokenType::Identifier)?.value);
1883 while self.check(TokenType::Comma) {
1884 self.advance();
1885 names.push(self.consume(TokenType::Identifier)?.value);
1886 }
1887 Ok(names)
1888 }
1889
1890 fn parse_bracketed_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1891 self.consume(TokenType::LBracket)?;
1892 let items = self.parse_extended_identifier_list()?;
1893 self.consume(TokenType::RBracket)?;
1894 Ok(items)
1895 }
1896
1897 fn parse_extended_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1898 let mut items = Vec::new();
1899 items.push(self.consume_any_ident_or_kw()?.value);
1900 while self.check(TokenType::Comma) {
1901 self.advance();
1902 items.push(self.consume_any_ident_or_kw()?.value);
1903 }
1904 Ok(items)
1905 }
1906
1907 fn parse_dotted_identifier(&mut self) -> Result<String, ParseError> {
1908 let mut parts = vec![self.consume_any_ident_or_kw()?.value];
1909 while self.check(TokenType::Dot) {
1910 self.advance();
1911 parts.push(self.consume_any_ident_or_kw()?.value);
1912 }
1913 Ok(parts.join("."))
1914 }
1915
1916 fn parse_expression_string(&mut self) -> Result<String, ParseError> {
1917 if self.check(TokenType::LBracket) {
1918 let items = self.parse_bracketed_dot_identifiers()?;
1919 return Ok(format!("[{}]", items.join(", ")));
1920 }
1921 self.parse_dotted_identifier()
1922 }
1923
1924 fn parse_bracketed_dot_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1925 self.consume(TokenType::LBracket)?;
1926 let mut items = vec![self.parse_dotted_identifier()?];
1927 while self.check(TokenType::Comma) {
1928 self.advance();
1929 items.push(self.parse_dotted_identifier()?);
1930 }
1931 self.consume(TokenType::RBracket)?;
1932 Ok(items)
1933 }
1934
1935 fn parse_argument_list(&mut self) -> Result<Vec<String>, ParseError> {
1936 let mut args = Vec::new();
1937 while !self.check(TokenType::RParen) {
1938 let tok = self.current().clone();
1939 match tok.ttype {
1940 TokenType::StringLit | TokenType::Integer | TokenType::Float => {
1941 self.advance();
1942 args.push(tok.value);
1943 }
1944 TokenType::Identifier => {
1945 self.advance();
1946 let mut val = tok.value;
1947 if self.check(TokenType::Dot) {
1948 self.advance();
1949 val.push('.');
1950 val.push_str(&self.consume_any_ident_or_kw()?.value);
1951 }
1952 args.push(val);
1953 }
1954 _ => {
1955 self.advance();
1956 let key = tok.value;
1957 if self.check(TokenType::Colon) {
1958 self.advance();
1959 let v = self.advance().value.clone();
1960 args.push(format!("{key}:{v}"));
1961 } else {
1962 args.push(key);
1963 }
1964 }
1965 }
1966 if self.check(TokenType::Comma) {
1967 self.advance();
1968 }
1969 }
1970 Ok(args)
1971 }
1972
1973 fn skip_value(&mut self) {
1975 match self.current().ttype {
1976 TokenType::LBracket => {
1977 self.advance();
1978 let mut depth = 1u32;
1979 while depth > 0 && !self.check(TokenType::Eof) {
1980 if self.check(TokenType::LBracket) {
1981 depth += 1;
1982 } else if self.check(TokenType::RBracket) {
1983 depth -= 1;
1984 }
1985 self.advance();
1986 }
1987 }
1988 TokenType::LBrace => {
1989 self.advance();
1990 let mut depth = 1u32;
1991 while depth > 0 && !self.check(TokenType::Eof) {
1992 if self.check(TokenType::LBrace) {
1993 depth += 1;
1994 } else if self.check(TokenType::RBrace) {
1995 depth -= 1;
1996 }
1997 self.advance();
1998 }
1999 }
2000 TokenType::Lt => {
2001 self.advance();
2003 let mut depth = 1u32;
2004 while depth > 0 && !self.check(TokenType::Eof) {
2005 if self.check(TokenType::Lt) {
2006 depth += 1;
2007 } else if self.check(TokenType::Gt) {
2008 depth -= 1;
2009 }
2010 self.advance();
2011 }
2012 }
2013 _ => {
2014 self.advance();
2015 while self.check(TokenType::Dot) {
2016 self.advance();
2017 self.advance();
2018 }
2019 }
2020 }
2021 }
2022
2023 fn skip_braced_block(&mut self) -> Result<(), ParseError> {
2025 self.consume(TokenType::LBrace)?;
2026 let mut depth = 1u32;
2027 while depth > 0 {
2028 if self.check(TokenType::Eof) {
2029 let tok = self.current();
2030 return Err(ParseError {
2031 message: "Unterminated block — expected '}'".to_string(),
2032 line: tok.line,
2033 column: tok.column,
2034 ..Default::default()
2035 });
2036 }
2037 if self.check(TokenType::LBrace) {
2038 depth += 1;
2039 } else if self.check(TokenType::RBrace) {
2040 depth -= 1;
2041 }
2042 self.advance();
2043 }
2044 Ok(())
2045 }
2046
2047 fn at_declaration_start(&self) -> bool {
2048 is_declaration_keyword(&self.current().ttype) || self.check(TokenType::Eof)
2049 }
2050
2051 fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
2054 let tok = self.current().clone();
2055
2056 if tok.ttype == TokenType::Budget && self.peek_is_identifier() {
2064 return self.parse_top_level_budget().map(Declaration::Budget);
2065 }
2066
2067 match tok.ttype {
2068 TokenType::Import => self.parse_import().map(Declaration::Import),
2069 TokenType::Persona => self.parse_persona().map(Declaration::Persona),
2070 TokenType::Context => self.parse_context().map(Declaration::Context),
2071 TokenType::Anchor => self.parse_anchor().map(Declaration::Anchor),
2072 TokenType::Memory => self.parse_memory().map(Declaration::Memory),
2073 TokenType::Tool => self.parse_tool().map(Declaration::Tool),
2074 TokenType::Type => self.parse_type_def().map(Declaration::Type),
2075 TokenType::Flow => self.parse_flow().map(Declaration::Flow),
2076 TokenType::Intent => self.parse_intent().map(Declaration::Intent),
2077 TokenType::Run => self.parse_run().map(Declaration::Run),
2078 TokenType::Let => self.parse_let().map(Declaration::Let),
2079 TokenType::Know | TokenType::Believe | TokenType::Speculate | TokenType::Doubt => {
2080 self.parse_epistemic_block().map(Declaration::Epistemic)
2081 }
2082 TokenType::Lambda => self.parse_lambda_data().map(Declaration::LambdaData),
2083
2084 TokenType::Agent => self.parse_agent().map(Declaration::Agent),
2086 TokenType::Shield => self.parse_shield().map(Declaration::Shield),
2087 TokenType::Window => self.parse_window().map(Declaration::Window),
2089 TokenType::Pix => self.parse_pix().map(Declaration::Pix),
2090 TokenType::Ledger => self.parse_ledger().map(Declaration::Ledger),
2091 TokenType::Psyche => self.parse_psyche().map(Declaration::Psyche),
2092 TokenType::Corpus => self.parse_corpus().map(Declaration::Corpus),
2093 TokenType::Dataspace => self.parse_dataspace().map(Declaration::Dataspace),
2094 TokenType::Ots => self.parse_ots().map(Declaration::Ots),
2095 TokenType::Mandate => self.parse_mandate().map(Declaration::Mandate),
2096 TokenType::Compute => self.parse_compute().map(Declaration::Compute),
2097 TokenType::Daemon => self.parse_daemon().map(Declaration::Daemon),
2098 TokenType::Extension => self.parse_extension().map(Declaration::Extension),
2099 TokenType::AxonStore => self.parse_axonstore().map(Declaration::AxonStore),
2100 TokenType::AxonEndpoint => self.parse_axonendpoint().map(Declaration::AxonEndpoint),
2101
2102 TokenType::Resource => self.parse_resource().map(Declaration::Resource),
2104 TokenType::Fabric => self.parse_fabric().map(Declaration::Fabric),
2105 TokenType::Manifest => self.parse_manifest().map(Declaration::Manifest),
2106 TokenType::Observe => self.parse_observe().map(Declaration::Observe),
2107
2108 TokenType::Reconcile => self.parse_reconcile().map(Declaration::Reconcile),
2110 TokenType::Lease => self.parse_lease().map(Declaration::Lease),
2111 TokenType::Ensemble => self.parse_ensemble().map(Declaration::Ensemble),
2112
2113 TokenType::Session => self.parse_session_definition().map(Declaration::Session),
2115 TokenType::Topology => self.parse_topology().map(Declaration::Topology),
2116
2117 TokenType::Socket => self.parse_socket().map(Declaration::Socket),
2119
2120 TokenType::Upstream => self.parse_upstream().map(Declaration::Upstream),
2122
2123 TokenType::Voice => self.parse_voice().map(Declaration::Voice),
2125
2126 TokenType::Cors => self.parse_cors().map(Declaration::Cors),
2128
2129 TokenType::Cache => self.parse_cache().map(Declaration::Cache),
2131 TokenType::Document => self.parse_document().map(Declaration::Document),
2132
2133 TokenType::Deliver => self.parse_deliver().map(Declaration::Deliver),
2135 TokenType::Notify => self.parse_notify().map(Declaration::Notify),
2136
2137 TokenType::Savant => self.parse_savant().map(Declaration::Savant),
2139
2140 TokenType::Synth => self.parse_synth().map(Declaration::Synth),
2142
2143 TokenType::Scope => self.parse_scope().map(Declaration::Scope),
2145
2146 TokenType::Credential => self.parse_credential().map(Declaration::Credential),
2148
2149 TokenType::Observable => self.parse_observable().map(Declaration::Observable),
2151
2152 TokenType::Witness => self.parse_witness().map(Declaration::Witness),
2154
2155 TokenType::Immune => self.parse_immune().map(Declaration::Immune),
2157 TokenType::Reflex => self.parse_reflex().map(Declaration::Reflex),
2158 TokenType::Heal => self.parse_heal().map(Declaration::Heal),
2159
2160 TokenType::Component => self.parse_component().map(Declaration::Component),
2162 TokenType::View => self.parse_view().map(Declaration::View),
2163
2164 TokenType::Channel => self.parse_channel().map(Declaration::Channel),
2166
2167 TokenType::Ingest
2170 | TokenType::Persist
2171 | TokenType::Retrieve
2172 | TokenType::Mutate
2173 | TokenType::Purge
2174 | TokenType::Transact => self.parse_generic_declaration(),
2175
2176 TokenType::Mcp => self.parse_generic_declaration(),
2178
2179 _ => {
2180 let hint = crate::smart_suggest::suggest_for(
2184 &tok.value,
2185 crate::smart_suggest::TOP_LEVEL_KEYWORD_NAMES,
2186 );
2187 let base = format!(
2188 "Unexpected token at top level: '{}' — expected declaration \
2189 (persona, context, anchor, flow, run, ...)",
2190 tok.value
2191 );
2192 let message = if hint.is_empty() {
2193 base
2194 } else {
2195 format!("{base}. {hint}")
2196 };
2197 Err(ParseError {
2198 message,
2199 line: tok.line,
2200 column: tok.column,
2201 ..Default::default()
2202 })
2203 }
2204 }
2205 }
2206
2207 fn parse_import(&mut self) -> Result<ImportNode, ParseError> {
2210 let tok = self.consume(TokenType::Import)?;
2211 let loc = self.loc_of(&tok);
2212
2213 let mut path_parts = Vec::new();
2214
2215 if self.check(TokenType::At) {
2217 self.advance();
2218 let first = self.consume(TokenType::Identifier)?;
2219 path_parts.push(format!("@{}", first.value));
2220 } else {
2221 let first = self.consume(TokenType::Identifier)?;
2222 path_parts.push(first.value);
2223 }
2224
2225 while self.check(TokenType::Dot) {
2226 self.advance();
2227 if self.check(TokenType::LBrace) {
2228 break;
2229 }
2230 let part = self.consume(TokenType::Identifier)?;
2231 path_parts.push(part.value);
2232 }
2233
2234 let mut names = Vec::new();
2235 if self.check(TokenType::LBrace) {
2236 self.advance();
2237 names = self.parse_identifier_list()?;
2238 self.consume(TokenType::RBrace)?;
2239 }
2240
2241 let mut allow_downgrade = false;
2250 if self.check(TokenType::At) {
2251 let at_tok = self.current().clone();
2252 self.advance();
2253 let ident = self.consume(TokenType::Identifier)?;
2254 if ident.value == "allow_downgrade" {
2255 allow_downgrade = true;
2256 } else {
2257 return Err(ParseError {
2258 message: format!(
2259 "unknown import annotation '@{}' — the only import annotation is \
2260 `@allow_downgrade` (the §115 epistemic-downgrade acknowledgment).",
2261 ident.value
2262 ),
2263 line: at_tok.line,
2264 column: at_tok.column,
2265 ..Default::default()
2266 });
2267 }
2268 }
2269
2270 let next_is_apx = self
2285 .tokens
2286 .get(self.pos + 1)
2287 .map(|t| t.value == "apx")
2288 .unwrap_or(false);
2289 if self.current().value == "with" && next_is_apx {
2290 let tok = self.current().clone();
2291 return Err(ParseError {
2292 message: "`import … with apx { … }` is RETRACTED (§111). The apx policy block was \
2293 parsed and silently DISCARDED — it never reached the IR, and no epistemic \
2294 dependency manager exists: no MEC/PCC verification, no EPR ranking, no \
2295 quarantine, no compliance gate. Declaring it verified nothing while \
2296 implying your supply chain was checked. Remove the `with apx { … }` \
2297 clause; the plain `import` resolves through the §115 Epistemic Module \
2298 System."
2299 .to_string(),
2300 line: tok.line,
2301 column: tok.column,
2302 ..Default::default()
2303 });
2304 }
2305
2306 Ok(ImportNode {
2307 module_path: path_parts,
2308 names,
2309 allow_downgrade,
2310 loc,
2311 leading_trivia: Vec::new(),
2312 trailing_trivia: Vec::new(),
2313 })
2314 }
2315
2316 fn parse_persona(&mut self) -> Result<PersonaDefinition, ParseError> {
2319 let tok = self.consume(TokenType::Persona)?;
2320 let loc = self.loc_of(&tok);
2321 let name = self.consume(TokenType::Identifier)?.value;
2322 self.consume(TokenType::LBrace)?;
2323
2324 let mut node = PersonaDefinition {
2325 name,
2326 domain: Vec::new(),
2327 tone: String::new(),
2328 confidence_threshold: None,
2329 cite_sources: None,
2330 refuse_if: Vec::new(),
2331 language: String::new(),
2332 description: String::new(),
2333 loc,
2334 leading_trivia: Vec::new(),
2335 trailing_trivia: Vec::new(),
2336 };
2337
2338 while !self.check(TokenType::RBrace) {
2339 let field_name = self.current().value.clone();
2340 self.advance();
2341 self.consume(TokenType::Colon)?;
2342
2343 match field_name.as_str() {
2344 "domain" => node.domain = self.parse_string_list()?,
2345 "tone" => node.tone = self.consume_any_ident_or_kw()?.value,
2346 "confidence_threshold" => node.confidence_threshold = Some(self.consume_number()?),
2347 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2348 "refuse_if" => node.refuse_if = self.parse_bracketed_identifiers()?,
2349 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2350 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2351 _ => self.skip_value(),
2352 }
2353 }
2354 self.consume(TokenType::RBrace)?;
2355 Ok(node)
2356 }
2357
2358 fn parse_context(&mut self) -> Result<ContextDefinition, ParseError> {
2361 let tok = self.consume(TokenType::Context)?;
2362 let loc = self.loc_of(&tok);
2363 let name = self.consume(TokenType::Identifier)?.value;
2364 self.consume(TokenType::LBrace)?;
2365
2366 let mut node = ContextDefinition {
2367 name,
2368 memory_scope: String::new(),
2369 language: String::new(),
2370 depth: String::new(),
2371 max_tokens: None,
2372 temperature: None,
2373 cite_sources: None,
2374 now_tz: None,
2375 loc,
2376 leading_trivia: Vec::new(),
2377 trailing_trivia: Vec::new(),
2378 };
2379
2380 while !self.check(TokenType::RBrace) {
2381 let field_name = self.current().value.clone();
2382 self.advance();
2383 self.consume(TokenType::Colon)?;
2384
2385 match field_name.as_str() {
2386 "memory" => node.memory_scope = self.consume_any_ident_or_kw()?.value,
2387 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2388 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
2389 "now" => node.now_tz = Some(self.consume(TokenType::StringLit)?.value),
2391 "max_tokens" => {
2392 node.max_tokens = Some(
2393 self.consume(TokenType::Integer)?
2394 .value
2395 .parse::<i64>()
2396 .unwrap_or(0),
2397 )
2398 }
2399 "temperature" => node.temperature = Some(self.consume_number()?),
2400 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2401 _ => self.skip_value(),
2402 }
2403 }
2404 self.consume(TokenType::RBrace)?;
2405 Ok(node)
2406 }
2407
2408 fn parse_anchor(&mut self) -> Result<AnchorConstraint, ParseError> {
2411 let tok = self.consume(TokenType::Anchor)?;
2412 let loc = self.loc_of(&tok);
2413 let name = self.consume(TokenType::Identifier)?.value;
2414 self.consume(TokenType::LBrace)?;
2415
2416 let mut node = AnchorConstraint {
2417 name,
2418 require: String::new(),
2419 reject: Vec::new(),
2420 enforce: String::new(),
2421 description: String::new(),
2422 confidence_floor: None,
2423 unknown_response: String::new(),
2424 on_violation: String::new(),
2425 on_violation_target: String::new(),
2426 loc,
2427 leading_trivia: Vec::new(),
2428 trailing_trivia: Vec::new(),
2429 };
2430
2431 while !self.check(TokenType::RBrace) {
2432 let field_name = self.current().value.clone();
2433 self.advance();
2434 self.consume(TokenType::Colon)?;
2435
2436 match field_name.as_str() {
2437 "require" => node.require = self.consume_any_ident_or_kw()?.value,
2438 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2439 "reject" => node.reject = self.parse_bracketed_identifiers()?,
2440 "enforce" => node.enforce = self.consume_any_ident_or_kw()?.value,
2441 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
2442 "unknown_response" => {
2443 node.unknown_response = self.consume(TokenType::StringLit)?.value
2444 }
2445 "on_violation" => {
2446 let action = self.consume_any_ident_or_kw()?.value;
2448 node.on_violation = action.clone();
2449 if action == "raise" || action == "fallback" {
2450 node.on_violation_target = self.consume_any_ident_or_kw()?.value;
2451 }
2452 }
2453 _ => self.skip_value(),
2454 }
2455 }
2456 self.consume(TokenType::RBrace)?;
2457 Ok(node)
2458 }
2459
2460 fn parse_memory(&mut self) -> Result<MemoryDefinition, ParseError> {
2463 let tok = self.consume(TokenType::Memory)?;
2464 let loc = self.loc_of(&tok);
2465 let name = self.consume(TokenType::Identifier)?.value;
2466 self.consume(TokenType::LBrace)?;
2467
2468 let mut node = MemoryDefinition {
2469 name,
2470 store: String::new(),
2471 backend: String::new(),
2472 retrieval: String::new(),
2473 decay: String::new(),
2474 loc,
2475 leading_trivia: Vec::new(),
2476 trailing_trivia: Vec::new(),
2477 };
2478
2479 while !self.check(TokenType::RBrace) {
2480 let field_name = self.current().value.clone();
2481 self.advance();
2482 self.consume(TokenType::Colon)?;
2483
2484 match field_name.as_str() {
2485 "store" => node.store = self.consume_any_ident_or_kw()?.value,
2486 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
2487 "retrieval" => node.retrieval = self.consume_any_ident_or_kw()?.value,
2488 "decay" => {
2489 if self.check(TokenType::Duration) {
2490 node.decay = self.advance().value.clone();
2491 } else {
2492 node.decay = self.consume_any_ident_or_kw()?.value;
2493 }
2494 }
2495 _ => self.skip_value(),
2496 }
2497 }
2498 self.consume(TokenType::RBrace)?;
2499 Ok(node)
2500 }
2501
2502 fn parse_tool(&mut self) -> Result<ToolDefinition, ParseError> {
2505 let tok = self.consume(TokenType::Tool)?;
2506 let loc = self.loc_of(&tok);
2507 let name = self.consume(TokenType::Identifier)?.value;
2508 self.consume(TokenType::LBrace)?;
2509
2510 let mut node = ToolDefinition {
2511 name,
2512 provider: String::new(),
2513 max_results: None,
2514 filter_expr: String::new(),
2515 timeout: String::new(),
2516 runtime: String::new(),
2517 resource_ref: String::new(),
2518 sandbox: None,
2519 effects: None,
2520 parameters: Vec::new(),
2521 output_type: None,
2522 requires: Vec::new(),
2523 secret: String::new(),
2524 secret_partition: String::new(),
2525 target: None,
2526 risk: None,
2527 argv: Vec::new(),
2528 cache: String::new(),
2529 scrape: None,
2530 loc,
2531 leading_trivia: Vec::new(),
2532 trailing_trivia: Vec::new(),
2533 };
2534
2535 let mut unknown_fields: Vec<(String, u32, u32)> = Vec::new();
2542
2543 while !self.check(TokenType::RBrace) {
2544 let field_tok = self.current().clone();
2545 let field_name = field_tok.value.clone();
2546 self.advance();
2547 self.consume(TokenType::Colon)?;
2548
2549 match field_name.as_str() {
2550 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
2551 "max_results" => {
2552 node.max_results = Some(
2553 self.consume(TokenType::Integer)?
2554 .value
2555 .parse::<i64>()
2556 .unwrap_or(0),
2557 )
2558 }
2559 "filter" => node.filter_expr = self.parse_filter_expression()?,
2560 "timeout" => node.timeout = self.consume(TokenType::Duration)?.value,
2561 "runtime" => node.runtime = self.consume_any_ident_or_kw()?.value,
2562 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
2566 "sandbox" => node.sandbox = Some(self.parse_bool()?),
2567 "effects" => node.effects = Some(self.parse_effect_row()?),
2568 "parameters" => node.parameters = self.parse_tool_param_schema()?,
2570 "output_type" => node.output_type = Some(self.parse_output_type_string()?),
2571 "requires" => {
2577 let bracket_tok = self.current().clone();
2578 let items = self.parse_bracketed_dot_identifiers()?;
2579 for slug in &items {
2580 if !is_valid_capability_slug(slug) {
2581 return Err(ParseError {
2582 message: format!(
2583 "Invalid capability slug '{slug}' in tool '{}' \
2584 `requires:`. Scope slugs must match \
2585 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — the same \
2586 grammar as `credential.grants`. Examples: \
2587 `w_organization_social`, `video.publish`.",
2588 node.name
2589 ),
2590 line: bracket_tok.line,
2591 column: bracket_tok.column,
2592 ..Default::default()
2593 });
2594 }
2595 }
2596 node.requires = items;
2597 }
2598 "secret" => node.secret = self.parse_dotted_identifier()?,
2602 "secret_partition" => {
2608 node.secret_partition = self.consume_any_ident_or_kw()?.value
2609 }
2610 "target" => node.target = Some(self.consume_any_ident_or_kw()?.value),
2612 "risk" => node.risk = Some(self.consume_any_ident_or_kw()?.value),
2613 "argv" => node.argv = self.parse_bracketed_strings()?,
2617 "cache" => node.cache = self.consume_any_ident_or_kw()?.value,
2620 "scrape" => node.scrape = Some(self.parse_scrape_spec()?),
2623 _ => {
2624 unknown_fields.push((field_name, field_tok.line, field_tok.column));
2625 self.skip_value();
2626 }
2627 }
2628 }
2629 self.consume(TokenType::RBrace)?;
2630
2631 if node.target.is_some() || node.scrape.is_some() {
2639 if let Some((field_name, line, column)) = unknown_fields.into_iter().next() {
2640 let (surface, valid) = if node.target.is_some() {
2641 (
2642 "technician tool (§Fase 84 D84.13)",
2643 "provider, parameters, output_type, timeout, effects, target, risk, argv",
2644 )
2645 } else {
2646 (
2647 "web-acquisition tool (§Fase 98 D98.2)",
2648 "provider, parameters, output_type, timeout, effects, secret, \
2649 secret_partition, cache, scrape",
2650 )
2651 };
2652 return Err(ParseError {
2653 message: format!(
2654 "unknown field `{field_name}` in {surface} `{}` — this tool uses \
2655 strict field checking; valid fields: {valid}",
2656 node.name
2657 ),
2658 line,
2659 column,
2660 ..Default::default()
2661 });
2662 }
2663 }
2664 Ok(node)
2665 }
2666
2667 fn parse_scrape_spec(&mut self) -> Result<crate::ast::ScrapeSpec, ParseError> {
2672 let open = self.consume(TokenType::LBrace)?;
2673 let loc = self.loc_of(&open);
2674 let mut spec = crate::ast::ScrapeSpec {
2675 loc,
2676 ..Default::default()
2677 };
2678 while !self.check(TokenType::RBrace) {
2679 let field_tok = self.current().clone();
2680 let field_name = field_tok.value.clone();
2681 self.advance();
2682 self.consume(TokenType::Colon)?;
2683 match field_name.as_str() {
2684 "engine" => spec.engine = Some(self.consume_any_ident_or_kw()?.value),
2685 "impersonate" => spec.impersonate = Some(self.consume_any_ident_or_kw()?.value),
2686 "render_wait" => spec.render_wait = Some(self.consume(TokenType::Duration)?.value),
2687 "proxy" => spec.proxy = self.parse_dotted_identifier()?,
2688 "respect_robots" => spec.respect_robots = Some(self.parse_bool()?),
2689 "extract" => spec.extract = self.parse_bracketed_strings()?,
2690 "adaptive" => spec.adaptive = Some(self.parse_bool()?),
2691 "similarity_floor" => spec.similarity_floor = self.parse_optional_float(),
2692 "follow" => spec.follow = self.consume(TokenType::StringLit)?.value,
2693 "max_depth" => {
2694 spec.max_depth =
2695 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2696 }
2697 "max_pages" => {
2698 spec.max_pages =
2699 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2700 }
2701 "concurrency" => {
2702 spec.concurrency =
2703 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2704 }
2705 "politeness" => spec.politeness = self.consume_any_ident_or_kw()?.value,
2706 "checkpoint" => spec.checkpoint = self.consume_any_ident_or_kw()?.value,
2707 other => {
2708 return Err(self.error(&format!(
2709 "unknown scrape field `{other}` — the `scrape: {{ … }}` block is a \
2710 closed catalog (§Fase 98 D98.2); valid fields: engine, impersonate, \
2711 render_wait, proxy, respect_robots, extract, adaptive, \
2712 similarity_floor, follow, max_depth, max_pages, concurrency, \
2713 politeness, checkpoint"
2714 )));
2715 }
2716 }
2717 }
2718 self.consume(TokenType::RBrace)?;
2719 Ok(spec)
2720 }
2721
2722 fn parse_tool_param_schema(&mut self) -> Result<Vec<Parameter>, ParseError> {
2728 self.consume(TokenType::LBrace)?;
2729 let mut params = Vec::new();
2730 while !self.check(TokenType::RBrace) {
2731 let name = self.consume_any_ident_or_kw()?;
2735 let ploc = self.loc_of(&name);
2736 self.consume(TokenType::Colon)?;
2737 let type_expr = self.parse_type_expr()?;
2738 params.push(Parameter {
2739 name: name.value,
2740 type_expr,
2741 loc: ploc,
2742 });
2743 if self.check(TokenType::Comma) {
2744 self.advance();
2745 } else {
2746 break;
2747 }
2748 }
2749 self.consume(TokenType::RBrace)?;
2750 Ok(params)
2751 }
2752
2753 fn parse_filter_expression(&mut self) -> Result<String, ParseError> {
2754 let name = self.consume_any_ident_or_kw()?.value;
2755 if self.check(TokenType::LParen) {
2756 self.advance();
2757 let mut parts = vec![name, "(".to_string()];
2758 while !self.check(TokenType::RParen) {
2759 parts.push(self.advance().value.clone());
2760 }
2761 self.consume(TokenType::RParen)?;
2762 parts.push(")".to_string());
2763 Ok(parts.join(""))
2764 } else {
2765 Ok(name)
2766 }
2767 }
2768
2769 fn parse_effect_row(&mut self) -> Result<EffectRow, ParseError> {
2770 let tok = self.consume(TokenType::Lt)?;
2771 let loc = self.loc_of(&tok);
2772 let mut effects = Vec::new();
2773 let mut epistemic_level = String::new();
2774
2775 while !self.check(TokenType::Gt) {
2776 let name = self.consume_any_ident_or_kw()?.value;
2777 if self.check(TokenType::Colon) {
2778 self.advance();
2779 let level = self.parse_qualifier_value()?;
2796 if name == "epistemic" {
2797 epistemic_level = level;
2798 } else {
2799 effects.push(format!("{name}:{level}"));
2800 }
2801 } else {
2802 effects.push(name);
2803 }
2804 if self.check(TokenType::Comma) {
2805 self.advance();
2806 }
2807 }
2808 self.consume(TokenType::Gt)?;
2809
2810 Ok(EffectRow {
2811 effects,
2812 epistemic_level,
2813 loc,
2814 })
2815 }
2816
2817 fn parse_qualifier_value(&mut self) -> Result<String, ParseError> {
2826 let mut buf = self.consume_dotted_slug_segment()?;
2827 loop {
2828 let sep = if self.check(TokenType::Dot) {
2829 '.'
2830 } else if self.check(TokenType::Colon) {
2831 ':'
2832 } else {
2833 break;
2834 };
2835 self.advance();
2836 let part = self.consume_dotted_slug_segment()?;
2837 buf.push(sep);
2838 buf.push_str(&part);
2839 }
2840 Ok(buf)
2841 }
2842
2843 fn consume_dotted_slug_segment(&mut self) -> Result<String, ParseError> {
2855 let first = self.consume_any_ident_or_kw()?;
2856 let mut buf = first.value.clone();
2857 let mut next_line = first.line;
2858 let mut next_col = first.column + first.value.chars().count() as u32;
2859 loop {
2860 let cur = self.current();
2861 let is_segment_token = matches!(cur.ttype, TokenType::Identifier | TokenType::Integer,);
2862 if !is_segment_token {
2863 break;
2864 }
2865 if cur.line != next_line || cur.column != next_col {
2866 break;
2867 }
2868 buf.push_str(&cur.value);
2869 next_col = cur.column + cur.value.chars().count() as u32;
2870 next_line = cur.line;
2871 self.pos += 1;
2872 }
2873 Ok(buf)
2874 }
2875
2876 fn parse_type_def(&mut self) -> Result<TypeDefinition, ParseError> {
2879 let tok = self.consume(TokenType::Type)?;
2880 let loc = self.loc_of(&tok);
2881 let name = self.consume(TokenType::Identifier)?.value;
2882
2883 let mut node = TypeDefinition {
2884 name,
2885 fields: Vec::new(),
2886 range_constraint: None,
2887 where_clause: None,
2888 compliance: Vec::new(),
2889 loc: loc.clone(),
2890 leading_trivia: Vec::new(),
2891 trailing_trivia: Vec::new(),
2892 };
2893
2894 if self.check(TokenType::LParen) {
2896 self.advance();
2897 let min_val = self.consume_number()?;
2898 self.consume(TokenType::DotDot)?;
2899 let max_val = self.consume_number()?;
2900 self.consume(TokenType::RParen)?;
2901 node.range_constraint = Some(RangeConstraint {
2902 min_value: min_val,
2903 max_value: max_val,
2904 loc: loc.clone(),
2905 });
2906 }
2907
2908 if self.check(TokenType::Where) {
2910 self.advance();
2911 let mut expr_parts = Vec::new();
2912 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
2913 if self.check(TokenType::Eof) {
2914 break;
2915 }
2916 expr_parts.push(self.advance().value.clone());
2917 }
2918 node.where_clause = Some(WhereClause {
2919 expression: expr_parts.join(" "),
2920 loc: loc.clone(),
2921 });
2922 }
2923
2924 if self.check(TokenType::Identifier) && self.current().value == "compliance" {
2927 self.advance();
2928 node.compliance = self.parse_bracketed_identifiers()?;
2929 }
2930
2931 if self.check(TokenType::LBrace) {
2933 self.advance();
2934 while !self.check(TokenType::RBrace) {
2935 let field_name = self.consume(TokenType::Identifier)?;
2936 let field_loc = self.loc_of(&field_name);
2937 self.consume(TokenType::Colon)?;
2938 let type_expr = self.parse_type_expr()?;
2939 node.fields.push(TypeField {
2940 name: field_name.value,
2941 type_expr,
2942 loc: field_loc,
2943 });
2944 if self.check(TokenType::Comma) {
2945 self.advance();
2946 }
2947 }
2948 self.consume(TokenType::RBrace)?;
2949 }
2950
2951 Ok(node)
2952 }
2953
2954 fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
2955 let name_tok = self.consume(TokenType::Identifier)?;
2956 let loc = self.loc_of(&name_tok);
2957 let mut generic_param = String::new();
2958 let mut optional = false;
2959
2960 if self.check(TokenType::Lt) {
2961 self.advance();
2962 let inner = self.parse_type_expr()?;
2972 generic_param = if inner.generic_param.is_empty() {
2973 inner.name
2974 } else {
2975 format!("{}<{}>", inner.name, inner.generic_param)
2976 };
2977 self.consume(TokenType::Gt)?;
2978 }
2979 if self.check(TokenType::LBracket) {
2983 self.advance();
2984 if matches!(self.current().ttype, TokenType::Integer | TokenType::Float) {
2985 generic_param = self.advance().value.clone();
2986 } else {
2987 let inner = self.parse_type_expr()?;
2988 generic_param = if inner.generic_param.is_empty() {
2989 inner.name
2990 } else {
2991 format!("{}[{}]", inner.name, inner.generic_param)
2992 };
2993 }
2994 self.consume(TokenType::RBracket)?;
2995 }
2996 if self.check(TokenType::Question) {
2997 self.advance();
2998 optional = true;
2999 }
3000
3001 Ok(TypeExpr {
3002 name: name_tok.value,
3003 generic_param,
3004 optional,
3005 loc,
3006 })
3007 }
3008
3009 fn parse_output_type_string(&mut self) -> Result<String, ParseError> {
3034 let expr = self.parse_type_expr()?;
3035 let mut s = expr.name;
3036 if !expr.generic_param.is_empty() {
3037 s.push('<');
3038 s.push_str(&expr.generic_param);
3039 s.push('>');
3040 }
3041 if expr.optional {
3042 s.push('?');
3043 }
3044 Ok(s)
3045 }
3046
3047 fn parse_flow(&mut self) -> Result<FlowDefinition, ParseError> {
3050 let tok = self.consume(TokenType::Flow)?;
3051 let loc = self.loc_of(&tok);
3052 let name = self.consume(TokenType::Identifier)?.value;
3053
3054 self.consume(TokenType::LParen)?;
3055 let mut parameters = Vec::new();
3056 if !self.check(TokenType::RParen) {
3057 parameters = self.parse_param_list()?;
3058 }
3059 self.consume(TokenType::RParen)?;
3060
3061 let mut return_type = None;
3062 if self.check(TokenType::Arrow) {
3063 self.advance();
3064 return_type = Some(self.parse_type_expr()?);
3065 }
3066
3067 self.consume(TokenType::LBrace)?;
3068 let mut body = Vec::new();
3069 while !self.check(TokenType::RBrace) {
3070 body.push(self.parse_flow_step()?);
3071 }
3072 self.consume(TokenType::RBrace)?;
3073
3074 Ok(FlowDefinition {
3075 name,
3076 parameters,
3077 return_type,
3078 body,
3079 loc,
3080 leading_trivia: Vec::new(),
3081 trailing_trivia: Vec::new(),
3082 })
3083 }
3084
3085 fn parse_param_list(&mut self) -> Result<Vec<Parameter>, ParseError> {
3086 let mut params = Vec::new();
3087
3088 let name = self.consume(TokenType::Identifier)?;
3089 let ploc = self.loc_of(&name);
3090 self.consume(TokenType::Colon)?;
3091 let type_expr = self.parse_type_expr()?;
3092 params.push(Parameter {
3093 name: name.value,
3094 type_expr,
3095 loc: ploc,
3096 });
3097
3098 while self.check(TokenType::Comma) {
3099 self.advance();
3100 let name = self.consume(TokenType::Identifier)?;
3101 let ploc = self.loc_of(&name);
3102 self.consume(TokenType::Colon)?;
3103 let type_expr = self.parse_type_expr()?;
3104 params.push(Parameter {
3105 name: name.value,
3106 type_expr,
3107 loc: ploc,
3108 });
3109 }
3110 Ok(params)
3111 }
3112
3113 fn parse_flow_step(&mut self) -> Result<FlowStep, ParseError> {
3116 let tok = self.current().clone();
3117
3118 match tok.ttype {
3119 TokenType::Step => self.parse_step().map(FlowStep::Step),
3120 TokenType::If => self.parse_if().map(FlowStep::If),
3121 TokenType::For => self.parse_for_in().map(FlowStep::ForIn),
3122 TokenType::Let => self.parse_let().map(FlowStep::Let),
3123 TokenType::Return => self.parse_return().map(FlowStep::Return),
3124 TokenType::Break => self.parse_break().map(FlowStep::Break),
3125 TokenType::Continue => self.parse_continue().map(FlowStep::Continue),
3126 TokenType::Lambda => self.parse_lambda_data_apply().map(FlowStep::LambdaDataApply),
3127
3128 TokenType::Probe => self.parse_flow_step_simple("probe").map(|l| FlowStep::Probe(ProbeStep { target: l.1, loc: l.0 })),
3130 TokenType::Reason => self.parse_flow_step_simple("reason").map(|l| FlowStep::Reason(ReasonStep { strategy: String::new(), target: l.1, loc: l.0 })),
3131 TokenType::Validate => self.parse_flow_step_simple("validate").map(|l| FlowStep::Validate(ValidateStep { target: l.1, rule: String::new(), loc: l.0 })),
3132 TokenType::Refine => self.parse_flow_step_simple("refine").map(|l| FlowStep::Refine(RefineStep { target: l.1, strategy: String::new(), loc: l.0 })),
3133 TokenType::Weave => self.parse_weave_step(),
3134 TokenType::Use => self.parse_use_step(),
3135 TokenType::Remember => self.parse_remember_step(),
3136 TokenType::Recall => self.parse_recall_step(),
3137 TokenType::Par => self.parse_par_block().map(FlowStep::Par),
3138 TokenType::Hibernate => self.parse_hibernate_step(),
3139 TokenType::Deliberate => self.parse_block_step("deliberate").map(|l| FlowStep::Deliberate(DeliberateBlock { loc: l })),
3140 TokenType::Consensus => self.parse_block_step("consensus").map(|l| FlowStep::Consensus(ConsensusBlock { loc: l })),
3141 TokenType::Forge => self.parse_forge_step().map(FlowStep::Forge),
3142 TokenType::Focus => self.parse_focus_step(),
3143 TokenType::Grad => self.parse_grad_step(),
3144 TokenType::Associate => self.parse_associate_step(),
3145 TokenType::Aggregate => self.parse_aggregate_step(),
3146 TokenType::Explore => self.parse_explore_step(),
3147 TokenType::Ingest => self.parse_ingest_step(),
3148 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 })),
3149 TokenType::Stream => self.parse_stream_block().map(FlowStep::Stream),
3155 TokenType::Navigate => self.parse_navigate_step(),
3156 TokenType::Drill => self.parse_drill_step(),
3157 TokenType::Trail => self.parse_flow_step_simple("trail").map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 })),
3158 TokenType::Corroborate => self.parse_corroborate_step(),
3159 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 })),
3160 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 })),
3161 TokenType::Compute => self.parse_compute_apply().map(FlowStep::ComputeApply),
3166 TokenType::Listen => self.parse_listen_step(),
3167 TokenType::Daemon => self.parse_flow_step_simple("daemon").map(|l| FlowStep::DaemonStep(DaemonStepNode { daemon_ref: l.1, loc: l.0 })),
3168 TokenType::Emit => self.parse_emit_step(),
3170 TokenType::Mint => self.parse_mint_step(),
3172 TokenType::Rotate => self.parse_rotate_step(),
3175 TokenType::Publish => self.parse_publish_step(),
3176 TokenType::Discover => self.parse_discover_step(),
3177 TokenType::Persist => self.parse_persist_step(),
3178 TokenType::Retrieve => self.parse_retrieve_step(),
3179 TokenType::Mutate => self.parse_mutate_step(),
3180 TokenType::Purge => self.parse_store_where_step().map(|(loc, store_name, where_expr)| FlowStep::Purge(PurgeStep { store_name, where_expr, loc })),
3181 TokenType::Transact => self.parse_block_step("transact").map(|l| FlowStep::Transact(TransactBlock { loc: l })),
3182 TokenType::Warden => self.parse_warden().map(FlowStep::Warden),
3184 TokenType::Quant => self.parse_quant().map(FlowStep::Quant),
3186 TokenType::Yield => self.parse_yield().map(FlowStep::Yield),
3188 TokenType::Run => self.parse_run().map(FlowStep::Run),
3192
3193 _ => {
3194 let hint = crate::smart_suggest::suggest_for(
3198 &tok.value,
3199 crate::smart_suggest::FLOW_BODY_KEYWORD_NAMES,
3200 );
3201 let base = format!(
3202 "Unexpected token in flow body: '{}' — expected step, if, for, let, return, ...",
3203 tok.value
3204 );
3205 let message = if hint.is_empty() {
3206 base
3207 } else {
3208 format!("{base}. {hint}")
3209 };
3210 Err(ParseError {
3211 message,
3212 line: tok.line,
3213 column: tok.column,
3214 ..Default::default()
3215 })
3216 }
3217 }
3218 }
3219
3220 fn parse_step(&mut self) -> Result<StepNode, ParseError> {
3223 let tok = self.consume(TokenType::Step)?;
3224 let loc = self.loc_of(&tok);
3225 let name = self.consume(TokenType::Identifier)?.value;
3226
3227 let mut persona_ref = String::new();
3228 if self.check(TokenType::Use) {
3229 self.advance();
3230 persona_ref = self.consume_any_ident_or_kw()?.value;
3231 }
3232
3233 self.consume(TokenType::LBrace)?;
3234
3235 let mut node = StepNode {
3236 name,
3237 persona_ref,
3238 given: String::new(),
3239 ask: String::new(),
3240 output_type: String::new(),
3241 confidence_floor: None,
3242 navigate_ref: String::new(),
3243 apply_ref: String::new(),
3244 requires_context: None,
3245 now_tz: None,
3246 loc,
3247 };
3248
3249 while !self.check(TokenType::RBrace) {
3250 let inner = self.current().clone();
3251
3252 match inner.ttype {
3253 TokenType::Given => {
3254 self.advance();
3255 self.consume(TokenType::Colon)?;
3256 node.given = self.parse_expression_string()?;
3257 }
3258 TokenType::Ask => {
3259 self.advance();
3260 self.consume(TokenType::Colon)?;
3261 node.ask = self.consume(TokenType::StringLit)?.value;
3262 }
3263 TokenType::Output => {
3264 self.advance();
3274 self.consume(TokenType::Colon)?;
3275 node.output_type = self.parse_output_type_string()?;
3276 }
3277 TokenType::Navigate => {
3278 self.advance();
3279 self.consume(TokenType::Colon)?;
3280 node.navigate_ref = self.parse_dotted_identifier()?;
3281 }
3282 TokenType::Identifier if inner.value == "confidence_floor" => {
3283 self.advance();
3284 self.consume(TokenType::Colon)?;
3285 node.confidence_floor = Some(self.consume_number()?);
3286 }
3287 TokenType::Identifier if inner.value == "apply" => {
3288 self.advance();
3289 self.consume(TokenType::Colon)?;
3290 node.apply_ref = self.consume_any_ident_or_kw()?.value;
3291 }
3292 TokenType::Identifier if inner.value == "requires_context" => {
3300 self.advance();
3301 self.consume(TokenType::Colon)?;
3302 let num = self.current().clone();
3303 let bad = |tok: &crate::tokens::Token| ParseError {
3304 message: format!(
3305 "`requires_context:` must be a positive integer token count \
3306 (got '{}')",
3307 tok.value
3308 ),
3309 line: tok.line,
3310 column: tok.column,
3311 ..Default::default()
3312 };
3313 if num.ttype != TokenType::Integer {
3314 return Err(bad(&num));
3315 }
3316 let value = num.value.parse::<u32>().map_err(|_| bad(&num))?;
3317 self.advance();
3318 node.requires_context = Some(value);
3319 }
3320 TokenType::Identifier if inner.value == "now" => {
3325 self.advance();
3326 self.consume(TokenType::Colon)?;
3327 let tz = self.current().clone();
3328 if tz.ttype != TokenType::StringLit {
3329 return Err(ParseError {
3330 message: format!(
3331 "`now:` must be an IANA timezone string literal like \
3332 \"America/Bogota\" or \"UTC\" (got '{}')",
3333 tz.value
3334 ),
3335 line: tz.line,
3336 column: tz.column,
3337 ..Default::default()
3338 });
3339 }
3340 self.advance();
3341 node.now_tz = Some(tz.value);
3342 }
3343 TokenType::Use => {
3353 let tool = self
3354 .tokens
3355 .get(self.pos + 1)
3356 .map(|t| t.value.as_str())
3357 .filter(|v| !v.is_empty())
3358 .unwrap_or("<Tool>");
3359 return Err(ParseError {
3360 message: format!(
3361 "`use` is not valid inside a `step {{ }}` body — the tool dispatch \
3362 would be silently dropped. To invoke a tool, either write the \
3363 flow-level step `use {tool} on <arg>` (outside this block), or bind \
3364 it inside this step with `apply: {tool}`. To attach a persona, put \
3365 it in the step header: `step <name> use <Persona> {{ … }}`."
3366 ),
3367 line: inner.line,
3368 column: inner.column,
3369 ..Default::default()
3370 });
3371 }
3372 TokenType::Probe
3374 | TokenType::Reason
3375 | TokenType::Weave
3376 | TokenType::Stream => {
3377 self.skip_flow_step_structural()?;
3378 }
3379 _ => {
3380 return Err(ParseError {
3381 message: format!(
3382 "Unexpected token in step body: '{}' — expected given, ask, \
3383 probe, reason, weave, stream, output, confidence_floor, navigate, \
3384 apply, requires_context, now",
3385 inner.value
3386 ),
3387 line: inner.line,
3388 column: inner.column,
3389 ..Default::default()
3390 });
3391 }
3392 }
3393 }
3394 self.consume(TokenType::RBrace)?;
3395 Ok(node)
3396 }
3397
3398 fn skip_flow_step_structural(&mut self) -> Result<(), ParseError> {
3400 self.advance();
3402 while !self.check(TokenType::LBrace)
3404 && !self.check(TokenType::RBrace)
3405 && !self.check(TokenType::Eof)
3406 {
3407 let tt = &self.current().ttype;
3409 if matches!(
3410 tt,
3411 TokenType::Step
3412 | TokenType::Given
3413 | TokenType::Ask
3414 | TokenType::Output
3415 | TokenType::Navigate
3416 | TokenType::Use
3417 | TokenType::Probe
3418 | TokenType::Reason
3419 | TokenType::Weave
3420 | TokenType::Stream
3421 | TokenType::If
3422 | TokenType::For
3423 | TokenType::Let
3424 | TokenType::Return
3425 ) {
3426 return Ok(());
3427 }
3428 self.advance();
3429 }
3430 if self.check(TokenType::LBrace) {
3432 self.skip_braced_block()?;
3433 }
3434 Ok(())
3435 }
3436
3437 fn parse_intent(&mut self) -> Result<IntentNode, ParseError> {
3440 let tok = self.consume(TokenType::Intent)?;
3441 let loc = self.loc_of(&tok);
3442 let name = self.consume(TokenType::Identifier)?.value;
3443 self.consume(TokenType::LBrace)?;
3444
3445 let mut node = IntentNode {
3446 name,
3447 given: String::new(),
3448 ask: String::new(),
3449 output_type: None,
3450 confidence_floor: None,
3451 loc,
3452 leading_trivia: Vec::new(),
3453 trailing_trivia: Vec::new(),
3454 };
3455
3456 while !self.check(TokenType::RBrace) {
3457 let field_name = self.current().value.clone();
3458 self.advance();
3459 self.consume(TokenType::Colon)?;
3460
3461 match field_name.as_str() {
3462 "given" => node.given = self.consume(TokenType::Identifier)?.value,
3463 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
3464 "output" => node.output_type = Some(self.parse_type_expr()?),
3465 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
3466 _ => self.skip_value(),
3467 }
3468 }
3469 self.consume(TokenType::RBrace)?;
3470 Ok(node)
3471 }
3472
3473 fn parse_run(&mut self) -> Result<RunStatement, ParseError> {
3476 let tok = self.consume(TokenType::Run)?;
3477 let loc = self.loc_of(&tok);
3478 let flow_name = self.consume(TokenType::Identifier)?.value;
3479
3480 self.consume(TokenType::LParen)?;
3481 let mut arguments = Vec::new();
3482 if !self.check(TokenType::RParen) {
3483 arguments = self.parse_argument_list()?;
3484 }
3485 self.consume(TokenType::RParen)?;
3486
3487 let mut node = RunStatement {
3488 flow_name,
3489 arguments,
3490 persona: String::new(),
3491 context: String::new(),
3492 anchors: Vec::new(),
3493 on_failure: String::new(),
3494 on_failure_params: Vec::new(),
3495 output_to: String::new(),
3496 effort: String::new(),
3497 loc,
3498 leading_trivia: Vec::new(),
3499 trailing_trivia: Vec::new(),
3500 };
3501
3502 while self.check_run_modifier() {
3503 let mod_tok = self.current().clone();
3504 match mod_tok.ttype {
3505 TokenType::As => {
3506 self.advance();
3507 node.persona = self.consume(TokenType::Identifier)?.value;
3508 }
3509 TokenType::Within => {
3510 self.advance();
3511 node.context = self.consume(TokenType::Identifier)?.value;
3512 }
3513 TokenType::ConstrainedBy => {
3514 self.advance();
3515 node.anchors = self.parse_bracketed_identifiers()?;
3516 }
3517 TokenType::OnFailure => {
3518 self.advance();
3519 self.consume(TokenType::Colon)?;
3520 node.on_failure = self.consume_any_ident_or_kw()?.value;
3521 if self.check(TokenType::LParen) {
3523 self.advance();
3524 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3525 let key = self.consume_any_ident_or_kw()?.value;
3526 self.consume(TokenType::Colon)?;
3527 let val = self.consume_any_ident_or_kw()?.value;
3528 node.on_failure_params.push((key, val));
3529 if self.check(TokenType::Comma) {
3530 self.advance();
3531 }
3532 }
3533 if self.check(TokenType::RParen) {
3534 self.advance();
3535 }
3536 }
3537 }
3538 TokenType::OutputTo => {
3539 self.advance();
3540 self.consume(TokenType::Colon)?;
3541 node.output_to = self.consume(TokenType::StringLit)?.value;
3542 }
3543 TokenType::Effort => {
3544 self.advance();
3545 self.consume(TokenType::Colon)?;
3546 node.effort = self.consume_any_ident_or_kw()?.value;
3547 }
3548 _ => break,
3549 }
3550 }
3551
3552 Ok(node)
3553 }
3554
3555 fn parse_epistemic_block(&mut self) -> Result<EpistemicBlock, ParseError> {
3558 let tok = self.current().clone();
3559 let mode = match tok.ttype {
3560 TokenType::Know => "know",
3561 TokenType::Believe => "believe",
3562 TokenType::Speculate => "speculate",
3563 TokenType::Doubt => "doubt",
3564 _ => unreachable!(),
3565 };
3566 self.advance();
3567 let loc = self.loc_of(&tok);
3568
3569 self.consume(TokenType::LBrace)?;
3570 let mut body = Vec::new();
3571 while !self.check(TokenType::RBrace) {
3572 body.push(self.parse_declaration()?);
3573 }
3574 self.consume(TokenType::RBrace)?;
3575
3576 Ok(EpistemicBlock {
3577 mode: mode.to_string(),
3578 body,
3579 loc,
3580 leading_trivia: Vec::new(),
3581 trailing_trivia: Vec::new(),
3582 })
3583 }
3584
3585 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
3593 self.parse_expr_bp(0)
3594 }
3595
3596 fn parse_expr_bp(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
3597 let mut lhs = match self.current().ttype {
3600 TokenType::Minus => {
3601 self.advance();
3602 Expr::Unary(UnOp::Neg, Box::new(self.parse_expr_bp(6)?))
3603 }
3604 TokenType::Not => {
3605 self.advance();
3606 Expr::Unary(UnOp::Not, Box::new(self.parse_expr_bp(6)?))
3607 }
3608 _ => self.parse_postfix()?,
3609 };
3610 while let Some((op, lbp)) = Self::binop_of(self.current().ttype.clone()) {
3612 if lbp < min_bp {
3613 break;
3614 }
3615 self.advance();
3616 let rhs = self.parse_expr_bp(lbp + 1)?;
3617 lhs = Expr::Binary(op, Box::new(lhs), Box::new(rhs));
3618 }
3619 Ok(lhs)
3620 }
3621
3622 fn binop_of(t: TokenType) -> Option<(BinOp, u8)> {
3625 Some(match t {
3626 TokenType::Or => (BinOp::Or, 1),
3627 TokenType::And => (BinOp::And, 2),
3628 TokenType::Eq => (BinOp::Eq, 3),
3629 TokenType::Neq => (BinOp::Ne, 3),
3630 TokenType::Lt => (BinOp::Lt, 3),
3631 TokenType::Lte => (BinOp::Le, 3),
3632 TokenType::Gt => (BinOp::Gt, 3),
3633 TokenType::Gte => (BinOp::Ge, 3),
3634 TokenType::Plus => (BinOp::Add, 4),
3635 TokenType::Minus => (BinOp::Sub, 4),
3636 TokenType::Star => (BinOp::Mul, 5),
3637 TokenType::Slash => (BinOp::Div, 5),
3638 TokenType::Percent => (BinOp::Mod, 5),
3639 _ => return None,
3640 })
3641 }
3642
3643 fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
3649 let mut expr = self.parse_expr_atom()?;
3650 loop {
3651 if self.check(TokenType::Dot) {
3652 self.advance();
3653 let name = self.consume_any_ident_or_kw()?.value;
3654 if let Some(builtin) = Builtin::from_name(&name) {
3655 let mut args = vec![expr];
3656 if self.check(TokenType::LParen) {
3657 self.advance();
3658 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3659 args.push(self.parse_expr_bp(0)?);
3660 if self.check(TokenType::Comma) {
3661 self.advance();
3662 } else {
3663 break;
3664 }
3665 }
3666 self.consume(TokenType::RParen)?;
3667 }
3668 expr = Expr::Call(builtin, args);
3669 } else {
3670 expr = match expr {
3674 Expr::Ref(p) => Expr::Ref(format!("{p}.{name}")),
3675 other => Expr::Field(Box::new(other), name),
3676 };
3677 }
3678 } else if self.check(TokenType::LBracket) {
3679 self.advance();
3681 let index = self.parse_expr_bp(0)?;
3682 self.consume(TokenType::RBracket)?;
3683 expr = Expr::Index(Box::new(expr), Box::new(index));
3684 } else {
3685 break;
3686 }
3687 }
3688 Ok(expr)
3689 }
3690
3691 fn parse_expr_atom(&mut self) -> Result<Expr, ParseError> {
3692 let tok = self.current().clone();
3693 match tok.ttype {
3694 TokenType::Integer => {
3695 self.advance();
3696 let lit = tok
3697 .value
3698 .parse::<i64>()
3699 .map(ExprLit::Int)
3700 .or_else(|_| tok.value.parse::<f64>().map(ExprLit::Float))
3701 .map_err(|_| ParseError {
3702 message: format!("invalid integer literal '{}'", tok.value),
3703 line: tok.line,
3704 column: tok.column,
3705 ..Default::default()
3706 })?;
3707 Ok(Expr::Lit(lit))
3708 }
3709 TokenType::Float => {
3710 self.advance();
3711 let f = tok.value.parse::<f64>().map_err(|_| ParseError {
3712 message: format!("invalid float literal '{}'", tok.value),
3713 line: tok.line,
3714 column: tok.column,
3715 ..Default::default()
3716 })?;
3717 Ok(Expr::Lit(ExprLit::Float(f)))
3718 }
3719 TokenType::Bool => {
3720 self.advance();
3721 Ok(Expr::Lit(ExprLit::Bool(tok.value == "true")))
3722 }
3723 TokenType::StringLit => {
3724 self.advance();
3725 Ok(Expr::Lit(ExprLit::Str(tok.value)))
3726 }
3727 TokenType::LParen => {
3728 self.advance();
3729 let inner = self.parse_expr_bp(0)?;
3730 self.consume(TokenType::RParen)?;
3731 Ok(inner)
3732 }
3733 _ => {
3734 Ok(Expr::Ref(self.consume_any_ident_or_kw()?.value))
3738 }
3739 }
3740 }
3741
3742 fn expr_lit_surface(lit: &ExprLit) -> String {
3747 match lit {
3748 ExprLit::Int(i) => i.to_string(),
3749 ExprLit::Float(f) => f.to_string(),
3750 ExprLit::Bool(b) => b.to_string(),
3751 ExprLit::Str(s) => s.clone(),
3752 }
3753 }
3754
3755 fn expr_leaf_surface(expr: &Expr) -> Option<String> {
3756 match expr {
3757 Expr::Ref(p) => Some(p.clone()),
3758 Expr::Lit(l) => Some(Self::expr_lit_surface(l)),
3759 _ => None,
3760 }
3761 }
3762
3763 fn expr_legacy_leaf(expr: &Expr) -> Option<(String, String, String)> {
3767 match expr {
3768 Expr::Ref(p) => Some((p.clone(), String::new(), String::new())),
3769 Expr::Binary(op, l, r) => {
3770 let op_s = match op {
3771 BinOp::Eq => "==",
3772 BinOp::Ne => "!=",
3773 BinOp::Lt => "<",
3774 BinOp::Le => "<=",
3775 BinOp::Gt => ">",
3776 BinOp::Ge => ">=",
3777 _ => return None,
3778 };
3779 let lhs = match &**l {
3780 Expr::Ref(p) => p.clone(),
3781 _ => return None,
3782 };
3783 let rhs = Self::expr_leaf_surface(r)?;
3784 Some((lhs, op_s.to_string(), rhs))
3785 }
3786 _ => None,
3787 }
3788 }
3789
3790 fn collect_or_leaves(expr: &Expr, out: &mut Vec<(String, String, String)>) -> bool {
3793 match expr {
3794 Expr::Binary(BinOp::Or, l, r) => {
3795 Self::collect_or_leaves(l, out) && Self::collect_or_leaves(r, out)
3796 }
3797 _ => match Self::expr_legacy_leaf(expr) {
3798 Some(t) => {
3799 out.push(t);
3800 true
3801 }
3802 None => false,
3803 },
3804 }
3805 }
3806
3807 #[allow(clippy::type_complexity)]
3813 fn cond_as_legacy(
3814 expr: &Expr,
3815 ) -> Option<(String, String, String, Vec<(String, String, String)>, String)> {
3816 let mut leaves = Vec::new();
3817 if !Self::collect_or_leaves(expr, &mut leaves) || leaves.is_empty() {
3818 return None;
3819 }
3820 let (c0, o0, v0) = leaves[0].clone();
3821 let rest = leaves[1..].to_vec();
3822 let conjunctor = if rest.is_empty() {
3823 String::new()
3824 } else {
3825 "or".to_string()
3826 };
3827 Some((c0, o0, v0, rest, conjunctor))
3828 }
3829
3830 fn parse_if(&mut self) -> Result<ConditionalNode, ParseError> {
3831 let tok = self.consume(TokenType::If)?;
3832 let loc = self.loc_of(&tok);
3833
3834 let expr = self.parse_expr()?;
3839 let (condition, comparison_op, comparison_value, conditions, conjunctor, cond) =
3840 match Self::cond_as_legacy(&expr) {
3841 Some((c, o, v, more, conj)) => (c, o, v, more, conj, None),
3842 None => (
3843 String::new(),
3844 String::new(),
3845 String::new(),
3846 Vec::new(),
3847 String::new(),
3848 Some(expr),
3849 ),
3850 };
3851
3852 let mut then_body = Vec::new();
3853 let mut else_body = Vec::new();
3854
3855 if self.check(TokenType::Arrow) {
3857 self.advance();
3858 then_body.push(self.parse_flow_step()?);
3859 } else if self.check(TokenType::LBrace) {
3860 self.advance();
3861 while !self.check(TokenType::RBrace) {
3862 then_body.push(self.parse_flow_step()?);
3863 }
3864 self.consume(TokenType::RBrace)?;
3865 }
3866
3867 if self.check(TokenType::Else) {
3869 self.advance();
3870 if self.check(TokenType::Arrow) {
3871 self.advance();
3872 else_body.push(self.parse_flow_step()?);
3873 } else if self.check(TokenType::LBrace) {
3874 self.advance();
3875 while !self.check(TokenType::RBrace) {
3876 else_body.push(self.parse_flow_step()?);
3877 }
3878 self.consume(TokenType::RBrace)?;
3879 }
3880 }
3881
3882 Ok(ConditionalNode {
3883 condition,
3884 comparison_op,
3885 comparison_value,
3886 then_body,
3887 else_body,
3888 conditions,
3889 conjunctor,
3890 cond,
3891 loc,
3892 })
3893 }
3894
3895 fn parse_for_in(&mut self) -> Result<ForInStatement, ParseError> {
3898 let tok = self.consume(TokenType::For)?;
3899 let loc = self.loc_of(&tok);
3900 let variable = self.consume(TokenType::Identifier)?.value;
3901 self.consume(TokenType::In)?;
3902 let iterable = self.parse_dotted_identifier()?;
3903
3904 self.consume(TokenType::LBrace)?;
3905 self.loop_depth += 1;
3912 let body_result = (|| -> Result<Vec<FlowStep>, ParseError> {
3913 let mut body = Vec::new();
3914 while !self.check(TokenType::RBrace) {
3915 body.push(self.parse_flow_step()?);
3916 }
3917 Ok(body)
3918 })();
3919 self.loop_depth -= 1;
3920 let body = body_result?;
3921 self.consume(TokenType::RBrace)?;
3922
3923 Ok(ForInStatement {
3924 variable,
3925 iterable,
3926 body,
3927 loc,
3928 })
3929 }
3930
3931 fn parse_break(&mut self) -> Result<BreakStatement, ParseError> {
3934 let tok = self.consume(TokenType::Break)?;
3935 let loc = self.loc_of(&tok);
3936 if self.loop_depth == 0 {
3937 return Err(ParseError {
3938 message: "'break' outside of a for-in loop body".to_string(),
3939 line: tok.line,
3940 column: tok.column,
3941 ..Default::default()
3942 });
3943 }
3944 Ok(BreakStatement { loc })
3945 }
3946
3947 fn parse_continue(&mut self) -> Result<ContinueStatement, ParseError> {
3950 let tok = self.consume(TokenType::Continue)?;
3951 let loc = self.loc_of(&tok);
3952 if self.loop_depth == 0 {
3953 return Err(ParseError {
3954 message: "'continue' outside of a for-in loop body".to_string(),
3955 line: tok.line,
3956 column: tok.column,
3957 ..Default::default()
3958 });
3959 }
3960 Ok(ContinueStatement { loc })
3961 }
3962
3963 fn parse_let(&mut self) -> Result<LetStatement, ParseError> {
3966 let tok = self.consume(TokenType::Let)?;
3967 let loc = self.loc_of(&tok);
3968
3969 let name = self.consume_any_ident_or_kw()?.value;
3971 let type_annotation = if self.check(TokenType::Colon) {
3973 self.advance();
3974 Some(self.parse_type_expr()?)
3975 } else {
3976 None
3977 };
3978 self.consume(TokenType::Assign)?;
3979 self.last_let_value_kind = "literal".to_string();
3982 let (value, value_ast) = self.parse_let_value_expr_with_ast()?;
3983
3984 Ok(LetStatement {
3985 identifier: name,
3986 value_expr: value,
3987 value_kind: self.last_let_value_kind.clone(),
3988 type_annotation,
3989 value_ast,
3990 loc,
3991 leading_trivia: Vec::new(),
3992 trailing_trivia: Vec::new(),
3993 })
3994 }
3995
3996 fn parse_let_value_expr(&mut self) -> Result<String, ParseError> {
3997 let atom = self.parse_let_atom()?;
3998
3999 if matches!(
4001 self.current().ttype,
4002 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
4003 ) {
4004 let mut parts = vec![atom];
4005 while matches!(
4006 self.current().ttype,
4007 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
4008 ) {
4009 parts.push(self.advance().value.clone());
4010 parts.push(self.parse_let_atom()?);
4011 }
4012 self.last_let_value_kind = "expression".to_string();
4013 return Ok(parts.join(" "));
4014 }
4015 Ok(atom)
4016 }
4017
4018 fn parse_let_value_expr_with_ast(&mut self) -> Result<(String, Option<Expr>), ParseError> {
4028 if self.check(TokenType::LBracket) {
4029 self.last_let_value_kind = "literal".to_string();
4030 return Ok((self.parse_let_list_literal()?, None));
4031 }
4032 let expr = self.parse_expr()?;
4033 Ok(match expr {
4034 Expr::Lit(lit) => {
4035 self.last_let_value_kind = "literal".to_string();
4036 (Self::expr_lit_surface(&lit), None)
4037 }
4038 Expr::Ref(p) => {
4039 self.last_let_value_kind = "reference".to_string();
4040 (p, None)
4041 }
4042 other => {
4043 self.last_let_value_kind = "expression".to_string();
4044 (Self::render_expr(&other), Some(other))
4045 }
4046 })
4047 }
4048
4049 fn render_expr(e: &Expr) -> String {
4052 match e {
4053 Expr::Lit(l) => Self::expr_lit_surface(l),
4054 Expr::Ref(p) => p.clone(),
4055 Expr::Unary(UnOp::Neg, x) => format!("-{}", Self::render_expr(x)),
4056 Expr::Unary(UnOp::Not, x) => format!("not {}", Self::render_expr(x)),
4057 Expr::Binary(op, l, r) => {
4058 let sym = match op {
4059 BinOp::Add => "+",
4060 BinOp::Sub => "-",
4061 BinOp::Mul => "*",
4062 BinOp::Div => "/",
4063 BinOp::Mod => "%",
4064 BinOp::Eq => "==",
4065 BinOp::Ne => "!=",
4066 BinOp::Lt => "<",
4067 BinOp::Le => "<=",
4068 BinOp::Gt => ">",
4069 BinOp::Ge => ">=",
4070 BinOp::And => "and",
4071 BinOp::Or => "or",
4072 };
4073 format!("({} {sym} {})", Self::render_expr(l), Self::render_expr(r))
4074 }
4075 Expr::Call(b, args) => {
4076 let recv = args.first().map(Self::render_expr).unwrap_or_default();
4077 let rest: Vec<String> = args.iter().skip(1).map(Self::render_expr).collect();
4078 if rest.is_empty() {
4079 format!("{recv}.{}", b.surface())
4080 } else {
4081 format!("{recv}.{}({})", b.surface(), rest.join(", "))
4082 }
4083 }
4084 Expr::Field(b, f) => format!("{}.{f}", Self::render_expr(b)),
4085 Expr::Index(b, i) => format!("{}[{}]", Self::render_expr(b), Self::render_expr(i)),
4086 }
4087 }
4088
4089 fn parse_let_atom(&mut self) -> Result<String, ParseError> {
4090 let tok = self.current().clone();
4091
4092 match tok.ttype {
4093 TokenType::StringLit => {
4094 self.last_let_value_kind = "literal".to_string();
4095 self.advance();
4096 Ok(tok.value)
4097 }
4098 TokenType::Integer | TokenType::Float => {
4099 self.last_let_value_kind = "literal".to_string();
4100 self.advance();
4101 Ok(tok.value)
4102 }
4103 TokenType::Bool => {
4104 self.last_let_value_kind = "literal".to_string();
4105 self.advance();
4106 Ok(tok.value)
4107 }
4108 TokenType::Identifier => {
4109 self.last_let_value_kind = "reference".to_string();
4110 self.parse_dotted_identifier()
4111 }
4112 TokenType::LBracket => {
4113 self.last_let_value_kind = "literal".to_string();
4114 self.parse_let_list_literal()
4115 }
4116 _ => {
4117 if self.pos + 1 < self.tokens.len()
4119 && self.tokens[self.pos + 1].ttype == TokenType::Dot
4120 {
4121 self.last_let_value_kind = "reference".to_string();
4122 return self.parse_dotted_identifier();
4123 }
4124 Err(ParseError {
4125 message: format!(
4126 "Expected value expression, found {:?}('{}')",
4127 tok.ttype, tok.value
4128 ),
4129 line: tok.line,
4130 column: tok.column,
4131 ..Default::default()
4132 })
4133 }
4134 }
4135 }
4136
4137 fn parse_let_list_literal(&mut self) -> Result<String, ParseError> {
4138 self.consume(TokenType::LBracket)?;
4139 let mut items = Vec::new();
4140 if !self.check(TokenType::RBracket) {
4141 items.push(self.parse_let_value_expr()?);
4142 while self.check(TokenType::Comma) {
4143 self.advance();
4144 if self.check(TokenType::RBracket) {
4145 break; }
4147 items.push(self.parse_let_value_expr()?);
4148 }
4149 }
4150 self.consume(TokenType::RBracket)?;
4151 Ok(format!("[{}]", items.join(", ")))
4152 }
4153
4154 fn parse_return(&mut self) -> Result<ReturnStatement, ParseError> {
4157 let tok = self.consume(TokenType::Return)?;
4158 let loc = self.loc_of(&tok);
4159 let value = self.parse_let_value_expr()?;
4160 Ok(ReturnStatement {
4161 value_expr: value,
4162 loc,
4163 })
4164 }
4165
4166 fn parse_flow_step_simple(&mut self, _kw: &str) -> Result<(Loc, String), ParseError> {
4170 let tok = self.current().clone();
4171 self.advance(); let target = if self.at_declaration_start()
4173 || self.check(TokenType::RBrace)
4174 || self.check(TokenType::Eof)
4175 {
4176 String::new()
4177 } else {
4178 self.consume_any_ident_or_kw()?.value.clone()
4179 };
4180 if self.check(TokenType::LBrace) {
4182 self.skip_braced_block()?;
4183 }
4184 Ok((
4185 Loc {
4186 line: tok.line,
4187 column: tok.column,
4188 },
4189 target,
4190 ))
4191 }
4192
4193 fn parse_stream_block(&mut self) -> Result<StreamBlock, ParseError> {
4204 let tok = self.current().clone();
4205 let loc = self.loc_of(&tok);
4206 self.advance(); while !self.check(TokenType::LBrace)
4212 && !self.check(TokenType::RBrace)
4213 && !self.check(TokenType::Eof)
4214 && !self.at_declaration_start()
4215 {
4216 self.advance();
4217 }
4218
4219 let mut body = Vec::new();
4220 if self.check(TokenType::LBrace) {
4221 self.advance();
4222 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4223 body.push(self.parse_flow_step()?);
4224 }
4225 self.consume(TokenType::RBrace)?;
4226 }
4227
4228 Ok(StreamBlock { body, loc })
4229 }
4230
4231 fn parse_block_step(&mut self, _kw: &str) -> Result<Loc, ParseError> {
4232 let tok = self.current().clone();
4233 self.advance();
4234 while !self.check(TokenType::LBrace)
4236 && !self.check(TokenType::RBrace)
4237 && !self.check(TokenType::Eof)
4238 && !self.at_declaration_start()
4239 {
4240 self.advance();
4241 }
4242 if self.check(TokenType::LBrace) {
4243 self.skip_braced_block()?;
4244 }
4245 Ok(Loc {
4246 line: tok.line,
4247 column: tok.column,
4248 })
4249 }
4250
4251 fn parse_forge_step(&mut self) -> Result<ForgeBlock, ParseError> {
4258 let tok = self.consume(TokenType::Forge)?;
4259 let name = self.consume(TokenType::Identifier)?.value;
4260 let mut node = ForgeBlock {
4261 name,
4262 novelty: 0.5,
4263 depth: 1,
4264 branches: 1,
4265 loc: Loc { line: tok.line, column: tok.column },
4266 ..Default::default()
4267 };
4268 self.consume(TokenType::LParen)?;
4270 let arg = self.consume_any_ident_or_kw()?.value;
4271 self.consume(TokenType::Colon)?;
4272 if arg != "seed" {
4273 return Err(self.error(&format!(
4274 "forge '{}' expects `seed:` as its argument, found `{arg}`",
4275 node.name
4276 )));
4277 }
4278 node.seed = self.consume(TokenType::StringLit)?.value;
4279 self.consume(TokenType::RParen)?;
4280 self.consume(TokenType::Arrow)?;
4282 node.output_type = self.consume_any_ident_or_kw()?.value;
4283 self.consume(TokenType::LBrace)?;
4285 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4286 let field = self.consume_any_ident_or_kw()?.value;
4287 self.consume(TokenType::Colon)?;
4288 match field.as_str() {
4289 "mode" => node.mode = self.consume_any_ident_or_kw()?.value,
4290 "novelty" => node.novelty = self.consume_number()?,
4291 "depth" => {
4292 node.depth = self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4293 }
4294 "branches" => {
4295 node.branches =
4296 self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4297 }
4298 "constraints" => node.constraints_ref = self.consume_any_ident_or_kw()?.value,
4299 other => {
4300 return Err(self.error(&format!("unknown forge field `{other}`")))
4301 }
4302 }
4303 if self.check(TokenType::Comma) {
4304 self.consume(TokenType::Comma)?;
4305 }
4306 }
4307 self.consume(TokenType::RBrace)?;
4308 Ok(node)
4309 }
4310
4311 fn parse_par_block(&mut self) -> Result<ParBlock, ParseError> {
4320 let tok = self.current().clone();
4321 self.advance(); self.consume(TokenType::LBrace)?;
4323 let mut branches: Vec<Vec<FlowStep>> = Vec::new();
4324 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4325 branches.push(vec![self.parse_flow_step()?]);
4326 }
4327 self.consume(TokenType::RBrace)?;
4328 Ok(ParBlock {
4329 branches,
4330 loc: Loc {
4331 line: tok.line,
4332 column: tok.column,
4333 },
4334 })
4335 }
4336
4337 fn parse_warden(&mut self) -> Result<WardenBlock, ParseError> {
4354 let tok = self.consume(TokenType::Warden)?;
4355 self.consume(TokenType::LParen)?;
4357 let target = self.consume_any_ident_or_kw()?.value;
4358 self.consume(TokenType::RParen)?;
4359 self.consume(TokenType::Within)?;
4361 let scope_ref = self.consume(TokenType::Identifier)?.value;
4362 let mut block = WardenBlock {
4363 target,
4364 scope_ref,
4365 body: Vec::new(),
4366 loc: Loc {
4367 line: tok.line,
4368 column: tok.column,
4369 },
4370 };
4371 self.consume(TokenType::LBrace)?;
4373 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4374 block.body.push(self.parse_flow_step()?);
4375 }
4376 self.consume(TokenType::RBrace)?;
4377 Ok(block)
4378 }
4379
4380 fn parse_scope(&mut self) -> Result<ScopeDefinition, ParseError> {
4385 let tok = self.consume(TokenType::Scope)?;
4386 let name = self.consume(TokenType::Identifier)?.value;
4387 let mut node = ScopeDefinition {
4388 name,
4389 loc: Loc {
4390 line: tok.line,
4391 column: tok.column,
4392 },
4393 ..Default::default()
4394 };
4395 self.consume(TokenType::LBrace)?;
4396 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4397 let key = self.consume_any_ident_or_kw()?.value;
4398 self.consume(TokenType::Colon)?;
4399 match key.as_str() {
4400 "targets" => {
4401 self.consume(TokenType::LBracket)?;
4402 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4403 let t = if self.check(TokenType::StringLit) {
4404 self.consume(TokenType::StringLit)?.value
4405 } else {
4406 self.consume_any_ident_or_kw()?.value
4407 };
4408 node.targets.push(t);
4409 if self.check(TokenType::Comma) {
4410 self.advance();
4411 }
4412 }
4413 self.consume(TokenType::RBracket)?;
4414 }
4415 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
4416 "approver" => {
4417 if self.current().value == "requires" {
4419 self.advance();
4420 }
4421 node.approver = self.consume(TokenType::StringLit)?.value;
4422 }
4423 other => {
4424 return Err(self.error(&format!(
4425 "unknown scope field `{other}` in scope `{}` — expected \
4426 `targets` / `depth` / `approver`",
4427 node.name
4428 )))
4429 }
4430 }
4431 if self.check(TokenType::Comma) {
4432 self.consume(TokenType::Comma)?;
4433 }
4434 }
4435 self.consume(TokenType::RBrace)?;
4436 Ok(node)
4437 }
4438
4439 fn parse_quant(&mut self) -> Result<QuantBlock, ParseError> {
4440 let tok = self.current().clone();
4441 self.advance(); let mut block = QuantBlock {
4444 encoding: None,
4445 observable: None,
4446 qubits: None,
4447 depth: None,
4448 bandwidth: None,
4449 reupload: None,
4450 effect: "quant_sim".to_string(),
4453 body: Vec::new(),
4454 loc: Loc {
4455 line: tok.line,
4456 column: tok.column,
4457 },
4458 };
4459
4460 if self.check(TokenType::LParen) {
4462 self.advance();
4463 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
4464 let key = self.consume_any_ident_or_kw()?.value;
4465 self.consume(TokenType::Colon)?;
4466 match key.as_str() {
4467 "encoding" => {
4468 block.encoding = Some(self.consume_any_ident_or_kw()?.value)
4469 }
4470 "observable" => {
4471 block.observable = Some(self.parse_dotted_identifier()?)
4472 }
4473 "qubits" => block.qubits = Some(self.consume_number()? as i64),
4474 "depth" => block.depth = Some(self.consume_number()? as i64),
4475 "bandwidth" => block.bandwidth = Some(self.consume_number()?),
4476 "reupload" => block.reupload = Some(self.consume_number()? as i64),
4478 "backend" => block.effect = self.consume_any_ident_or_kw()?.value,
4480 other => {
4481 return Err(ParseError {
4482 message: format!(
4483 "Unknown `quant` attribute `{other}` — expected one of \
4484 encoding, observable, qubits, depth, bandwidth, reupload, backend"
4485 ),
4486 line: self.current().line,
4487 column: self.current().column,
4488 ..Default::default()
4489 });
4490 }
4491 }
4492 if self.check(TokenType::Comma) {
4494 self.advance();
4495 }
4496 }
4497 self.consume(TokenType::RParen)?;
4498 }
4499
4500 self.consume(TokenType::LBrace)?;
4502 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4503 block.body.push(self.parse_flow_step()?);
4504 }
4505 self.consume(TokenType::RBrace)?;
4506
4507 Ok(block)
4508 }
4509
4510 fn parse_yield(&mut self) -> Result<YieldStatement, ParseError> {
4514 let tok = self.consume(TokenType::Yield)?;
4515 let loc = self.loc_of(&tok);
4516 self.last_let_value_kind = "literal".to_string();
4517 let value_expr = self.parse_let_value_expr()?;
4518 Ok(YieldStatement {
4519 value_expr,
4520 value_kind: self.last_let_value_kind.clone(),
4521 loc,
4522 })
4523 }
4524
4525 fn parse_compute_apply(&mut self) -> Result<ComputeApplyStep, ParseError> {
4532 let tok = self.current().clone();
4533 let loc = self.loc_of(&tok);
4534 self.advance(); let compute_name = self.consume_any_ident_or_kw()?.value.clone();
4536
4537 let mut arguments = Vec::new();
4538 if self.current().value == "on" {
4539 self.advance();
4540 loop {
4541 arguments.push(self.consume_any_ident_or_kw()?.value.clone());
4542 if self.check(TokenType::Comma) {
4543 self.advance();
4544 } else {
4545 break;
4546 }
4547 }
4548 }
4549
4550 let mut output_name = String::new();
4551 if self.check(TokenType::Arrow) {
4552 self.advance();
4553 output_name = self.consume_any_ident_or_kw()?.value.clone();
4554 }
4555
4556 Ok(ComputeApplyStep {
4557 compute_name,
4558 arguments,
4559 output_name,
4560 loc,
4561 })
4562 }
4563
4564 fn parse_apply_step(&mut self, _kw: &str) -> Result<(Loc, String, String, String), ParseError> {
4565 let tok = self.current().clone();
4566 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
4568 let mut target = String::new();
4569 let mut output_type = String::new();
4570 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4572 let next = self.current().clone();
4573 if next.value == "on" {
4574 self.advance();
4575 target = self.consume_any_ident_or_kw()?.value.clone();
4576 }
4577 }
4578 if self.check(TokenType::Arrow) {
4580 self.advance();
4581 output_type = self.consume_any_ident_or_kw()?.value.clone();
4582 }
4583 if self.check(TokenType::LBrace) {
4585 self.skip_braced_block()?;
4586 }
4587 Ok((
4588 Loc {
4589 line: tok.line,
4590 column: tok.column,
4591 },
4592 name,
4593 target,
4594 output_type,
4595 ))
4596 }
4597
4598 fn parse_weave_step(&mut self) -> Result<FlowStep, ParseError> {
4599 let tok = self.current().clone();
4600 self.advance();
4601 let mut node = WeaveStep {
4602 sources: Vec::new(),
4603 target: String::new(),
4604 format_type: String::new(),
4605 priority: Vec::new(),
4606 style: String::new(),
4607 loc: Loc {
4608 line: tok.line,
4609 column: tok.column,
4610 },
4611 };
4612 if self.check(TokenType::LBrace) {
4613 self.advance();
4614 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4615 let f = self.current().value.clone();
4616 self.advance();
4617 if self.check(TokenType::Colon) {
4618 self.advance();
4619 match f.as_str() {
4620 "sources" => node.sources = self.parse_bracketed_identifiers()?,
4621 "target" => node.target = self.consume_any_ident_or_kw()?.value.clone(),
4622 "format" => {
4623 node.format_type = self.consume_any_ident_or_kw()?.value.clone()
4624 }
4625 "priority" => node.priority = self.parse_bracketed_identifiers()?,
4626 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
4627 _ => self.skip_value(),
4628 }
4629 }
4630 }
4631 if self.check(TokenType::RBrace) {
4632 self.advance();
4633 }
4634 }
4635 Ok(FlowStep::Weave(node))
4636 }
4637
4638 fn parse_use_step(&mut self) -> Result<FlowStep, ParseError> {
4639 let tok = self.current().clone();
4640 self.advance();
4641 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
4642 let args = if self.check(TokenType::LParen) {
4653 UseArgs::Named(self.parse_named_arg_list()?)
4654 } else {
4655 let mut argument = String::new();
4656 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4657 let next = self.current().clone();
4658 if next.value == "on" {
4659 self.advance();
4660 argument = self.consume_any_ident_or_kw()?.value.clone();
4661 }
4662 }
4663 UseArgs::LegacyPositional(argument)
4664 };
4665 if self.check(TokenType::LBrace) {
4666 self.skip_braced_block()?;
4667 }
4668 Ok(FlowStep::UseTool(UseToolStep {
4669 tool_name,
4670 args,
4671 loc: Loc {
4672 line: tok.line,
4673 column: tok.column,
4674 },
4675 }))
4676 }
4677
4678 fn parse_named_arg_list(&mut self) -> Result<Vec<(String, String, String)>, ParseError> {
4684 self.consume(TokenType::LParen)?;
4685 let mut args = Vec::new();
4686 while !self.check(TokenType::RParen) {
4687 let name = self.consume_any_ident_or_kw()?.value;
4690 self.consume(TokenType::Assign)?;
4691 let value = self.parse_let_atom()?;
4692 let value_kind = self.last_let_value_kind.clone();
4696 args.push((name, value, value_kind));
4697 if self.check(TokenType::Comma) {
4698 self.advance();
4699 } else {
4700 break;
4701 }
4702 }
4703 self.consume(TokenType::RParen)?;
4704 Ok(args)
4705 }
4706
4707 fn parse_remember_step(&mut self) -> Result<FlowStep, ParseError> {
4708 let tok = self.current().clone();
4709 self.advance();
4710 let expr = self.consume_any_ident_or_kw()?.value.clone();
4711 let mut mem = String::new();
4712 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4713 let next = self.current().clone();
4714 if next.value == "in" || next.ttype == TokenType::In {
4715 self.advance();
4716 mem = self.consume_any_ident_or_kw()?.value.clone();
4717 }
4718 }
4719 Ok(FlowStep::Remember(RememberStep {
4720 expression: expr,
4721 memory_target: mem,
4722 loc: Loc {
4723 line: tok.line,
4724 column: tok.column,
4725 },
4726 }))
4727 }
4728
4729 fn parse_recall_step(&mut self) -> Result<FlowStep, ParseError> {
4730 let tok = self.current().clone();
4731 self.advance();
4732 let query = if self.check(TokenType::StringLit) {
4733 self.consume(TokenType::StringLit)?.value.clone()
4734 } else {
4735 self.consume_any_ident_or_kw()?.value.clone()
4736 };
4737 let mut mem = String::new();
4738 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4739 let next = self.current().clone();
4740 if next.value == "from" || next.ttype == TokenType::From {
4741 self.advance();
4742 mem = self.consume_any_ident_or_kw()?.value.clone();
4743 }
4744 }
4745 Ok(FlowStep::Recall(RecallStep {
4746 query,
4747 memory_source: mem,
4748 loc: Loc {
4749 line: tok.line,
4750 column: tok.column,
4751 },
4752 }))
4753 }
4754
4755 fn parse_hibernate_step(&mut self) -> Result<FlowStep, ParseError> {
4756 let tok = self.current().clone();
4757 self.advance();
4758 let mut event = String::new();
4759 let mut timeout = String::new();
4760 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4761 event = self.consume_any_ident_or_kw()?.value.clone();
4762 }
4763 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4764 let next = self.current().clone();
4765 if next.ttype == TokenType::Duration {
4766 self.advance();
4767 timeout = next.value.clone();
4768 }
4769 }
4770 Ok(FlowStep::Hibernate(HibernateStep {
4771 event_name: event,
4772 timeout,
4773 loc: Loc {
4774 line: tok.line,
4775 column: tok.column,
4776 },
4777 }))
4778 }
4779
4780 fn parse_grad_step(&mut self) -> Result<FlowStep, ParseError> {
4789 let tok = self.current().clone();
4790 self.advance();
4791 let target = self.consume_any_ident_or_kw()?.value.clone();
4792 let mut wrt: Vec<String> = Vec::new();
4793 let mut output = String::new();
4794 if !self.at_declaration_start() && self.current().value == "wrt" {
4795 self.advance();
4796 if self.check(TokenType::LBracket) {
4797 wrt = self.parse_bracketed_identifiers()?;
4798 } else {
4799 wrt.push(self.consume_any_ident_or_kw()?.value.clone());
4800 }
4801 }
4802 if !self.at_declaration_start() && self.current().value == "as" {
4803 self.advance();
4804 output = self.consume_any_ident_or_kw()?.value.clone();
4805 }
4806 Ok(FlowStep::Grad(GradStep {
4807 target,
4808 wrt,
4809 output,
4810 loc: Loc {
4811 line: tok.line,
4812 column: tok.column,
4813 },
4814 }))
4815 }
4816
4817 fn parse_focus_step(&mut self) -> Result<FlowStep, ParseError> {
4818 let tok = self.current().clone();
4819 self.advance();
4820 let expression = if self.at_declaration_start()
4821 || self.check(TokenType::RBrace)
4822 || self.check(TokenType::Eof)
4823 {
4824 String::new()
4825 } else {
4826 self.consume_any_ident_or_kw()?.value.clone()
4827 };
4828 let mut where_expr = String::new();
4829 let mut select: Vec<String> = Vec::new();
4830 let mut output = String::new();
4831 if self.check(TokenType::LBrace) {
4832 self.advance();
4833 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4834 if self.check(TokenType::Comma) {
4835 self.advance();
4836 continue;
4837 }
4838 let f = self.current().value.clone();
4839 self.advance();
4840 if self.check(TokenType::Colon) {
4841 self.advance();
4842 match f.as_str() {
4843 "where" => {
4844 where_expr = self.consume(TokenType::StringLit)?.value.clone()
4845 }
4846 "select" => select = self.parse_bracketed_identifiers()?,
4847 "as" | "alias" => {
4848 output = self.consume_any_ident_or_kw()?.value.clone()
4849 }
4850 _ => self.skip_value(),
4851 }
4852 }
4853 }
4854 if self.check(TokenType::RBrace) {
4855 self.advance();
4856 }
4857 }
4858 Ok(FlowStep::Focus(FocusStep {
4859 expression,
4860 where_expr,
4861 select,
4862 output,
4863 loc: Loc {
4864 line: tok.line,
4865 column: tok.column,
4866 },
4867 }))
4868 }
4869
4870 fn parse_associate_step(&mut self) -> Result<FlowStep, ParseError> {
4871 let tok = self.current().clone();
4872 self.advance();
4873 let left = self.consume_any_ident_or_kw()?.value.clone();
4874 let mut right = String::new();
4875 let mut using = String::new();
4876 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4877 right = self.consume_any_ident_or_kw()?.value.clone();
4878 }
4879 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4880 let next = self.current().clone();
4881 if next.value == "using" {
4882 self.advance();
4883 using = self.consume_any_ident_or_kw()?.value.clone();
4884 }
4885 }
4886 let mut output = String::new();
4887 if self.check(TokenType::LBrace) {
4888 self.advance();
4889 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4890 let f = self.current().value.clone();
4891 self.advance();
4892 if self.check(TokenType::Colon) {
4893 self.advance();
4894 match f.as_str() {
4895 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
4896 _ => self.skip_value(),
4897 }
4898 }
4899 }
4900 if self.check(TokenType::RBrace) {
4901 self.advance();
4902 }
4903 }
4904 Ok(FlowStep::Associate(AssociateStep {
4905 left,
4906 right,
4907 using_field: using,
4908 output,
4909 loc: Loc {
4910 line: tok.line,
4911 column: tok.column,
4912 },
4913 }))
4914 }
4915
4916 fn parse_aggregate_step(&mut self) -> Result<FlowStep, ParseError> {
4917 let tok = self.current().clone();
4918 self.advance();
4919 let target = self.consume_any_ident_or_kw()?.value.clone();
4920 let mut group_by = Vec::new();
4921 let mut alias = String::new();
4922 let mut compute: Vec<String> = Vec::new();
4923 let mut where_expr = String::new();
4924 if self.check(TokenType::LBrace) {
4925 self.advance();
4926 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4927 let f = self.current().value.clone();
4928 self.advance();
4929 if self.check(TokenType::Colon) {
4930 self.advance();
4931 match f.as_str() {
4932 "group_by" => group_by = self.parse_bracketed_identifiers()?,
4933 "alias" | "as" => alias = self.consume_any_ident_or_kw()?.value.clone(),
4934 "compute" => compute = self.parse_bracketed_aggregates()?,
4937 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
4939 _ => self.skip_value(),
4940 }
4941 }
4942 }
4943 if self.check(TokenType::RBrace) {
4944 self.advance();
4945 }
4946 }
4947 Ok(FlowStep::Aggregate(AggregateStep {
4948 target,
4949 group_by,
4950 alias,
4951 compute,
4952 where_expr,
4953 loc: Loc {
4954 line: tok.line,
4955 column: tok.column,
4956 },
4957 }))
4958 }
4959
4960 fn parse_explore_step(&mut self) -> Result<FlowStep, ParseError> {
4961 let tok = self.current().clone();
4962 self.advance();
4963 let target = self.consume_any_ident_or_kw()?.value.clone();
4964 let mut limit = None;
4965 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4966 if self.current().ttype == TokenType::Integer {
4967 limit = self.current().value.parse::<i64>().ok();
4968 self.advance();
4969 }
4970 }
4971 let mut output = String::new();
4972 if self.check(TokenType::LBrace) {
4973 self.advance();
4974 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4975 let f = self.current().value.clone();
4976 self.advance();
4977 if self.check(TokenType::Colon) {
4978 self.advance();
4979 match f.as_str() {
4980 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
4981 _ => self.skip_value(),
4982 }
4983 }
4984 }
4985 if self.check(TokenType::RBrace) {
4986 self.advance();
4987 }
4988 }
4989 Ok(FlowStep::ExploreStep(ExploreStepNode {
4990 target,
4991 limit,
4992 output,
4993 loc: Loc {
4994 line: tok.line,
4995 column: tok.column,
4996 },
4997 }))
4998 }
4999
5000 fn parse_bracketed_aggregates(&mut self) -> Result<Vec<String>, ParseError> {
5003 let mut out = Vec::new();
5004 self.consume(TokenType::LBracket)?;
5005 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5006 let name = self.consume_any_ident_or_kw()?.value.clone();
5007 if self.check(TokenType::LParen) {
5008 self.advance();
5009 let col = self.consume_any_ident_or_kw()?.value.clone();
5010 self.consume(TokenType::RParen)?;
5011 out.push(format!("{name}({col})"));
5012 } else {
5013 out.push(name);
5014 }
5015 if self.check(TokenType::Comma) {
5016 self.advance();
5017 }
5018 }
5019 self.consume(TokenType::RBracket)?;
5020 Ok(out)
5021 }
5022
5023 fn parse_ingest_step(&mut self) -> Result<FlowStep, ParseError> {
5038 let tok = self.current().clone();
5039 self.advance();
5040 let source = self.consume_any_ident_or_kw()?.value.clone();
5041 let mut target = String::new();
5042 let mut format = String::new();
5043 let mut max_bytes: Option<u64> = None;
5044 let mut max_rows: Option<u64> = None;
5045 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5046 let next = self.current().clone();
5047 if next.value == "into" || next.ttype == TokenType::Into {
5048 self.advance();
5049 target = self.consume_any_ident_or_kw()?.value.clone();
5050 }
5051 }
5052 if self.check(TokenType::LBrace) {
5053 self.consume(TokenType::LBrace)?;
5054 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5055 if self.check(TokenType::Comma) {
5057 self.advance();
5058 continue;
5059 }
5060 let entry = self.current().clone();
5061 match entry.value.as_str() {
5062 "format" => {
5063 self.advance();
5064 self.consume(TokenType::Colon)?;
5065 format = self.consume_any_ident_or_kw()?.value.clone();
5066 }
5067 "limits" => {
5068 self.advance();
5069 self.consume(TokenType::LBrace)?;
5070 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5071 let bound = self.current().clone();
5072 self.advance();
5073 self.consume(TokenType::Colon)?;
5074 let num_tok = self.consume(TokenType::Integer)?.clone();
5075 let value = num_tok.value.parse::<u64>().map_err(|_| ParseError {
5076 message: format!(
5077 "ingest `limits` bound `{}` must be a non-negative \
5078 integer byte/row count, got `{}`.",
5079 bound.value, num_tok.value
5080 ),
5081 line: num_tok.line,
5082 column: num_tok.column,
5083 ..Default::default()
5084 })?;
5085 match bound.value.as_str() {
5086 "max_bytes" => max_bytes = Some(value),
5087 "max_rows" => max_rows = Some(value),
5088 other => {
5089 return Err(ParseError {
5090 message: format!(
5091 "Unknown ingest limit `{other}`. The closed \
5092 limits grammar is `max_bytes: <N>` and \
5093 `max_rows: <N>` — bounds enforced on the raw \
5094 stream BEFORE parsing (§100).",
5095 ),
5096 line: bound.line,
5097 column: bound.column,
5098 ..Default::default()
5099 });
5100 }
5101 }
5102 if self.check(TokenType::Comma) {
5103 self.advance();
5104 }
5105 }
5106 self.consume(TokenType::RBrace)?;
5107 }
5108 other => {
5109 return Err(ParseError {
5110 message: format!(
5111 "Unknown entry `{other}` in ingest body. The closed \
5112 grammar is `format: csv|json` and \
5113 `limits {{ max_bytes: <N>, max_rows: <N> }}`.",
5114 ),
5115 line: entry.line,
5116 column: entry.column,
5117 ..Default::default()
5118 });
5119 }
5120 }
5121 }
5122 self.consume(TokenType::RBrace)?;
5123 }
5124 Ok(FlowStep::Ingest(IngestStep {
5125 source,
5126 target,
5127 format,
5128 max_bytes,
5129 max_rows,
5130 loc: Loc {
5131 line: tok.line,
5132 column: tok.column,
5133 },
5134 }))
5135 }
5136
5137 fn parse_navigate_step(&mut self) -> Result<FlowStep, ParseError> {
5138 let tok = self.current().clone();
5139 self.advance();
5140 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
5141 let mut node = NavigateStep {
5142 pix_name,
5143 corpus_name: String::new(),
5144 query_expr: String::new(),
5145 trail_enabled: false,
5146 output_name: String::new(),
5147 seed: String::new(),
5148 budget: None,
5149 where_expr: String::new(),
5150 loc: Loc {
5151 line: tok.line,
5152 column: tok.column,
5153 },
5154 };
5155 if self.check(TokenType::LBrace) {
5156 self.advance();
5157 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5158 let f = self.current().value.clone();
5159 self.advance();
5160 if self.check(TokenType::Colon) {
5161 self.advance();
5162 match f.as_str() {
5163 "corpus" => {
5164 node.corpus_name = self.consume_any_ident_or_kw()?.value.clone()
5165 }
5166 "query" => {
5167 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
5168 }
5169 "trail" => {
5170 node.trail_enabled = self.consume_any_ident_or_kw()?.value == "true"
5171 }
5172 "output" | "as" => {
5173 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5174 }
5175 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
5177 "budget" => node.budget = self.parse_optional_int(),
5178 "where" => {
5186 node.where_expr = self.consume(TokenType::StringLit)?.value.clone()
5187 }
5188 _ => self.skip_value(),
5189 }
5190 }
5191 }
5192 if self.check(TokenType::RBrace) {
5193 self.advance();
5194 }
5195 }
5196 Ok(FlowStep::Navigate(node))
5197 }
5198
5199 fn parse_drill_step(&mut self) -> Result<FlowStep, ParseError> {
5200 let tok = self.current().clone();
5201 self.advance();
5202 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
5203 let mut node = DrillStep {
5204 pix_name,
5205 subtree_path: String::new(),
5206 query_expr: String::new(),
5207 output_name: String::new(),
5208 loc: Loc {
5209 line: tok.line,
5210 column: tok.column,
5211 },
5212 };
5213 if self.check(TokenType::LBrace) {
5214 self.advance();
5215 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5216 let f = self.current().value.clone();
5217 self.advance();
5218 if self.check(TokenType::Colon) {
5219 self.advance();
5220 match f.as_str() {
5221 "subtree" | "path" => {
5222 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
5223 }
5224 "query" => {
5225 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
5226 }
5227 "output" | "as" => {
5228 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5229 }
5230 _ => self.skip_value(),
5231 }
5232 }
5233 }
5234 if self.check(TokenType::RBrace) {
5235 self.advance();
5236 }
5237 }
5238 Ok(FlowStep::Drill(node))
5239 }
5240
5241 fn parse_corroborate_step(&mut self) -> Result<FlowStep, ParseError> {
5242 let tok = self.current().clone();
5243 self.advance();
5244 let nav_ref = self.consume_any_ident_or_kw()?.value.clone();
5245 let mut output = String::new();
5246 if self.check(TokenType::Arrow) {
5247 self.advance();
5248 output = self.consume_any_ident_or_kw()?.value.clone();
5249 }
5250 Ok(FlowStep::Corroborate(CorroborateStep {
5251 navigate_ref: nav_ref,
5252 output_name: output,
5253 loc: Loc {
5254 line: tok.line,
5255 column: tok.column,
5256 },
5257 }))
5258 }
5259
5260 fn parse_listen_step(&mut self) -> Result<FlowStep, ParseError> {
5261 let tok = self.current().clone();
5262 self.advance();
5263 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
5267 (self.consume(TokenType::StringLit)?.value.clone(), false)
5268 } else {
5269 (self.consume_any_ident_or_kw()?.value.clone(), true)
5270 };
5271 let mut alias = String::new();
5272 if !self.at_declaration_start()
5273 && !self.check(TokenType::RBrace)
5274 && !self.check(TokenType::LBrace)
5275 {
5276 let next = self.current().clone();
5277 if next.value == "as" || next.ttype == TokenType::As {
5278 self.advance();
5279 alias = self.consume_any_ident_or_kw()?.value.clone();
5280 }
5281 }
5282 let body = self.parse_listener_body()?;
5286 Ok(FlowStep::Listen(ListenStep {
5287 channel,
5288 channel_is_ref,
5289 event_alias: alias,
5290 body,
5291 loc: Loc {
5292 line: tok.line,
5293 column: tok.column,
5294 },
5295 }))
5296 }
5297
5298 fn parse_listener_body(&mut self) -> Result<Vec<FlowStep>, ParseError> {
5303 let mut body = Vec::new();
5304 if self.check(TokenType::LBrace) {
5305 self.advance(); while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5307 body.push(self.parse_flow_step()?);
5308 }
5309 self.consume(TokenType::RBrace)?;
5310 }
5311 Ok(body)
5312 }
5313
5314 fn parse_retrieve_step(&mut self) -> Result<FlowStep, ParseError> {
5315 let tok = self.current().clone();
5316 self.advance();
5317 let store = self.consume_any_ident_or_kw()?.value.clone();
5318 let mut where_expr = String::new();
5319 let mut alias = String::new();
5320 let mut order_by = String::new();
5321 let mut limit_expr = String::new();
5322 let mut aggregate = String::new();
5323 let mut group_by = String::new();
5324 let mut cache = String::new();
5325 if self.check(TokenType::LBrace) {
5326 self.advance();
5327 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5328 let f = self.current().value.clone();
5329 self.advance();
5330 if self.check(TokenType::Colon) {
5331 self.advance();
5332 match f.as_str() {
5333 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
5334 "as" | "alias" => alias = self.consume_any_ident_or_kw()?.value.clone(),
5335 "order_by" => {
5338 order_by = self.consume(TokenType::StringLit)?.value.clone()
5339 }
5340 "limit" => {
5345 let t = self.current().clone();
5346 match t.ttype {
5347 TokenType::Integer | TokenType::StringLit => {
5348 limit_expr = t.value.clone();
5349 self.advance();
5350 }
5351 _ => self.skip_value(),
5352 }
5353 }
5354 "aggregate" => {
5361 aggregate = self.consume(TokenType::StringLit)?.value.clone()
5362 }
5363 "group_by" => {
5364 group_by = self.consume(TokenType::StringLit)?.value.clone()
5365 }
5366 "cache" => cache = self.consume_any_ident_or_kw()?.value.clone(),
5372 _ => self.skip_value(),
5373 }
5374 }
5375 }
5376 if self.check(TokenType::RBrace) {
5377 self.advance();
5378 }
5379 }
5380 Ok(FlowStep::Retrieve(RetrieveStep {
5381 store_name: store,
5382 where_expr,
5383 alias,
5384 order_by,
5385 limit_expr,
5386 aggregate,
5387 group_by,
5388 cache,
5389 loc: Loc {
5390 line: tok.line,
5391 column: tok.column,
5392 },
5393 }))
5394 }
5395
5396 fn parse_store_where_step(
5409 &mut self,
5410 ) -> Result<(Loc, String, String), ParseError> {
5411 let tok = self.current().clone();
5412 self.advance(); let store = if self.at_declaration_start()
5414 || self.check(TokenType::RBrace)
5415 || self.check(TokenType::Eof)
5416 {
5417 String::new()
5418 } else {
5419 self.consume_any_ident_or_kw()?.value.clone()
5420 };
5421 let mut where_expr = String::new();
5422 if self.check(TokenType::LBrace) {
5423 self.advance();
5424 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5425 let field = self.current().value.clone();
5426 self.advance();
5427 if self.check(TokenType::Colon) {
5428 self.advance();
5429 match field.as_str() {
5430 "where" => {
5431 where_expr =
5432 self.consume(TokenType::StringLit)?.value.clone()
5433 }
5434 _ => self.skip_value(),
5435 }
5436 }
5437 }
5438 if self.check(TokenType::RBrace) {
5439 self.advance();
5440 }
5441 }
5442 Ok((
5443 Loc {
5444 line: tok.line,
5445 column: tok.column,
5446 },
5447 store,
5448 where_expr,
5449 ))
5450 }
5451
5452 fn parse_persist_step(&mut self) -> Result<FlowStep, ParseError> {
5471 let tok = self.current().clone();
5472 self.advance(); if self.current().value == "into" && !self.check(TokenType::LBrace) {
5476 self.advance();
5477 }
5478 let store = if self.at_declaration_start()
5479 || self.check(TokenType::LBrace)
5480 || self.check(TokenType::RBrace)
5481 || self.check(TokenType::Eof)
5482 {
5483 String::new()
5484 } else {
5485 self.consume_any_ident_or_kw()?.value.clone()
5486 };
5487 let mut fields: Vec<(String, String)> = Vec::new();
5488 if self.check(TokenType::LBrace) {
5489 self.advance();
5490 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5491 let col = self.current().value.clone();
5492 self.advance();
5493 if self.check(TokenType::Colon) {
5494 self.advance();
5495 let value = if self.check(TokenType::StringLit) {
5496 self.consume(TokenType::StringLit)?.value.clone()
5497 } else if self.check(TokenType::RBrace)
5498 || self.check(TokenType::Eof)
5499 || self.check(TokenType::Colon)
5500 {
5501 String::new()
5502 } else {
5503 let v = self.current().clone();
5504 self.advance();
5505 v.value.clone()
5506 };
5507 fields.push((col, value));
5508 }
5509 }
5510 if self.check(TokenType::RBrace) {
5511 self.advance();
5512 }
5513 }
5514 Ok(FlowStep::Persist(PersistStep {
5515 store_name: store,
5516 fields,
5517 loc: Loc {
5518 line: tok.line,
5519 column: tok.column,
5520 },
5521 }))
5522 }
5523
5524 fn parse_mutate_step(&mut self) -> Result<FlowStep, ParseError> {
5538 let tok = self.current().clone();
5539 self.advance(); let store = if self.at_declaration_start()
5541 || self.check(TokenType::LBrace)
5542 || self.check(TokenType::RBrace)
5543 || self.check(TokenType::Eof)
5544 {
5545 String::new()
5546 } else {
5547 self.consume_any_ident_or_kw()?.value.clone()
5548 };
5549 let mut where_expr = String::new();
5550 let mut fields: Vec<(String, String)> = Vec::new();
5551 if self.check(TokenType::LBrace) {
5552 self.advance();
5553 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5554 let key = self.current().value.clone();
5555 self.advance();
5556 if self.check(TokenType::Colon) {
5557 self.advance();
5558 if key == "where" {
5559 where_expr =
5560 self.consume(TokenType::StringLit)?.value.clone();
5561 } else {
5562 let value = if self.check(TokenType::StringLit) {
5563 self.consume(TokenType::StringLit)?.value.clone()
5564 } else if self.check(TokenType::RBrace)
5565 || self.check(TokenType::Eof)
5566 || self.check(TokenType::Colon)
5567 {
5568 String::new()
5569 } else {
5570 let v = self.current().clone();
5571 self.advance();
5572 v.value.clone()
5573 };
5574 fields.push((key, value));
5575 }
5576 }
5577 }
5578 if self.check(TokenType::RBrace) {
5579 self.advance();
5580 }
5581 }
5582 Ok(FlowStep::Mutate(MutateStep {
5583 store_name: store,
5584 where_expr,
5585 fields,
5586 loc: Loc {
5587 line: tok.line,
5588 column: tok.column,
5589 },
5590 }))
5591 }
5592
5593 fn parse_agent(&mut self) -> Result<AgentDefinition, ParseError> {
5596 let tok = self.consume(TokenType::Agent)?;
5597 let name = self.consume(TokenType::Identifier)?.value;
5598 let mut node = AgentDefinition {
5599 name,
5600 goal: String::new(),
5601 tools: Vec::new(),
5602 memory_ref: String::new(),
5603 strategy: String::new(),
5604 on_stuck: String::new(),
5605 shield_ref: String::new(),
5606 max_iterations: None,
5607 max_tokens: None,
5608 max_time: String::new(),
5609 max_cost: None,
5610 loc: Loc {
5611 line: tok.line,
5612 column: tok.column,
5613 },
5614 leading_trivia: Vec::new(),
5615 trailing_trivia: Vec::new(),
5616 };
5617 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
5619 self.advance();
5620 }
5621 self.consume(TokenType::LBrace)?;
5622 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5623 let field = self.current().clone();
5624 let field_name = field.value.clone();
5625 self.advance();
5626 if self.check(TokenType::Colon) {
5627 self.advance();
5628 match field_name.as_str() {
5629 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
5630 "tools" => node.tools = self.parse_bracketed_identifiers()?,
5631 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
5632 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
5633 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
5634 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
5635 "max_iterations" => node.max_iterations = self.parse_optional_int(),
5636 "max_tokens" => node.max_tokens = self.parse_optional_int(),
5637 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
5638 "max_cost" => node.max_cost = self.parse_optional_float(),
5639 _ => self.skip_value(),
5640 }
5641 } else if self.check(TokenType::LBrace) {
5642 self.skip_braced_block()?;
5643 }
5644 }
5645 self.consume(TokenType::RBrace)?;
5646 Ok(node)
5647 }
5648
5649 fn parse_extension(&mut self) -> Result<ExtensionDefinition, ParseError> {
5654 let tok = self.consume(TokenType::Extension)?;
5655 let name = self.consume(TokenType::Identifier)?.value;
5656 let mut node = ExtensionDefinition {
5657 name,
5658 category: String::new(),
5659 members: Vec::new(),
5660 loc: Loc {
5661 line: tok.line,
5662 column: tok.column,
5663 },
5664 leading_trivia: Vec::new(),
5665 trailing_trivia: Vec::new(),
5666 };
5667 self.consume(TokenType::LBrace)?;
5668 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5669 let field_name = self.current().value.clone();
5670 self.advance();
5671 if self.check(TokenType::Colon) {
5672 self.advance();
5673 match field_name.as_str() {
5674 "category" => {
5675 node.category = self.consume_any_ident_or_kw()?.value.clone()
5676 }
5677 "members" => node.members = self.parse_extension_members()?,
5678 _ => self.skip_value(),
5679 }
5680 } else if self.check(TokenType::LBrace) {
5681 self.skip_braced_block()?;
5682 }
5683 }
5684 self.consume(TokenType::RBrace)?;
5685 Ok(node)
5686 }
5687
5688 fn parse_extension_members(&mut self) -> Result<Vec<ExtensionMember>, ParseError> {
5692 let mut members = Vec::new();
5693 self.consume(TokenType::LBracket)?;
5694 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5695 let name_tok = self.consume(TokenType::StringLit)?;
5696 let mut member = ExtensionMember {
5697 name: name_tok.value.clone(),
5698 semantics: None,
5699 default_confidence: None,
5700 loc: Loc {
5701 line: name_tok.line,
5702 column: name_tok.column,
5703 },
5704 };
5705 if self.check(TokenType::Colon) {
5707 self.advance();
5708 self.consume(TokenType::LBrace)?;
5709 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5710 let mkey = self.current().value.clone();
5711 self.advance();
5712 if self.check(TokenType::Colon) {
5713 self.advance();
5714 match mkey.as_str() {
5715 "semantics" => {
5716 member.semantics =
5717 Some(self.consume(TokenType::StringLit)?.value.clone())
5718 }
5719 "default_confidence" => {
5720 member.default_confidence = self.parse_optional_float()
5721 }
5722 _ => self.skip_value(),
5723 }
5724 }
5725 if self.check(TokenType::Comma) {
5726 self.advance();
5727 }
5728 }
5729 self.consume(TokenType::RBrace)?;
5730 }
5731 members.push(member);
5732 if self.check(TokenType::Comma) {
5733 self.advance();
5734 }
5735 }
5736 self.consume(TokenType::RBracket)?;
5737 Ok(members)
5738 }
5739
5740 fn parse_window(&mut self) -> Result<WindowDefinition, ParseError> {
5743 let tok = self.consume(TokenType::Window)?;
5744 let name = self.consume(TokenType::Identifier)?.value;
5745 let mut node = WindowDefinition {
5746 name,
5747 timezone: String::new(),
5748 allow: Vec::new(),
5749 exclude: Vec::new(),
5750 on_outside: String::new(),
5751 loc: Loc {
5752 line: tok.line,
5753 column: tok.column,
5754 },
5755 leading_trivia: Vec::new(),
5756 trailing_trivia: Vec::new(),
5757 };
5758 self.consume(TokenType::LBrace)?;
5759 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5760 let field_name = self.consume_any_ident_or_kw()?.value;
5761 self.consume(TokenType::Colon)?;
5762 match field_name.as_str() {
5763 "timezone" => node.timezone = self.consume(TokenType::StringLit)?.value,
5764 "allow" => node.allow = self.parse_window_allow()?,
5765 "exclude" => node.exclude = self.parse_window_exclude()?,
5766 "on_outside" => node.on_outside = self.consume_any_ident_or_kw()?.value,
5767 _ => self.skip_value(),
5768 }
5769 }
5770 self.consume(TokenType::RBrace)?;
5771 Ok(node)
5772 }
5773
5774 fn parse_window_allow(&mut self) -> Result<Vec<WindowSpan>, ParseError> {
5776 self.consume(TokenType::LBracket)?;
5777 let mut spans = Vec::new();
5778 if !self.check(TokenType::RBracket) {
5779 spans.push(self.parse_window_span()?);
5780 while self.check(TokenType::Comma) {
5781 self.advance();
5782 if self.check(TokenType::RBracket) {
5783 break; }
5785 spans.push(self.parse_window_span()?);
5786 }
5787 }
5788 self.consume(TokenType::RBracket)?;
5789 Ok(spans)
5790 }
5791
5792 fn parse_window_exclude(&mut self) -> Result<Vec<String>, ParseError> {
5796 self.consume(TokenType::LBracket)?;
5797 let mut dates = Vec::new();
5798 if !self.check(TokenType::RBracket) {
5799 dates.push(self.consume(TokenType::StringLit)?.value);
5800 while self.check(TokenType::Comma) {
5801 self.advance();
5802 if self.check(TokenType::RBracket) {
5803 break; }
5805 dates.push(self.consume(TokenType::StringLit)?.value);
5806 }
5807 }
5808 self.consume(TokenType::RBracket)?;
5809 Ok(dates)
5810 }
5811
5812 fn parse_window_span(&mut self) -> Result<WindowSpan, ParseError> {
5814 let tok = self.consume(TokenType::LBrace)?;
5815 let mut span = WindowSpan {
5816 day_start: String::new(),
5817 day_end: String::new(),
5818 hour_start: 0,
5819 hour_end: 0,
5820 loc: Loc {
5821 line: tok.line,
5822 column: tok.column,
5823 },
5824 };
5825 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5826 let field = self.consume_any_ident_or_kw()?.value;
5827 self.consume(TokenType::Colon)?;
5828 match field.as_str() {
5829 "days" => {
5830 span.day_start = self.consume_any_ident_or_kw()?.value;
5831 self.consume(TokenType::DotDot)?;
5832 span.day_end = self.consume_any_ident_or_kw()?.value;
5833 }
5834 "hours" => {
5835 span.hour_start = self.consume_number()? as i64;
5836 self.consume(TokenType::DotDot)?;
5837 span.hour_end = self.consume_number()? as i64;
5838 }
5839 _ => self.skip_value(),
5840 }
5841 if self.check(TokenType::Comma) {
5842 self.advance();
5843 }
5844 }
5845 self.consume(TokenType::RBrace)?;
5846 Ok(span)
5847 }
5848
5849 fn parse_shield(&mut self) -> Result<ShieldDefinition, ParseError> {
5850 let tok = self.consume(TokenType::Shield)?;
5851 let name = self.consume(TokenType::Identifier)?.value;
5852 let mut node = ShieldDefinition {
5853 name,
5854 scan: Vec::new(),
5855 strategy: String::new(),
5856 on_breach: String::new(),
5857 severity: String::new(),
5858 quarantine: String::new(),
5859 max_retries: None,
5860 confidence_threshold: None,
5861 allow_tools: Vec::new(),
5862 deny_tools: Vec::new(),
5863 sandbox: None,
5864 redact: Vec::new(),
5865 log: String::new(),
5866 deflect_message: String::new(),
5867 taint: String::new(),
5868 compliance: Vec::new(),
5869 sign: String::new(),
5870 unknown_fields: Vec::new(),
5871 loc: Loc {
5872 line: tok.line,
5873 column: tok.column,
5874 },
5875 leading_trivia: Vec::new(),
5876 trailing_trivia: Vec::new(),
5877 };
5878 self.consume(TokenType::LBrace)?;
5879 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5880 let field_name = self.current().value.clone();
5881 let field_loc = Loc {
5882 line: self.current().line,
5883 column: self.current().column,
5884 };
5885 self.advance();
5886 if self.check(TokenType::Colon) {
5887 self.advance();
5888 match field_name.as_str() {
5889 "scan" => node.scan = self.parse_bracketed_identifiers()?,
5890 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
5891 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
5892 "severity" => node.severity = self.consume_any_ident_or_kw()?.value.clone(),
5893 "quarantine" => {
5894 node.quarantine = self.consume(TokenType::StringLit)?.value.clone()
5895 }
5896 "max_retries" => node.max_retries = self.parse_optional_int(),
5897 "confidence_threshold" => {
5898 node.confidence_threshold = self.parse_optional_float()
5899 }
5900 "allow_tools" => node.allow_tools = self.parse_bracketed_identifiers()?,
5901 "deny_tools" => node.deny_tools = self.parse_bracketed_identifiers()?,
5902 "sandbox" => {
5903 node.sandbox = Some(self.consume_any_ident_or_kw()?.value == "true")
5904 }
5905 "redact" => node.redact = self.parse_bracketed_identifiers()?,
5906 "log" => node.log = self.consume_any_ident_or_kw()?.value.clone(),
5907 "deflect_message" => {
5908 node.deflect_message = self.consume(TokenType::StringLit)?.value.clone()
5909 }
5910 "taint" => node.taint = self.consume_any_ident_or_kw()?.value.clone(),
5911 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
5913 "sign" => node.sign = self.consume_any_ident_or_kw()?.value.clone(),
5916 _ => {
5920 node.unknown_fields.push((field_name.clone(), field_loc));
5921 self.skip_value()
5922 }
5923 }
5924 } else if self.check(TokenType::LBrace) {
5925 self.skip_braced_block()?;
5926 }
5927 }
5928 self.consume(TokenType::RBrace)?;
5929 Ok(node)
5930 }
5931
5932 fn parse_pix(&mut self) -> Result<PixDefinition, ParseError> {
5933 let tok = self.consume(TokenType::Pix)?;
5934 let name = self.consume(TokenType::Identifier)?.value;
5935 let mut node = PixDefinition {
5936 name,
5937 source: String::new(),
5938 depth: None,
5939 branching: None,
5940 model: String::new(),
5941 loc: Loc {
5942 line: tok.line,
5943 column: tok.column,
5944 },
5945 leading_trivia: Vec::new(),
5946 trailing_trivia: Vec::new(),
5947 };
5948 self.consume(TokenType::LBrace)?;
5949 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5950 let field_name = self.current().value.clone();
5951 self.advance();
5952 if self.check(TokenType::Colon) {
5953 self.advance();
5954 match field_name.as_str() {
5955 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
5956 "depth" => node.depth = self.parse_optional_int(),
5957 "branching" => node.branching = self.parse_optional_int(),
5958 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
5959 _ => self.skip_value(),
5960 }
5961 } else if self.check(TokenType::LBrace) {
5962 self.skip_braced_block()?;
5963 }
5964 }
5965 self.consume(TokenType::RBrace)?;
5966 Ok(node)
5967 }
5968
5969 fn parse_ledger(&mut self) -> Result<LedgerDefinition, ParseError> {
5975 let tok = self.consume(TokenType::Ledger)?;
5976 let name = self.consume(TokenType::Identifier)?.value;
5977 let mut node = LedgerDefinition {
5978 name,
5979 source: String::new(),
5980 depth: None,
5981 branching: None,
5982 model: String::new(),
5983 loc: Loc {
5984 line: tok.line,
5985 column: tok.column,
5986 },
5987 leading_trivia: Vec::new(),
5988 trailing_trivia: Vec::new(),
5989 };
5990 self.consume(TokenType::LBrace)?;
5991 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5992 let field_name = self.current().value.clone();
5993 self.advance();
5994 if self.check(TokenType::Colon) {
5995 self.advance();
5996 match field_name.as_str() {
5997 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
5998 "depth" => node.depth = self.parse_optional_int(),
5999 "branching" => node.branching = self.parse_optional_int(),
6000 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
6001 _ => self.skip_value(),
6002 }
6003 } else if self.check(TokenType::LBrace) {
6004 self.skip_braced_block()?;
6005 }
6006 }
6007 self.consume(TokenType::RBrace)?;
6008 Ok(node)
6009 }
6010
6011 fn parse_psyche(&mut self) -> Result<PsycheDefinition, ParseError> {
6012 let tok = self.consume(TokenType::Psyche)?;
6013 let name = self.consume(TokenType::Identifier)?.value;
6014 let mut node = PsycheDefinition {
6015 name,
6016 dimensions: Vec::new(),
6017 manifold_noise: None,
6018 manifold_momentum: None,
6019 safety_constraints: Vec::new(),
6020 quantum_enabled: None,
6021 inference_mode: String::new(),
6022 loc: Loc {
6023 line: tok.line,
6024 column: tok.column,
6025 },
6026 leading_trivia: Vec::new(),
6027 trailing_trivia: Vec::new(),
6028 };
6029 self.consume(TokenType::LBrace)?;
6030 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6031 let field_name = self.current().value.clone();
6032 self.advance();
6033 if self.check(TokenType::Colon) {
6034 self.advance();
6035 match field_name.as_str() {
6036 "dimensions" => node.dimensions = self.parse_bracketed_identifiers()?,
6037 "manifold_noise" => node.manifold_noise = self.parse_optional_float(),
6038 "manifold_momentum" => node.manifold_momentum = self.parse_optional_float(),
6039 "safety_constraints" => {
6040 node.safety_constraints = self.parse_bracketed_identifiers()?
6041 }
6042 "quantum_enabled" => {
6043 node.quantum_enabled = Some(self.consume_any_ident_or_kw()?.value == "true")
6044 }
6045 "inference_mode" => {
6046 node.inference_mode = self.consume_any_ident_or_kw()?.value.clone()
6047 }
6048 _ => self.skip_value(),
6049 }
6050 } else if self.check(TokenType::LBrace) {
6051 self.skip_braced_block()?;
6052 }
6053 }
6054 self.consume(TokenType::RBrace)?;
6055 Ok(node)
6056 }
6057
6058 fn parse_corpus(&mut self) -> Result<CorpusDefinition, ParseError> {
6059 let tok = self.consume(TokenType::Corpus)?;
6060 let name = self.consume(TokenType::Identifier)?.value;
6061 let mut node = CorpusDefinition {
6062 name,
6063 documents: Vec::new(),
6064 relations: Vec::new(),
6065 adaptive: false,
6066 mcp_server: String::new(),
6067 mcp_resource_uri: String::new(),
6068 store_source: None,
6069 loc: Loc {
6070 line: tok.line,
6071 column: tok.column,
6072 },
6073 leading_trivia: Vec::new(),
6074 trailing_trivia: Vec::new(),
6075 };
6076 let mut dynamic = false;
6080 if self.check(TokenType::From) {
6081 self.advance();
6082 if self.check(TokenType::AxonStore) {
6083 self.advance();
6084 dynamic = true;
6085 } else {
6086 self.consume(TokenType::Mcp)?;
6087 self.consume(TokenType::LParen)?;
6088 node.mcp_server = self.consume(TokenType::StringLit)?.value.clone();
6089 self.consume(TokenType::Comma)?;
6090 node.mcp_resource_uri = self.consume(TokenType::StringLit)?.value.clone();
6091 self.consume(TokenType::RParen)?;
6092 return Ok(node);
6093 }
6094 }
6095 self.consume(TokenType::LBrace)?;
6096 let mut src = CorpusStoreSource {
6099 doc_store: String::new(),
6100 doc_id_col: String::new(),
6101 doc_title_col: String::new(),
6102 edge_store: String::new(),
6103 edge_from_col: String::new(),
6104 edge_to_col: String::new(),
6105 edge_type_col: String::new(),
6106 edge_weight_col: String::new(),
6107 loc: Loc {
6108 line: tok.line,
6109 column: tok.column,
6110 },
6111 };
6112 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6113 let field_name = self.current().value.clone();
6114 self.advance();
6115 if self.check(TokenType::Colon) {
6116 self.advance();
6117 match field_name.as_str() {
6118 "documents" if dynamic => {
6120 let (store, cols) = self.parse_corpus_store_mapping(2)?;
6121 src.doc_store = store;
6122 src.doc_id_col = cols[0].clone();
6123 src.doc_title_col = cols[1].clone();
6124 }
6125 "documents" => node.documents = self.parse_bracketed_identifiers()?,
6126 "relations" if dynamic => {
6128 let (store, cols) = self.parse_corpus_store_mapping(4)?;
6129 src.edge_store = store;
6130 src.edge_from_col = cols[0].clone();
6131 src.edge_to_col = cols[1].clone();
6132 src.edge_type_col = cols[2].clone();
6133 src.edge_weight_col = cols[3].clone();
6134 }
6135 "relations" => node.relations = self.parse_corpus_relations()?,
6137 "adaptive" => node.adaptive = self.consume_any_ident_or_kw()?.value == "true",
6139 _ => self.skip_value(),
6140 }
6141 } else if self.check(TokenType::LBrace) {
6142 self.skip_braced_block()?;
6143 }
6144 }
6145 self.consume(TokenType::RBrace)?;
6146 if dynamic {
6147 node.store_source = Some(src);
6148 }
6149 Ok(node)
6150 }
6151
6152 fn parse_corpus_store_mapping(&mut self, n: usize) -> Result<(String, Vec<String>), ParseError> {
6159 let store = self.consume(TokenType::Identifier)?.value.clone();
6160 self.consume(TokenType::LParen)?;
6161 let mut cols = Vec::with_capacity(n);
6162 for i in 0..n {
6163 if i > 0 {
6164 self.consume(TokenType::Comma)?;
6165 }
6166 cols.push(self.consume_any_ident_or_kw()?.value.clone());
6167 }
6168 self.consume(TokenType::RParen)?;
6169 Ok((store, cols))
6170 }
6171
6172 fn parse_corpus_relations(&mut self) -> Result<Vec<CorpusRelation>, ParseError> {
6178 let mut out = Vec::new();
6179 self.consume(TokenType::LBracket)?;
6180 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
6181 if self.check(TokenType::Comma) {
6182 self.advance();
6183 continue;
6184 }
6185 let tok = self.current().clone();
6186 let etype = self.consume_any_ident_or_kw()?.value.clone();
6187 self.consume(TokenType::LParen)?;
6188 let from = self.consume_any_ident_or_kw()?.value.clone();
6189 self.consume(TokenType::Comma)?;
6190 let to = self.consume_any_ident_or_kw()?.value.clone();
6191 self.consume(TokenType::Comma)?;
6192 let weight = self.consume_number()?;
6193 self.consume(TokenType::RParen)?;
6194 out.push(CorpusRelation {
6195 etype,
6196 from,
6197 to,
6198 weight,
6199 loc: Loc { line: tok.line, column: tok.column },
6200 });
6201 }
6202 self.consume(TokenType::RBracket)?;
6203 Ok(out)
6204 }
6205
6206 fn parse_dataspace(&mut self) -> Result<DataspaceDefinition, ParseError> {
6223 let tok = self.consume(TokenType::Dataspace)?;
6224 let name = self.consume(TokenType::Identifier)?.value;
6225 let mut node = DataspaceDefinition {
6226 name,
6227 columns: Vec::new(),
6228 loc: Loc {
6229 line: tok.line,
6230 column: tok.column,
6231 },
6232 leading_trivia: Vec::new(),
6233 trailing_trivia: Vec::new(),
6234 };
6235 if self.check(TokenType::LBrace) {
6236 self.consume(TokenType::LBrace)?;
6237 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6238 let entry = self.current().clone();
6239 if entry.value != "column" {
6240 return Err(ParseError {
6241 message: format!(
6242 "Unknown entry `{}` in dataspace `{}`. A dataspace body \
6243 declares its columnar schema: `column <name>: <Type>` \
6244 (one per line, over the closed type catalog — \
6245 Text, Int, Float, Bool, Timestamp, Json).",
6246 entry.value, node.name
6247 ),
6248 line: entry.line,
6249 column: entry.column,
6250 ..Default::default()
6251 });
6252 }
6253 self.advance(); let col_tok = self.current().clone();
6255 let col_name = self.consume_any_ident_or_kw()?.value.clone();
6256 self.consume(TokenType::Colon)?;
6257 let declared_type = self.consume_any_ident_or_kw()?.value.clone();
6258 node.columns.push(crate::ast::DataspaceColumn {
6259 name: col_name,
6260 declared_type,
6261 loc: Loc {
6262 line: col_tok.line,
6263 column: col_tok.column,
6264 },
6265 });
6266 }
6267 self.consume(TokenType::RBrace)?;
6268 }
6269 Ok(node)
6270 }
6271
6272 fn parse_ots(&mut self) -> Result<OtsDefinition, ParseError> {
6273 let tok = self.consume(TokenType::Ots)?;
6274 let name = self.consume(TokenType::Identifier)?.value;
6275 let mut node = OtsDefinition {
6276 name,
6277 teleology: String::new(),
6278 homotopy_search: String::new(),
6279 loss_function: String::new(),
6280 loc: Loc {
6281 line: tok.line,
6282 column: tok.column,
6283 },
6284 leading_trivia: Vec::new(),
6285 trailing_trivia: Vec::new(),
6286 };
6287 if self.check(TokenType::Lt) {
6289 while !self.check(TokenType::Gt) && !self.check(TokenType::Eof) {
6290 self.advance();
6291 }
6292 if self.check(TokenType::Gt) {
6293 self.advance();
6294 }
6295 }
6296 self.consume(TokenType::LBrace)?;
6297 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6298 let field_name = self.current().value.clone();
6299 self.advance();
6300 if self.check(TokenType::Colon) {
6301 self.advance();
6302 match field_name.as_str() {
6303 "teleology" => {
6304 node.teleology = self.consume(TokenType::StringLit)?.value.clone()
6305 }
6306 "homotopy_search" => {
6307 node.homotopy_search = self.consume_any_ident_or_kw()?.value.clone()
6308 }
6309 "loss_function" => {
6310 node.loss_function = self.consume(TokenType::StringLit)?.value.clone()
6311 }
6312 _ => self.skip_value(),
6313 }
6314 } else if self.check(TokenType::LBrace) {
6315 self.skip_braced_block()?;
6316 }
6317 }
6318 self.consume(TokenType::RBrace)?;
6319 Ok(node)
6320 }
6321
6322 fn parse_mandate(&mut self) -> Result<MandateDefinition, ParseError> {
6323 let tok = self.consume(TokenType::Mandate)?;
6324 let name = self.consume(TokenType::Identifier)?.value;
6325 let mut node = MandateDefinition {
6326 name,
6327 constraint: String::new(),
6328 kp: None,
6329 ki: None,
6330 kd: None,
6331 tolerance: None,
6332 max_steps: None,
6333 on_violation: String::new(),
6334 loc: Loc {
6335 line: tok.line,
6336 column: tok.column,
6337 },
6338 leading_trivia: Vec::new(),
6339 trailing_trivia: Vec::new(),
6340 };
6341 self.consume(TokenType::LBrace)?;
6342 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6343 let field_name = self.current().value.clone();
6344 self.advance();
6345 if self.check(TokenType::Colon) {
6346 self.advance();
6347 match field_name.as_str() {
6348 "constraint" => {
6349 node.constraint = self.consume(TokenType::StringLit)?.value.clone()
6350 }
6351 "kp" | "Kp" => node.kp = self.parse_optional_float(),
6352 "ki" | "Ki" => node.ki = self.parse_optional_float(),
6353 "kd" | "Kd" => node.kd = self.parse_optional_float(),
6354 "tolerance" => node.tolerance = self.parse_optional_float(),
6355 "max_steps" => node.max_steps = self.parse_optional_int(),
6356 "on_violation" => {
6357 node.on_violation = self.consume_any_ident_or_kw()?.value.clone()
6358 }
6359 _ => self.skip_value(),
6360 }
6361 } else if self.check(TokenType::LBrace) {
6362 self.skip_braced_block()?;
6363 }
6364 }
6365 self.consume(TokenType::RBrace)?;
6366 Ok(node)
6367 }
6368
6369 fn parse_compute(&mut self) -> Result<ComputeDefinition, ParseError> {
6398 let tok = self.consume(TokenType::Compute)?;
6399 let name = self.consume(TokenType::Identifier)?.value;
6400 let mut node = ComputeDefinition {
6401 name,
6402 shield_ref: String::new(),
6403 parameters: Vec::new(),
6404 return_type: String::new(),
6405 body: None,
6406 loc: Loc {
6407 line: tok.line,
6408 column: tok.column,
6409 },
6410 leading_trivia: Vec::new(),
6411 trailing_trivia: Vec::new(),
6412 };
6413
6414 if self.check(TokenType::LParen) {
6416 self.advance();
6417 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
6418 let ptok = self.current().clone();
6419 let pname = self.consume_any_ident_or_kw()?.value.clone();
6420 self.consume(TokenType::Colon)?;
6421 let ptype = self.parse_type_expr()?;
6422 node.parameters.push(Parameter {
6423 name: pname,
6424 type_expr: ptype,
6425 loc: self.loc_of(&ptok),
6426 });
6427 if self.check(TokenType::Comma) {
6428 self.advance();
6429 }
6430 }
6431 self.consume(TokenType::RParen)?;
6432 }
6433
6434 if self.check(TokenType::Arrow) {
6436 self.advance();
6437 node.return_type = self.consume_any_ident_or_kw()?.value.clone();
6438 }
6439
6440 self.consume(TokenType::LBrace)?;
6441 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6442 let is_field = self
6452 .tokens
6453 .get(self.pos + 1)
6454 .map(|t| t.ttype == TokenType::Colon)
6455 .unwrap_or(false);
6456 if is_field {
6457 let field_name = self.current().value.clone();
6458 self.advance();
6459 self.consume(TokenType::Colon)?;
6460 match field_name.as_str() {
6461 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6462 _ => self.skip_value(),
6463 }
6464 } else {
6465 node.body = Some(self.parse_expr()?);
6466 }
6467 }
6468 self.consume(TokenType::RBrace)?;
6469 Ok(node)
6470 }
6471
6472 fn parse_daemon(&mut self) -> Result<DaemonDefinition, ParseError> {
6473 let tok = self.consume(TokenType::Daemon)?;
6474 let name = self.consume(TokenType::Identifier)?.value;
6475 let mut node = DaemonDefinition {
6476 name,
6477 goal: String::new(),
6478 tools: Vec::new(),
6479 memory_ref: String::new(),
6480 strategy: String::new(),
6481 on_stuck: String::new(),
6482 shield_ref: String::new(),
6483 window_ref: String::new(),
6484 budget: None,
6485 max_tokens: None,
6486 max_time: String::new(),
6487 max_cost: None,
6488 listeners: Vec::new(),
6489 requires_capabilities: Vec::new(),
6490 loc: Loc {
6491 line: tok.line,
6492 column: tok.column,
6493 },
6494 leading_trivia: Vec::new(),
6495 trailing_trivia: Vec::new(),
6496 };
6497 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
6499 self.advance();
6500 }
6501 self.consume(TokenType::LBrace)?;
6502 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6503 let field = self.current().clone();
6504 let field_name = field.value.clone();
6505 self.advance();
6506 if self.check(TokenType::Colon) {
6507 self.advance();
6508 match field_name.as_str() {
6509 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
6510 "tools" => node.tools = self.parse_bracketed_identifiers()?,
6511 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
6512 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
6513 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
6514 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6515 "window" => node.window_ref = self.consume_any_ident_or_kw()?.value.clone(),
6517 "max_tokens" => node.max_tokens = self.parse_optional_int(),
6518 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
6519 "max_cost" => node.max_cost = self.parse_optional_float(),
6520 "requires" => {
6525 let bracket_tok = self.current().clone();
6526 let items = self.parse_bracketed_dot_identifiers()?;
6527 for slug in &items {
6528 if !is_valid_capability_slug(slug) {
6529 return Err(ParseError {
6530 message: format!(
6531 "Invalid capability slug '{slug}' in daemon '{}' \
6532 `requires:`. Capability slugs must match \
6533 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
6534 lowercase identifiers. Examples: `daemon.run`, \
6535 `memory.write`, `flow.execute`.",
6536 node.name
6537 ),
6538 line: bracket_tok.line,
6539 column: bracket_tok.column,
6540 ..Default::default()
6541 });
6542 }
6543 }
6544 node.requires_capabilities = items;
6545 }
6546 _ => self.skip_value(),
6547 }
6548 } else if field.ttype == TokenType::Listen {
6549 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
6555 (self.consume(TokenType::StringLit)?.value.clone(), false)
6556 } else {
6557 (self.consume_any_ident_or_kw()?.value.clone(), true)
6558 };
6559 let mut alias = String::new();
6560 if !self.at_declaration_start()
6561 && !self.check(TokenType::RBrace)
6562 && !self.check(TokenType::LBrace)
6563 {
6564 let next = self.current().clone();
6565 if next.value == "as" || next.ttype == TokenType::As {
6566 self.advance();
6567 alias = self.consume_any_ident_or_kw()?.value.clone();
6568 }
6569 }
6570 let listen_loc = Loc {
6571 line: field.line,
6572 column: field.column,
6573 };
6574 let body = self.parse_listener_body()?;
6578 node.listeners.push(ListenStep {
6579 channel,
6580 channel_is_ref,
6581 event_alias: alias,
6582 body,
6583 loc: listen_loc,
6584 });
6585 } else if field_name == "budget" && self.check(TokenType::LBrace) {
6586 node.budget = Some(self.parse_budget_block(field.line, field.column)?);
6588 } else if self.check(TokenType::LBrace) {
6589 self.skip_braced_block()?;
6590 }
6591 }
6592 self.consume(TokenType::RBrace)?;
6593 Ok(node)
6594 }
6595
6596 fn parse_top_level_budget(&mut self) -> Result<BudgetBlock, ParseError> {
6605 let kw = self.consume(TokenType::Budget)?; let name = self.consume(TokenType::Identifier)?.value;
6607 let mut block = self.parse_budget_block(kw.line, kw.column)?;
6608 block.name = name;
6609 Ok(block)
6610 }
6611
6612 fn parse_budget_block(&mut self, line: u32, column: u32) -> Result<BudgetBlock, ParseError> {
6614 self.consume(TokenType::LBrace)?;
6615 let mut quotas = Vec::new();
6616 let mut on_exhausted = String::new();
6617 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6618 let field = self.current().clone();
6619 let field_name = self.consume_any_ident_or_kw()?.value;
6620 match field_name.as_str() {
6621 "rate" | "max" => {
6622 quotas.push(self.parse_budget_quota(field_name, field.line, field.column)?);
6623 }
6624 "on_exhausted" => {
6625 self.consume(TokenType::Colon)?;
6626 on_exhausted = self.consume_any_ident_or_kw()?.value;
6627 }
6628 _ => self.skip_value(),
6629 }
6630 }
6631 self.consume(TokenType::RBrace)?;
6632 Ok(BudgetBlock {
6633 name: String::new(),
6634 quotas,
6635 on_exhausted,
6636 loc: Loc { line, column },
6637 leading_trivia: Vec::new(),
6638 trailing_trivia: Vec::new(),
6639 })
6640 }
6641
6642 fn parse_budget_quota(
6645 &mut self,
6646 kind: String,
6647 line: u32,
6648 column: u32,
6649 ) -> Result<BudgetQuota, ParseError> {
6650 self.consume(TokenType::Colon)?;
6651 let limit = self.consume_number()? as i64;
6652 let _per = self.consume_any_ident_or_kw()?; let period = self.consume_any_ident_or_kw()?.value;
6655 let _on = self.consume_any_ident_or_kw()?; let _tool = self.consume_any_ident_or_kw()?; self.consume(TokenType::LParen)?;
6659 let effect = self.consume_any_ident_or_kw()?.value;
6660 self.consume(TokenType::RParen)?;
6661 Ok(BudgetQuota {
6662 kind,
6663 limit,
6664 period,
6665 effect,
6666 loc: Loc { line, column },
6667 })
6668 }
6669
6670 fn parse_axonstore(&mut self) -> Result<AxonStoreDefinition, ParseError> {
6671 let tok = self.consume(TokenType::AxonStore)?;
6672 let name = self.consume(TokenType::Identifier)?.value;
6673 let mut node = AxonStoreDefinition {
6674 name,
6675 backend: String::new(),
6676 connection: String::new(),
6677 resource_ref: String::new(),
6678 confidence_floor: None,
6679 isolation: String::new(),
6680 on_breach: String::new(),
6681 capability: String::new(),
6682 class: String::new(),
6683 column_schema: None,
6684 loc: Loc {
6685 line: tok.line,
6686 column: tok.column,
6687 },
6688 leading_trivia: Vec::new(),
6689 trailing_trivia: Vec::new(),
6690 };
6691 self.consume(TokenType::LBrace)?;
6692 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6693 let field = self.current().clone();
6694 let field_name = field.value.clone();
6695 if field.ttype == TokenType::Schema {
6701 self.advance();
6702 let parsed = self.parse_store_schema_declaration(&node.name, field.line, field.column)?;
6703 node.column_schema = Some(parsed);
6704 continue;
6705 }
6706 self.advance();
6707 if self.check(TokenType::Colon) {
6708 self.advance();
6709 match field_name.as_str() {
6710 "backend" => node.backend = self.consume_any_ident_or_kw()?.value.clone(),
6711 "class" => node.class = self.parse_dotted_identifier()?,
6719 "connection" => {
6720 node.connection = self.consume(TokenType::StringLit)?.value.clone()
6721 }
6722 "resource" => {
6728 node.resource_ref = self.consume_any_ident_or_kw()?.value.clone()
6729 }
6730 "confidence_floor" => node.confidence_floor = self.parse_optional_float(),
6731 "isolation" => node.isolation = self.consume_any_ident_or_kw()?.value.clone(),
6732 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
6733 "capability" => {
6737 let slug_tok = self.consume(TokenType::StringLit)?.clone();
6738 if !is_valid_capability_slug(&slug_tok.value) {
6739 return Err(ParseError {
6740 message: format!(
6741 "Invalid capability slug '{}' in axonstore '{}' \
6742 `capability:`. Capability slugs must match \
6743 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
6744 lowercase identifiers starting with a letter. Examples: \
6745 `admin`, `tenant.read`, `hipaa.phi.read`.",
6746 slug_tok.value, node.name
6747 ),
6748 line: slug_tok.line,
6749 column: slug_tok.column,
6750 ..Default::default()
6751 });
6752 }
6753 node.capability = slug_tok.value.clone();
6754 }
6755 _ => self.skip_value(),
6756 }
6757 } else if self.check(TokenType::LBrace) {
6758 self.skip_braced_block()?;
6759 }
6760 }
6761 self.consume(TokenType::RBrace)?;
6762 Ok(node)
6763 }
6764
6765 fn parse_store_schema_declaration(
6777 &mut self,
6778 store_name: &str,
6779 sch_line: u32,
6780 sch_col: u32,
6781 ) -> Result<crate::store_schema::StoreColumnSchema, ParseError> {
6782 use crate::store_schema::{StoreColumn, StoreColumnSchema, StoreColumnType};
6783
6784 if self.check(TokenType::LBrace) {
6786 self.consume(TokenType::LBrace)?;
6787 let mut columns: Vec<StoreColumn> = Vec::new();
6788 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6789 let col_tok = self.current().clone();
6790 let col_name = self.consume_any_ident_or_kw()?.value.clone();
6791 self.consume(TokenType::Colon)?;
6792 let type_tok = self.consume_any_ident_or_kw()?.clone();
6793 let col_type = StoreColumnType::from_token(&type_tok.value).ok_or_else(|| {
6794 let names = StoreColumnType::all_canonical_names();
6795 let suggestion =
6796 crate::smart_suggest::suggest_for(&type_tok.value, &names);
6797 let suggest_suffix = if suggestion.is_empty() {
6798 String::new()
6799 } else {
6800 format!(" {suggestion}")
6801 };
6802 let known = names.join(", ");
6803 ParseError {
6804 message: format!(
6805 "Unknown column type `{}` for column `{}` in \
6806 axonstore `{}` `schema:` block. The closed \
6807 v1.38.0 column-type catalog (Fase 38.b D1) \
6808 is {{{known}}} (plus common lowercase \
6809 aliases — `int`/`integer`/`int4` for \
6810 `Int`, `bool`/`boolean` for `Bool`, etc.).\
6811 {suggest_suffix}",
6812 type_tok.value, col_name, store_name
6813 ),
6814 line: type_tok.line,
6815 column: type_tok.column,
6816 ..Default::default()
6817 }
6818 })?;
6819
6820 let mut json_shape: Option<String> = None;
6830 if self.check(TokenType::Lt) {
6831 self.advance();
6832 let shape_tok = self.consume_any_ident_or_kw()?.clone();
6833 self.consume(TokenType::Gt)?;
6834 if matches!(col_type, StoreColumnType::Json | StoreColumnType::Jsonb) {
6835 json_shape = Some(shape_tok.value.clone());
6836 } else {
6837 return Err(ParseError {
6838 message: format!(
6839 "axon-T841 a shape lens `<{shape}>` may refine \
6840 only a `Json` / `Jsonb` column, but column \
6841 `{col}` in axonstore `{store}` is `{ty}`. Drop \
6842 the `<{shape}>` (a rigid column already has a \
6843 fixed shape), or change the column type to \
6844 `Json<{shape}>` if it carries open documents.",
6845 shape = shape_tok.value,
6846 col = col_name,
6847 store = store_name,
6848 ty = col_type.canonical_name(),
6849 ),
6850 line: shape_tok.line,
6851 column: shape_tok.column,
6852 ..Default::default()
6853 });
6854 }
6855 }
6856
6857 let mut col = StoreColumn {
6858 name: col_name,
6859 col_type,
6860 json_shape,
6861 primary_key: false,
6862 auto_increment: false,
6863 not_null: false,
6864 unique: false,
6865 indexed: false,
6866 default_value: String::new(),
6867 identity: false,
6872 line: col_tok.line,
6873 column: col_tok.column,
6874 };
6875
6876 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6879 if self.current().ttype != TokenType::Identifier {
6880 break;
6883 }
6884 let constraint = self.current().value.clone();
6885 match constraint.as_str() {
6886 "primary_key" => {
6887 col.primary_key = true;
6888 self.advance();
6889 }
6890 "auto_increment" => {
6891 col.auto_increment = true;
6892 self.advance();
6893 }
6894 "not_null" => {
6895 col.not_null = true;
6896 self.advance();
6897 }
6898 "unique" => {
6899 col.unique = true;
6900 self.advance();
6901 }
6902 "index" => {
6908 col.indexed = true;
6909 self.advance();
6910 }
6911 "identity" => {
6922 col.identity = true;
6923 self.advance();
6924 }
6925 "default" => {
6926 self.advance();
6927 let dv = self.current().clone();
6928 if matches!(
6929 dv.ttype,
6930 TokenType::StringLit
6931 | TokenType::Integer
6932 | TokenType::Float
6933 ) {
6934 col.default_value = dv.value.clone();
6935 self.advance();
6936 } else {
6937 col.default_value =
6938 self.consume_any_ident_or_kw()?.value.clone();
6939 }
6940 }
6941 _ => break,
6942 }
6943 }
6944
6945 columns.push(col);
6946 }
6947 self.consume(TokenType::RBrace)?;
6948 return Ok(StoreColumnSchema::Inline {
6949 columns,
6950 leading_trivia: Vec::new(),
6951 line: sch_line,
6952 column: sch_col,
6953 });
6954 }
6955
6956 if !self.check(TokenType::Colon) {
6958 let cur = self.current().clone();
6959 return Err(ParseError {
6960 message: format!(
6961 "axonstore `{store_name}` `schema:` declaration expects \
6962 `{{ … }}` (inline columns), `: \"manifest.ref\"` \
6963 (manifest reference), or `: env:VAR` (per-tenant schema \
6964 namespace). Got `{}` instead.",
6965 cur.value
6966 ),
6967 line: cur.line,
6968 column: cur.column,
6969 ..Default::default()
6970 });
6971 }
6972 self.consume(TokenType::Colon)?;
6973
6974 if self.check(TokenType::StringLit) {
6976 let lit = self.consume(TokenType::StringLit)?.clone();
6977 let value = lit.value.clone();
6978 if let Some(var) = value.strip_prefix("env:") {
6979 let var = var.trim();
6980 if var.is_empty() {
6981 return Err(ParseError {
6982 message: format!(
6983 "axonstore `{store_name}` `schema: \"env:\"` is \
6984 missing the variable name after the `env:` \
6985 prefix."
6986 ),
6987 line: lit.line,
6988 column: lit.column,
6989 ..Default::default()
6990 });
6991 }
6992 return Ok(StoreColumnSchema::EnvVar {
6993 var_name: var.to_string(),
6994 line: sch_line,
6995 column: sch_col,
6996 });
6997 }
6998 if value.trim().is_empty() {
7000 return Err(ParseError {
7001 message: format!(
7002 "axonstore `{store_name}` `schema:` manifest reference \
7003 is empty. Expected `\"qualified.name\"` — e.g. \
7004 `\"public.tenants\"`."
7005 ),
7006 line: lit.line,
7007 column: lit.column,
7008 ..Default::default()
7009 });
7010 }
7011 return Ok(StoreColumnSchema::ManifestRef {
7012 qualified_name: value,
7013 line: sch_line,
7014 column: sch_col,
7015 });
7016 }
7017
7018 let env_tok = self.current().clone();
7021 if env_tok.value == "env" {
7022 self.advance();
7023 if !self.check(TokenType::Colon) {
7024 return Err(ParseError {
7025 message: format!(
7026 "axonstore `{store_name}` `schema: env` is missing the \
7027 `:` separator. Expected `schema: env:VAR`."
7028 ),
7029 line: env_tok.line,
7030 column: env_tok.column,
7031 ..Default::default()
7032 });
7033 }
7034 self.advance(); let var_tok = self.consume_any_ident_or_kw()?.clone();
7036 if var_tok.value.trim().is_empty() {
7037 return Err(ParseError {
7038 message: format!(
7039 "axonstore `{store_name}` `schema: env:` is missing \
7040 the variable name."
7041 ),
7042 line: var_tok.line,
7043 column: var_tok.column,
7044 ..Default::default()
7045 });
7046 }
7047 return Ok(StoreColumnSchema::EnvVar {
7048 var_name: var_tok.value.clone(),
7049 line: sch_line,
7050 column: sch_col,
7051 });
7052 }
7053
7054 Err(ParseError {
7055 message: format!(
7056 "axonstore `{store_name}` `schema:` declaration expects \
7057 `{{ … }}` (inline columns), `\"manifest.ref\"` (manifest \
7058 reference), or `env:VAR` (per-tenant schema namespace). \
7059 Got `{}` instead.",
7060 env_tok.value
7061 ),
7062 line: env_tok.line,
7063 column: env_tok.column,
7064 ..Default::default()
7065 })
7066 }
7067
7068 fn parse_resource(&mut self) -> Result<ResourceDefinition, ParseError> {
7075 let tok = self.consume(TokenType::Resource)?;
7076 let name = self.consume(TokenType::Identifier)?.value;
7077 let mut node = ResourceDefinition {
7078 name,
7079 kind: String::new(),
7080 endpoint: String::new(),
7081 capacity: None,
7082 lifetime: "affine".to_string(),
7083 certainty_floor: None,
7084 shield_ref: String::new(),
7085 within: String::new(),
7086 loc: Loc {
7087 line: tok.line,
7088 column: tok.column,
7089 },
7090 leading_trivia: Vec::new(),
7091 trailing_trivia: Vec::new(),
7092 };
7093 self.consume(TokenType::LBrace)?;
7094 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7095 let field_tok = self.current().clone();
7096 let field_name = field_tok.value.clone();
7097 self.advance();
7098 if !self.check(TokenType::Colon) {
7099 if self.check(TokenType::LBrace) {
7101 self.skip_braced_block()?;
7102 }
7103 continue;
7104 }
7105 self.advance(); match field_name.as_str() {
7107 "kind" => node.kind = self.consume_any_ident_or_kw()?.value,
7108 "endpoint" => {
7121 node.endpoint = if self.check(TokenType::StringLit) {
7122 self.consume(TokenType::StringLit)?.value
7123 } else {
7124 self.parse_dotted_identifier()?
7125 };
7126 }
7127 "capacity" => {
7128 node.capacity = self.parse_optional_int();
7129 }
7130 "lifetime" => {
7131 let lt_tok = self.consume_any_ident_or_kw()?;
7132 let lt = lt_tok.value;
7133 if !matches!(lt.as_str(), "linear" | "affine" | "persistent") {
7134 return Err(ParseError {
7135 message: format!(
7136 "Invalid lifetime '{lt}' in resource '{}' — \
7137 expected linear | affine | persistent",
7138 node.name
7139 ),
7140 line: lt_tok.line,
7141 column: lt_tok.column,
7142 ..Default::default()
7143 });
7144 }
7145 node.lifetime = lt;
7146 }
7147 "certainty_floor" => {
7148 node.certainty_floor = self.parse_optional_float();
7149 }
7150 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7151 "within" => node.within = self.consume_any_ident_or_kw()?.value,
7155 unknown => {
7165 return Err(ParseError {
7166 message: format!(
7167 "Unknown field '{unknown}' in resource '{}' — expected one of: \
7168 kind, endpoint, capacity, lifetime, certainty_floor, shield, within",
7169 node.name
7170 ),
7171 line: field_tok.line,
7172 column: field_tok.column,
7173 ..Default::default()
7174 });
7175 }
7176 }
7177 }
7178 self.consume(TokenType::RBrace)?;
7179 Ok(node)
7180 }
7181
7182 fn parse_fabric(&mut self) -> Result<FabricDefinition, ParseError> {
7184 let tok = self.consume(TokenType::Fabric)?;
7185 let name = self.consume(TokenType::Identifier)?.value;
7186 let mut node = FabricDefinition {
7187 name,
7188 provider: String::new(),
7189 region: String::new(),
7190 zones: None,
7191 ephemeral: None,
7192 shield_ref: String::new(),
7193 loc: Loc {
7194 line: tok.line,
7195 column: tok.column,
7196 },
7197 leading_trivia: Vec::new(),
7198 trailing_trivia: Vec::new(),
7199 };
7200 self.consume(TokenType::LBrace)?;
7201 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7202 let field_name = self.current().value.clone();
7203 self.advance();
7204 if !self.check(TokenType::Colon) {
7205 if self.check(TokenType::LBrace) {
7206 self.skip_braced_block()?;
7207 }
7208 continue;
7209 }
7210 self.advance(); match field_name.as_str() {
7212 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
7213 "region" => node.region = self.consume(TokenType::StringLit)?.value,
7214 "zones" => node.zones = self.parse_optional_int(),
7215 "ephemeral" => {
7216 let b = self.parse_bool()?;
7217 node.ephemeral = Some(b);
7218 }
7219 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7220 _ => self.skip_value(),
7221 }
7222 }
7223 self.consume(TokenType::RBrace)?;
7224 Ok(node)
7225 }
7226
7227 fn parse_manifest(&mut self) -> Result<ManifestDefinition, ParseError> {
7229 let tok = self.consume(TokenType::Manifest)?;
7230 let name = self.consume(TokenType::Identifier)?.value;
7231 let mut node = ManifestDefinition {
7232 name,
7233 resources: Vec::new(),
7234 fabric_ref: String::new(),
7235 region: String::new(),
7236 zones: None,
7237 compliance: Vec::new(),
7238 loc: Loc {
7239 line: tok.line,
7240 column: tok.column,
7241 },
7242 leading_trivia: Vec::new(),
7243 trailing_trivia: Vec::new(),
7244 };
7245 self.consume(TokenType::LBrace)?;
7246 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7247 let field_name = self.current().value.clone();
7248 self.advance();
7249 if !self.check(TokenType::Colon) {
7250 if self.check(TokenType::LBrace) {
7251 self.skip_braced_block()?;
7252 }
7253 continue;
7254 }
7255 self.advance();
7256 match field_name.as_str() {
7257 "resources" => node.resources = self.parse_bracketed_identifiers()?,
7258 "fabric" => node.fabric_ref = self.consume_any_ident_or_kw()?.value,
7259 "region" => node.region = self.consume(TokenType::StringLit)?.value,
7260 "zones" => node.zones = self.parse_optional_int(),
7261 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
7262 _ => self.skip_value(),
7263 }
7264 }
7265 self.consume(TokenType::RBrace)?;
7266 Ok(node)
7267 }
7268
7269 fn parse_observe(&mut self) -> Result<ObserveDefinition, ParseError> {
7271 let tok = self.consume(TokenType::Observe)?;
7272 let name = self.consume(TokenType::Identifier)?.value;
7273 self.consume(TokenType::From)?;
7275 let target = self.consume(TokenType::Identifier)?.value;
7276 let mut node = ObserveDefinition {
7277 name,
7278 target,
7279 sources: Vec::new(),
7280 quorum: None,
7281 timeout: String::new(),
7282 on_partition: "fail".to_string(),
7283 certainty_floor: None,
7284 loc: Loc {
7285 line: tok.line,
7286 column: tok.column,
7287 },
7288 leading_trivia: Vec::new(),
7289 trailing_trivia: Vec::new(),
7290 };
7291 self.consume(TokenType::LBrace)?;
7292 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7293 let field_name = self.current().value.clone();
7294 self.advance();
7295 if !self.check(TokenType::Colon) {
7296 if self.check(TokenType::LBrace) {
7297 self.skip_braced_block()?;
7298 }
7299 continue;
7300 }
7301 self.advance();
7302 match field_name.as_str() {
7303 "sources" => node.sources = self.parse_bracketed_identifiers()?,
7304 "quorum" => node.quorum = self.parse_optional_int(),
7305 "timeout" => {
7306 let t = self.current().clone();
7307 match t.ttype {
7308 TokenType::Duration | TokenType::StringLit => {
7309 self.advance();
7310 node.timeout = t.value;
7311 }
7312 _ => node.timeout = self.consume_any_ident_or_kw()?.value,
7313 }
7314 }
7315 "on_partition" => {
7316 let p_tok = self.consume_any_ident_or_kw()?;
7317 let p = p_tok.value;
7318 if !matches!(p.as_str(), "fail" | "shield_quarantine") {
7319 return Err(ParseError {
7320 message: format!(
7321 "Invalid on_partition '{p}' in observe '{}' — \
7322 expected fail | shield_quarantine",
7323 node.name
7324 ),
7325 line: p_tok.line,
7326 column: p_tok.column,
7327 ..Default::default()
7328 });
7329 }
7330 node.on_partition = p;
7331 }
7332 "certainty_floor" => node.certainty_floor = self.parse_optional_float(),
7333 _ => self.skip_value(),
7334 }
7335 }
7336 self.consume(TokenType::RBrace)?;
7337 Ok(node)
7338 }
7339
7340 fn parse_reconcile(&mut self) -> Result<ReconcileDefinition, ParseError> {
7344 let tok = self.consume(TokenType::Reconcile)?;
7345 let name = self.consume(TokenType::Identifier)?.value;
7346 let mut node = ReconcileDefinition {
7347 name,
7348 observe_ref: String::new(),
7349 threshold: None,
7350 tolerance: None,
7351 on_drift: "provision".to_string(),
7352 shield_ref: String::new(),
7353 mandate_ref: String::new(),
7354 max_retries: 3,
7355 loc: Loc {
7356 line: tok.line,
7357 column: tok.column,
7358 },
7359 leading_trivia: Vec::new(),
7360 trailing_trivia: Vec::new(),
7361 };
7362 self.consume(TokenType::LBrace)?;
7363 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7364 let field_name = self.current().value.clone();
7365 self.advance();
7366 if !self.check(TokenType::Colon) {
7367 if self.check(TokenType::LBrace) {
7368 self.skip_braced_block()?;
7369 }
7370 continue;
7371 }
7372 self.advance();
7373 match field_name.as_str() {
7374 "observe" => node.observe_ref = self.consume_any_ident_or_kw()?.value,
7375 "threshold" => node.threshold = self.parse_optional_float(),
7376 "tolerance" => node.tolerance = self.parse_optional_float(),
7377 "on_drift" => {
7378 let d_tok = self.consume_any_ident_or_kw()?;
7379 let d = d_tok.value;
7380 if !matches!(d.as_str(), "provision" | "alert" | "refine") {
7381 return Err(ParseError {
7382 message: format!(
7383 "Invalid on_drift '{d}' in reconcile '{}' — \
7384 expected provision | alert | refine",
7385 node.name
7386 ),
7387 line: d_tok.line,
7388 column: d_tok.column,
7389 ..Default::default()
7390 });
7391 }
7392 node.on_drift = d;
7393 }
7394 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7395 "mandate" => node.mandate_ref = self.consume_any_ident_or_kw()?.value,
7396 "max_retries" => {
7397 if let Some(v) = self.parse_optional_int() {
7398 node.max_retries = v;
7399 }
7400 }
7401 _ => self.skip_value(),
7402 }
7403 }
7404 self.consume(TokenType::RBrace)?;
7405 Ok(node)
7406 }
7407
7408 fn parse_lease(&mut self) -> Result<LeaseDefinition, ParseError> {
7410 let tok = self.consume(TokenType::Lease)?;
7411 let name = self.consume(TokenType::Identifier)?.value;
7412 let mut node = LeaseDefinition {
7413 name,
7414 resource_ref: String::new(),
7415 duration: String::new(),
7416 acquire: "on_start".to_string(),
7417 on_expire: "anchor_breach".to_string(),
7418 loc: Loc {
7419 line: tok.line,
7420 column: tok.column,
7421 },
7422 leading_trivia: Vec::new(),
7423 trailing_trivia: Vec::new(),
7424 };
7425 self.consume(TokenType::LBrace)?;
7426 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7427 let field_name = self.current().value.clone();
7428 self.advance();
7429 if !self.check(TokenType::Colon) {
7430 if self.check(TokenType::LBrace) {
7431 self.skip_braced_block()?;
7432 }
7433 continue;
7434 }
7435 self.advance();
7436 match field_name.as_str() {
7437 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
7438 "duration" => {
7439 let t = self.current().clone();
7440 match t.ttype {
7441 TokenType::Duration | TokenType::StringLit => {
7442 self.advance();
7443 node.duration = t.value;
7444 }
7445 _ => node.duration = self.consume_any_ident_or_kw()?.value,
7446 }
7447 }
7448 "acquire" => {
7449 let a_tok = self.consume_any_ident_or_kw()?;
7450 let a = a_tok.value;
7451 if !matches!(a.as_str(), "on_start" | "on_demand") {
7452 return Err(ParseError {
7453 message: format!(
7454 "Invalid acquire '{a}' in lease '{}' — \
7455 expected on_start | on_demand",
7456 node.name
7457 ),
7458 line: a_tok.line,
7459 column: a_tok.column,
7460 ..Default::default()
7461 });
7462 }
7463 node.acquire = a;
7464 }
7465 "on_expire" => {
7466 let e_tok = self.consume_any_ident_or_kw()?;
7467 let e = e_tok.value;
7468 if !matches!(e.as_str(), "anchor_breach" | "release" | "extend") {
7469 return Err(ParseError {
7470 message: format!(
7471 "Invalid on_expire '{e}' in lease '{}' — \
7472 expected anchor_breach | release | extend",
7473 node.name
7474 ),
7475 line: e_tok.line,
7476 column: e_tok.column,
7477 ..Default::default()
7478 });
7479 }
7480 node.on_expire = e;
7481 }
7482 _ => self.skip_value(),
7483 }
7484 }
7485 self.consume(TokenType::RBrace)?;
7486 Ok(node)
7487 }
7488
7489 fn parse_ensemble(&mut self) -> Result<EnsembleDefinition, ParseError> {
7491 let tok = self.consume(TokenType::Ensemble)?;
7492 let name = self.consume(TokenType::Identifier)?.value;
7493 let mut node = EnsembleDefinition {
7494 name,
7495 observations: Vec::new(),
7496 quorum: None,
7497 aggregation: "majority".to_string(),
7498 certainty_mode: "min".to_string(),
7499 loc: Loc {
7500 line: tok.line,
7501 column: tok.column,
7502 },
7503 leading_trivia: Vec::new(),
7504 trailing_trivia: Vec::new(),
7505 };
7506 self.consume(TokenType::LBrace)?;
7507 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7508 let field_name = self.current().value.clone();
7509 self.advance();
7510 if !self.check(TokenType::Colon) {
7511 if self.check(TokenType::LBrace) {
7512 self.skip_braced_block()?;
7513 }
7514 continue;
7515 }
7516 self.advance();
7517 match field_name.as_str() {
7518 "observations" => node.observations = self.parse_bracketed_identifiers()?,
7519 "quorum" => node.quorum = self.parse_optional_int(),
7520 "aggregation" => {
7521 let a_tok = self.consume_any_ident_or_kw()?;
7522 let a = a_tok.value;
7523 if !matches!(a.as_str(), "majority" | "weighted" | "byzantine") {
7524 return Err(ParseError {
7525 message: format!(
7526 "Invalid aggregation '{a}' in ensemble '{}' — \
7527 expected majority | weighted | byzantine",
7528 node.name
7529 ),
7530 line: a_tok.line,
7531 column: a_tok.column,
7532 ..Default::default()
7533 });
7534 }
7535 node.aggregation = a;
7536 }
7537 "certainty_mode" => {
7538 let c_tok = self.consume_any_ident_or_kw()?;
7539 let c = c_tok.value;
7540 if !matches!(c.as_str(), "min" | "weighted" | "harmonic") {
7541 return Err(ParseError {
7542 message: format!(
7543 "Invalid certainty_mode '{c}' in ensemble '{}' — \
7544 expected min | weighted | harmonic",
7545 node.name
7546 ),
7547 line: c_tok.line,
7548 column: c_tok.column,
7549 ..Default::default()
7550 });
7551 }
7552 node.certainty_mode = c;
7553 }
7554 _ => self.skip_value(),
7555 }
7556 }
7557 self.consume(TokenType::RBrace)?;
7558 Ok(node)
7559 }
7560
7561 fn parse_session_definition(&mut self) -> Result<SessionDefinition, ParseError> {
7569 let tok = self.consume(TokenType::Session)?;
7570 let name = self.consume(TokenType::Identifier)?.value;
7571 let mut node = SessionDefinition {
7572 name,
7573 roles: Vec::new(),
7574 loc: Loc {
7575 line: tok.line,
7576 column: tok.column,
7577 },
7578 leading_trivia: Vec::new(),
7579 trailing_trivia: Vec::new(),
7580 };
7581 self.consume(TokenType::LBrace)?;
7582 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7583 let role_tok = self.consume_any_ident_or_kw()?;
7584 self.consume(TokenType::Colon)?;
7585 let steps = self.parse_session_steps()?;
7586 node.roles.push(SessionRole {
7587 name: role_tok.value,
7588 steps,
7589 loc: Loc {
7590 line: role_tok.line,
7591 column: role_tok.column,
7592 },
7593 });
7594 }
7595 self.consume(TokenType::RBrace)?;
7596 Ok(node)
7597 }
7598
7599 fn parse_observable(&mut self) -> Result<ObservableDefinition, ParseError> {
7612 let tok = self.consume(TokenType::Observable)?;
7613 let name = self.consume(TokenType::Identifier)?.value;
7614 let mut node = ObservableDefinition {
7615 name,
7616 qubits: None,
7617 terms: Vec::new(),
7618 loc: Loc {
7619 line: tok.line,
7620 column: tok.column,
7621 },
7622 leading_trivia: Vec::new(),
7623 trailing_trivia: Vec::new(),
7624 };
7625 self.consume(TokenType::LBrace)?;
7626 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7627 let key_tok = self.consume_any_ident_or_kw()?;
7628 self.consume(TokenType::Colon)?;
7629 match key_tok.value.as_str() {
7630 "qubits" => node.qubits = Some(self.consume_number()? as i64),
7631 "term" => {
7632 let term_loc = Loc {
7633 line: key_tok.line,
7634 column: key_tok.column,
7635 };
7636 let mut negative = false;
7638 if self.check(TokenType::Minus) {
7639 self.advance();
7640 negative = true;
7641 } else if self.check(TokenType::Plus) {
7642 self.advance();
7643 }
7644 let mag = self.consume_number()?;
7645 let coefficient = if negative { -mag } else { mag };
7646 self.consume(TokenType::Star)?;
7648 let pauli = self.consume(TokenType::StringLit)?.value;
7649 node.terms.push(PauliTerm {
7650 coefficient,
7651 pauli,
7652 loc: term_loc,
7653 });
7654 }
7655 _ => self.skip_value(),
7656 }
7657 }
7658 self.consume(TokenType::RBrace)?;
7659 Ok(node)
7660 }
7661
7662 fn parse_witness(&mut self) -> Result<WitnessDefinition, ParseError> {
7670 let tok = self.consume(TokenType::Witness)?;
7671 let name = self.consume(TokenType::Identifier)?.value;
7672 let mut node = WitnessDefinition {
7673 name,
7674 claim: String::new(),
7675 baseline: String::new(),
7676 metric: String::new(),
7677 threshold: 0.0,
7678 data: String::new(),
7679 loc: Loc {
7680 line: tok.line,
7681 column: tok.column,
7682 },
7683 leading_trivia: Vec::new(),
7684 trailing_trivia: Vec::new(),
7685 };
7686 self.consume(TokenType::LBrace)?;
7687 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7688 let key_tok = self.consume_any_ident_or_kw()?;
7689 self.consume(TokenType::Colon)?;
7690 match key_tok.value.as_str() {
7691 "claim" => node.claim = self.consume_any_ident_or_kw()?.value,
7692 "against" => node.baseline = self.consume_any_ident_or_kw()?.value,
7695 "metric" => node.metric = self.consume_any_ident_or_kw()?.value,
7696 "threshold" => node.threshold = self.consume_number()?,
7697 "data" => node.data = self.consume_any_ident_or_kw()?.value,
7698 _ => self.skip_value(),
7699 }
7700 }
7701 self.consume(TokenType::RBrace)?;
7702 Ok(node)
7703 }
7704
7705 fn parse_socket(&mut self) -> Result<SocketDefinition, ParseError> {
7710 let tok = self.consume(TokenType::Socket)?;
7711 let name = self.consume(TokenType::Identifier)?.value;
7712 let mut node = SocketDefinition {
7713 name,
7714 loc: Loc { line: tok.line, column: tok.column },
7715 ..Default::default()
7716 };
7717 self.consume(TokenType::LBrace)?;
7718 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7719 let key = self.consume_any_ident_or_kw()?.value;
7720 self.consume(TokenType::Colon)?;
7721 match key.as_str() {
7722 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
7723 "backpressure" => {
7724 let kind = self.consume_any_ident_or_kw()?.value;
7726 if kind != "credit" {
7727 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
7728 }
7729 self.consume(TokenType::LParen)?;
7730 let n = self
7731 .consume(TokenType::Integer)?
7732 .value
7733 .parse::<i64>()
7734 .map_err(|_| self.error("backpressure credit must be an integer"))?;
7735 self.consume(TokenType::RParen)?;
7736 node.backpressure_credit = Some(n);
7737 }
7738 "reconnect" => {
7739 let mode = self.consume_any_ident_or_kw()?.value;
7740 node.reconnect = mode == "cognitive_state";
7741 }
7742 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
7743 other => return Err(self.error(&format!("unknown socket field `{other}`"))),
7744 }
7745 if self.check(TokenType::Comma) {
7747 self.consume(TokenType::Comma)?;
7748 }
7749 }
7750 self.consume(TokenType::RBrace)?;
7751 Ok(node)
7752 }
7753
7754 fn parse_upstream(&mut self) -> Result<UpstreamDefinition, ParseError> {
7762 let tok = self.consume(TokenType::Upstream)?;
7763 let name = self.consume(TokenType::Identifier)?.value;
7764 let mut node = UpstreamDefinition {
7765 name,
7766 loc: Loc { line: tok.line, column: tok.column },
7767 ..Default::default()
7768 };
7769 if self.check(TokenType::From) {
7771 self.advance();
7772 let base = self.consume(TokenType::Identifier)?.value;
7773 self.consume(TokenType::At)?;
7774 let version = self.consume_any_ident_or_kw()?.value;
7775 node.preset = Some(format!("{base}@{version}"));
7776 }
7777 self.consume(TokenType::LBrace)?;
7778 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7779 let key = self.consume_any_ident_or_kw()?.value;
7780 self.consume(TokenType::Colon)?;
7781 match key.as_str() {
7782 "transport" => node.transport = self.consume_any_ident_or_kw()?.value,
7783 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
7784 "role" => node.role = self.consume_any_ident_or_kw()?.value,
7785 "resolve" => node.resolve = self.parse_dotted_identifier()?,
7786 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
7790 "secret" => node.secret = self.parse_dotted_identifier()?,
7791 "auth" => {
7792 node.auth_kind = self.consume_any_ident_or_kw()?.value;
7795 if self.check(TokenType::LParen) {
7796 self.consume(TokenType::LParen)?;
7797 node.auth_name = Some(self.consume(TokenType::StringLit)?.value);
7798 if self.check(TokenType::Comma) {
7799 self.consume(TokenType::Comma)?;
7800 node.auth_prefix = Some(self.consume(TokenType::StringLit)?.value);
7801 }
7802 self.consume(TokenType::RParen)?;
7803 }
7804 }
7805 "map" => node.map = self.parse_upstream_map()?,
7806 "reconnect" => node.reconnect = Some(self.parse_upstream_reconnect()?),
7807 "overflow" => node.overflow = Some(self.consume_any_ident_or_kw()?.value),
7808 "backpressure" => {
7809 let kind = self.consume_any_ident_or_kw()?.value;
7811 if kind != "credit" {
7812 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
7813 }
7814 self.consume(TokenType::LParen)?;
7815 let n = self
7816 .consume(TokenType::Integer)?
7817 .value
7818 .parse::<i64>()
7819 .map_err(|_| self.error("backpressure credit must be an integer"))?;
7820 self.consume(TokenType::RParen)?;
7821 node.backpressure_credit = Some(n);
7822 }
7823 other => return Err(self.error(&format!("unknown upstream field `{other}`"))),
7824 }
7825 if self.check(TokenType::Comma) {
7827 self.consume(TokenType::Comma)?;
7828 }
7829 }
7830 self.consume(TokenType::RBrace)?;
7831 Ok(node)
7832 }
7833
7834 fn parse_cors(&mut self) -> Result<CorsDefinition, ParseError> {
7843 let tok = self.consume(TokenType::Cors)?;
7844 let name = self.consume(TokenType::Identifier)?.value;
7845 let mut node = CorsDefinition {
7846 name,
7847 loc: Loc { line: tok.line, column: tok.column },
7848 ..Default::default()
7849 };
7850 self.consume(TokenType::LBrace)?;
7851 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7852 let key = self.consume_any_ident_or_kw()?.value;
7853 self.consume(TokenType::Colon)?;
7854 match key.as_str() {
7855 "allow_origins" => node.allow_origins = self.parse_bracketed_strings()?,
7856 "allow_methods" => node.allow_methods = self.parse_bracketed_identifiers()?,
7857 "allow_headers" => node.allow_headers = self.parse_bracketed_strings()?,
7858 "allow_credentials" => {
7859 node.allow_credentials = self.consume_any_ident_or_kw()?.value == "true"
7860 }
7861 "max_age" => node.max_age = Some(self.consume(TokenType::Duration)?.value),
7862 "expose_headers" => node.expose_headers = self.parse_bracketed_strings()?,
7863 other => return Err(self.error(&format!("unknown cors field `{other}`"))),
7864 }
7865 if self.check(TokenType::Comma) {
7867 self.consume(TokenType::Comma)?;
7868 }
7869 }
7870 self.consume(TokenType::RBrace)?;
7871 Ok(node)
7872 }
7873
7874 fn parse_credential(&mut self) -> Result<CredentialDefinition, ParseError> {
7882 let tok = self.consume(TokenType::Credential)?;
7883 let name = self.consume(TokenType::Identifier)?.value;
7884 let mut node = CredentialDefinition {
7885 name,
7886 loc: Loc { line: tok.line, column: tok.column },
7887 ..Default::default()
7888 };
7889 self.consume(TokenType::LBrace)?;
7890 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7891 let key = self.consume_any_ident_or_kw()?.value;
7892 self.consume(TokenType::Colon)?;
7893 match key.as_str() {
7894 "ttl" => node.ttl = self.consume(TokenType::Duration)?.value,
7895 "grants" => {
7896 let bracket_tok = self.current().clone();
7897 let items = self.parse_bracketed_dot_identifiers()?;
7898 for slug in &items {
7899 if !is_valid_capability_slug(slug) {
7900 return Err(ParseError {
7901 message: format!(
7902 "Invalid capability slug '{slug}' in credential '{}' \
7903 `grants:`. Capability slugs must match \
7904 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
7905 lowercase identifiers starting with a letter. Examples: \
7906 `chat.invoke`, `flow.execute`.",
7907 node.name
7908 ),
7909 line: bracket_tok.line,
7910 column: bracket_tok.column,
7911 ..Default::default()
7912 });
7913 }
7914 }
7915 node.grants = items;
7916 }
7917 other => return Err(self.error(&format!("unknown credential field `{other}`"))),
7918 }
7919 if self.check(TokenType::Comma) {
7921 self.consume(TokenType::Comma)?;
7922 }
7923 }
7924 self.consume(TokenType::RBrace)?;
7925 Ok(node)
7926 }
7927
7928 fn parse_cache(&mut self) -> Result<CacheDefinition, ParseError> {
7935 let tok = self.consume(TokenType::Cache)?;
7936 let name = self.consume(TokenType::Identifier)?.value;
7937 let mut node = CacheDefinition {
7938 name,
7939 loc: Loc { line: tok.line, column: tok.column },
7940 ..Default::default()
7941 };
7942 self.consume(TokenType::LBrace)?;
7943 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7944 let key = self.consume_any_ident_or_kw()?.value;
7945 self.consume(TokenType::Colon)?;
7946 match key.as_str() {
7947 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
7948 "ttl" => node.ttl = Some(self.consume(TokenType::Duration)?.value),
7949 "key" => node.key_params = self.parse_bracketed_identifiers()?,
7950 "default" => {
7951 node.default_policy = self.consume_any_ident_or_kw()?.value == "true"
7952 }
7953 "apply_to_effects" => {
7954 node.apply_to_effects = self.parse_bracketed_identifiers()?
7955 }
7956 "invalidate_on" => node.invalidate_on = self.parse_bracketed_identifiers()?,
7957 other => return Err(self.error(&format!("unknown cache field `{other}`"))),
7958 }
7959 if self.check(TokenType::Comma) {
7960 self.consume(TokenType::Comma)?;
7961 }
7962 }
7963 self.consume(TokenType::RBrace)?;
7964 Ok(node)
7965 }
7966
7967 fn parse_document(&mut self) -> Result<crate::ast::DocumentDefinition, ParseError> {
7975 let tok = self.consume(TokenType::Document)?;
7976 let name = self.consume(TokenType::Identifier)?.value;
7977 let mut node = crate::ast::DocumentDefinition {
7978 name,
7979 loc: Loc {
7980 line: tok.line,
7981 column: tok.column,
7982 },
7983 ..Default::default()
7984 };
7985 self.consume(TokenType::LBrace)?;
7986 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7987 let field = self.current().clone();
7988 let field_name = field.value.clone();
7989 self.advance();
7990 if self.check(TokenType::Colon) {
7991 self.advance();
7992 match field_name.as_str() {
7993 "target" => node.target = self.consume_any_ident_or_kw()?.value,
7994 "template" => node.template = self.parse_dotted_identifier()?,
7995 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
7996 "effects" => node.effects = Some(self.parse_effect_row()?),
7997 other => {
7998 return Err(self.error(&format!(
7999 "unknown document field `{other}` in document `{}` — expected \
8000 `target:` / `template:` / `provenance:` / `effects:`, or a body \
8001 block (`section {{ … }}` / `slide {{ … }}` / `sheet {{ … }}`)",
8002 node.name
8003 )))
8004 }
8005 }
8006 } else if self.check(TokenType::LBrace) {
8007 node.blocks
8008 .push(self.parse_doc_block_body(field_name, field.line, field.column)?);
8009 } else {
8010 return Err(self.error(&format!(
8011 "unexpected `{field_name}` in document `{}` body — expected a `field:` or a \
8012 body block `{field_name} {{ … }}`",
8013 node.name
8014 )));
8015 }
8016 if self.check(TokenType::Comma) {
8017 self.advance();
8018 }
8019 }
8020 self.consume(TokenType::RBrace)?;
8021 Ok(node)
8022 }
8023
8024 fn parse_doc_block_body(
8030 &mut self,
8031 kind: String,
8032 line: u32,
8033 column: u32,
8034 ) -> Result<crate::ast::DocBlock, ParseError> {
8035 let mut block = crate::ast::DocBlock {
8036 kind,
8037 loc: Loc { line, column },
8038 ..Default::default()
8039 };
8040 self.consume(TokenType::LBrace)?;
8041 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8042 let name_tok = self.current().clone();
8043 let name = self.consume_any_ident_or_kw()?.value;
8044 if self.check(TokenType::Colon) {
8045 self.advance();
8046 let value = self.parse_doc_scalar()?;
8047 block.fields.push((name, value));
8048 } else if self.check(TokenType::LBrace) {
8049 let child = self.parse_doc_block_body(name, name_tok.line, name_tok.column)?;
8050 block.children.push(child);
8051 } else {
8052 return Err(self.error(&format!(
8053 "in document block `{}`: `{name}` must be a `field:` value or open a nested \
8054 block `{name} {{ … }}`",
8055 block.kind
8056 )));
8057 }
8058 if self.check(TokenType::Comma) {
8059 self.advance();
8060 }
8061 }
8062 self.consume(TokenType::RBrace)?;
8063 Ok(block)
8064 }
8065
8066 fn parse_doc_scalar(&mut self) -> Result<crate::ast::DocScalar, ParseError> {
8071 let tok = self.current().clone();
8072 match tok.ttype {
8073 TokenType::StringLit => {
8074 self.advance();
8075 Ok(crate::ast::DocScalar::Text(tok.value))
8076 }
8077 TokenType::Integer => {
8078 self.advance();
8079 Ok(crate::ast::DocScalar::Int(tok.value.parse::<i64>().unwrap_or(0)))
8080 }
8081 TokenType::Bool => {
8082 self.advance();
8083 Ok(crate::ast::DocScalar::Bool(tok.value == "true"))
8084 }
8085 TokenType::LBracket => {
8086 let items = self.parse_bracketed_strings()?;
8087 Ok(crate::ast::DocScalar::List(items))
8088 }
8089 _ => {
8090 let name = self.consume_any_ident_or_kw()?.value;
8091 Ok(crate::ast::DocScalar::Ref(name))
8092 }
8093 }
8094 }
8095
8096 fn parse_notify(&mut self) -> Result<crate::ast::NotifyDefinition, ParseError> {
8121 let tok = self.consume(TokenType::Notify)?;
8122 let name = self.consume(TokenType::Identifier)?.value;
8123 let mut node = crate::ast::NotifyDefinition {
8124 name,
8125 loc: Loc {
8126 line: tok.line,
8127 column: tok.column,
8128 },
8129 ..Default::default()
8130 };
8131 self.consume(TokenType::LBrace)?;
8132 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8133 let field = self.current().clone();
8134 let field_name = field.value.clone();
8135 self.advance();
8136 if self.check(TokenType::Colon) {
8137 self.advance();
8138 match field_name.as_str() {
8139 "channel" => node.channel = self.consume_any_ident_or_kw()?.value,
8140 "to" => {
8141 if self.current().value == "secret" && self.peek_is_lparen() {
8145 self.advance(); self.consume(TokenType::LParen)?;
8147 node.to_secret = self.parse_dotted_identifier()?;
8148 self.consume(TokenType::RParen)?;
8149 node.to_is_secret = true;
8150 } else if self.check(TokenType::StringLit) {
8151 node.to_secret = self.consume(TokenType::StringLit)?.value.clone();
8152 node.to_is_secret = false;
8153 } else {
8154 node.to_secret = self.consume_any_ident_or_kw()?.value.clone();
8155 node.to_is_secret = false;
8156 }
8157 }
8158 "template" => {
8159 node.template = self.consume(TokenType::StringLit)?.value.clone()
8160 }
8161 "window" => {
8162 if self.check(TokenType::Integer) {
8165 let n = self.consume(TokenType::Integer)?.value.clone();
8166 let unit = self.consume_any_ident_or_kw()?.value.clone();
8167 node.window = format!("{n}{unit}");
8168 } else {
8169 node.window = self.consume_any_ident_or_kw()?.value.clone();
8170 }
8171 }
8172 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
8173 "effects" => node.effects = Some(self.parse_effect_row()?),
8174 other => {
8175 return Err(self.error(&format!(
8176 "unknown notify field `{other}` in notify `{}` — expected \
8177 `channel:` / `to:` / `template:` / `window:` / `provenance:` / \
8178 `effects:`",
8179 node.name
8180 )))
8181 }
8182 }
8183 }
8184 }
8185 self.consume(TokenType::RBrace)?;
8186 Ok(node)
8187 }
8188
8189 fn peek_is_identifier(&self) -> bool {
8193 self.tokens
8194 .get(self.pos + 1)
8195 .map(|t| t.ttype == TokenType::Identifier)
8196 .unwrap_or(false)
8197 }
8198
8199 fn peek_is_lparen(&self) -> bool {
8200 self.tokens
8201 .get(self.pos + 1)
8202 .map(|t| t.ttype == TokenType::LParen)
8203 .unwrap_or(false)
8204 }
8205
8206 fn parse_deliver(&mut self) -> Result<crate::ast::DeliverDefinition, ParseError> {
8208 let tok = self.consume(TokenType::Deliver)?;
8209 let name = self.consume(TokenType::Identifier)?.value;
8210 let mut node = crate::ast::DeliverDefinition {
8211 name,
8212 loc: Loc {
8213 line: tok.line,
8214 column: tok.column,
8215 },
8216 ..Default::default()
8217 };
8218 self.consume(TokenType::LBrace)?;
8219 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8220 let field = self.current().clone();
8221 let field_name = field.value.clone();
8222 self.advance();
8223 if self.check(TokenType::Colon) {
8224 self.advance();
8225 match field_name.as_str() {
8226 "target" => node.target = self.consume_any_ident_or_kw()?.value,
8227 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
8228 "secret" => node.secret = self.consume_any_ident_or_kw()?.value,
8229 "effects" => node.effects = Some(self.parse_effect_row()?),
8230 other => {
8231 return Err(self.error(&format!(
8232 "unknown deliver field `{other}` in deliver `{}` — expected \
8233 `target:` / `provenance:` / `secret:` / `effects:`, or an operation \
8234 block (`upsert_contact {{ … }}` / `create_deal {{ … }}` / \
8235 `add_note {{ … }}`)",
8236 node.name
8237 )))
8238 }
8239 }
8240 } else if self.check(TokenType::LBrace) {
8241 node.ops
8242 .push(self.parse_deliver_op(field_name, field.line, field.column)?);
8243 } else {
8244 return Err(self.error(&format!(
8245 "unexpected `{field_name}` in deliver `{}` body — expected a `field:` or an \
8246 operation block `{field_name} {{ … }}`",
8247 node.name
8248 )));
8249 }
8250 if self.check(TokenType::Comma) {
8251 self.advance();
8252 }
8253 }
8254 self.consume(TokenType::RBrace)?;
8255 Ok(node)
8256 }
8257
8258 fn parse_deliver_op(
8262 &mut self,
8263 kind: String,
8264 line: u32,
8265 column: u32,
8266 ) -> Result<crate::ast::DeliverOp, ParseError> {
8267 let mut op = crate::ast::DeliverOp {
8268 kind,
8269 loc: Loc { line, column },
8270 ..Default::default()
8271 };
8272 self.consume(TokenType::LBrace)?;
8273 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8274 let name = self.consume_any_ident_or_kw()?.value;
8275 self.consume(TokenType::Colon).map_err(|_| {
8276 self.error(&format!(
8277 "in deliver operation `{}`: `{name}` must be a `field: value` pair — an \
8278 operation binds scalar fields, it takes no nested blocks",
8279 op.kind
8280 ))
8281 })?;
8282 let value = self.parse_doc_scalar()?;
8283 op.fields.push((name, value));
8284 if self.check(TokenType::Comma) {
8285 self.advance();
8286 }
8287 }
8288 self.consume(TokenType::RBrace)?;
8289 Ok(op)
8290 }
8291
8292 fn parse_savant(&mut self) -> Result<SavantDefinition, ParseError> {
8298 let tok = self.consume(TokenType::Savant)?;
8299 let name = self.consume(TokenType::Identifier)?.value;
8300 let mut node = SavantDefinition {
8301 name,
8302 loc: Loc {
8303 line: tok.line,
8304 column: tok.column,
8305 },
8306 ..Default::default()
8307 };
8308 self.consume(TokenType::LBrace)?;
8309 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8310 let field = self.current().clone();
8311 let field_name = field.value.clone();
8312 self.advance();
8313 if self.check(TokenType::Colon) {
8314 self.advance();
8315 match field_name.as_str() {
8316 "domain" => node.domain = self.consume(TokenType::StringLit)?.value,
8317 other => {
8318 return Err(self.error(&format!(
8319 "unknown savant field `{other}` in savant `{}` — expected \
8320 `domain:` or a `cognition` / `memory` / `budget` / `mandate` block",
8321 node.name
8322 )))
8323 }
8324 }
8325 } else if field_name == "cognition" {
8326 node.cognition = Some(self.parse_savant_cognition(field.line, field.column)?);
8327 } else if field_name == "memory" {
8328 node.memory = Some(self.parse_savant_memory(field.line, field.column)?);
8329 } else if field_name == "budget" {
8330 node.budget = Some(self.parse_savant_budget(field.line, field.column)?);
8331 } else if field_name == "mandate" {
8332 node.mandates
8333 .push(self.parse_savant_mandate(field.line, field.column)?);
8334 } else {
8335 return Err(self.error(&format!(
8336 "unexpected `{field_name}` in savant `{}` body — expected `domain:` or a \
8337 `cognition` / `memory` / `budget` / `mandate` block",
8338 node.name
8339 )));
8340 }
8341 if self.check(TokenType::Comma) {
8342 self.advance();
8343 }
8344 }
8345 self.consume(TokenType::RBrace)?;
8346 Ok(node)
8347 }
8348
8349 fn parse_savant_cognition(
8352 &mut self,
8353 line: u32,
8354 column: u32,
8355 ) -> Result<SavantCognition, ParseError> {
8356 self.consume(TokenType::LBrace)?;
8357 let mut node = SavantCognition {
8358 loc: Loc { line, column },
8359 ..Default::default()
8360 };
8361 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8362 let key = self.consume_any_ident_or_kw()?.value;
8363 self.consume(TokenType::Colon)?;
8364 match key.as_str() {
8365 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
8366 "entropic_threshold" => node.entropic_threshold = self.parse_optional_float(),
8367 "divergence" => node.divergence = self.consume_any_ident_or_kw()?.value,
8368 other => {
8369 return Err(self.error(&format!(
8370 "unknown savant `cognition` field `{other}` — expected \
8371 `depth` / `entropic_threshold` / `divergence`"
8372 )))
8373 }
8374 }
8375 if self.check(TokenType::Comma) {
8376 self.advance();
8377 }
8378 }
8379 self.consume(TokenType::RBrace)?;
8380 Ok(node)
8381 }
8382
8383 fn parse_savant_memory(
8386 &mut self,
8387 line: u32,
8388 column: u32,
8389 ) -> Result<SavantMemory, ParseError> {
8390 self.consume(TokenType::LBrace)?;
8391 let mut node = SavantMemory {
8392 loc: Loc { line, column },
8393 ..Default::default()
8394 };
8395 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8396 let key = self.consume_any_ident_or_kw()?.value;
8397 self.consume(TokenType::Colon)?;
8398 match key.as_str() {
8399 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
8400 "corpus_graph" => {
8401 node.corpus_graph = self.consume_any_ident_or_kw()?.value == "true"
8402 }
8403 "isolation_level" => node.isolation_level = self.consume_any_ident_or_kw()?.value,
8404 other => {
8405 return Err(self.error(&format!(
8406 "unknown savant `memory` field `{other}` — expected \
8407 `backend` / `corpus_graph` / `isolation_level`"
8408 )))
8409 }
8410 }
8411 if self.check(TokenType::Comma) {
8412 self.advance();
8413 }
8414 }
8415 self.consume(TokenType::RBrace)?;
8416 Ok(node)
8417 }
8418
8419 fn parse_savant_budget(
8422 &mut self,
8423 line: u32,
8424 column: u32,
8425 ) -> Result<SavantBudget, ParseError> {
8426 self.consume(TokenType::LBrace)?;
8427 let mut node = SavantBudget {
8428 loc: Loc { line, column },
8429 ..Default::default()
8430 };
8431 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8432 let key = self.consume_any_ident_or_kw()?.value;
8433 self.consume(TokenType::Colon)?;
8434 match key.as_str() {
8435 "max_iterations" => node.max_iterations = self.parse_optional_int(),
8436 "max_tool_synth" => node.max_tool_synth = self.parse_optional_int(),
8437 other => {
8438 return Err(self.error(&format!(
8439 "unknown savant `budget` field `{other}` — expected \
8440 `max_iterations` / `max_tool_synth`"
8441 )))
8442 }
8443 }
8444 if self.check(TokenType::Comma) {
8445 self.advance();
8446 }
8447 }
8448 self.consume(TokenType::RBrace)?;
8449 Ok(node)
8450 }
8451
8452 fn parse_savant_mandate(
8455 &mut self,
8456 line: u32,
8457 column: u32,
8458 ) -> Result<SavantMandate, ParseError> {
8459 let name = self.consume(TokenType::Identifier)?.value;
8460 let mut node = SavantMandate {
8461 name,
8462 loc: Loc { line, column },
8463 ..Default::default()
8464 };
8465 self.consume(TokenType::LBrace)?;
8466 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8467 let key = self.consume_any_ident_or_kw()?.value;
8468 self.consume(TokenType::Colon)?;
8469 match key.as_str() {
8470 "objective" => node.objective = self.consume(TokenType::StringLit)?.value,
8471 "output" => node.output_type = self.consume_any_ident_or_kw()?.value,
8472 other => {
8473 return Err(self.error(&format!(
8474 "unknown savant `mandate` field `{other}` — expected `objective` / `output`"
8475 )))
8476 }
8477 }
8478 if self.check(TokenType::Comma) {
8479 self.advance();
8480 }
8481 }
8482 self.consume(TokenType::RBrace)?;
8483 Ok(node)
8484 }
8485
8486 fn parse_synth(&mut self) -> Result<SynthDefinition, ParseError> {
8491 let tok = self.consume(TokenType::Synth)?;
8492 let name = self.consume(TokenType::Identifier)?.value;
8493 let mut node = SynthDefinition {
8494 name,
8495 loc: Loc {
8496 line: tok.line,
8497 column: tok.column,
8498 },
8499 ..Default::default()
8500 };
8501 self.consume(TokenType::LBrace)?;
8502 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8503 let key = self.consume_any_ident_or_kw()?.value;
8504 self.consume(TokenType::Colon)?;
8505 match key.as_str() {
8506 "target" => node.target = self.consume(TokenType::StringLit)?.value,
8507 "risk" => node.risk = self.consume_any_ident_or_kw()?.value,
8508 "language" => node.language = self.consume_any_ident_or_kw()?.value,
8509 "sandbox" => node.sandbox = self.consume_any_ident_or_kw()?.value,
8510 "review" => node.review = self.consume_any_ident_or_kw()?.value,
8511 "max_lines" => node.max_lines = self.parse_optional_int(),
8512 other => {
8513 return Err(self.error(&format!(
8514 "unknown synth field `{other}` in synth `{}` — expected `target` / `risk` \
8515 / `language` / `sandbox` / `review` / `max_lines`",
8516 node.name
8517 )))
8518 }
8519 }
8520 if self.check(TokenType::Comma) {
8521 self.consume(TokenType::Comma)?;
8522 }
8523 }
8524 self.consume(TokenType::RBrace)?;
8525 Ok(node)
8526 }
8527
8528 fn parse_voice(&mut self) -> Result<VoiceDefinition, ParseError> {
8533 let tok = self.consume(TokenType::Voice)?;
8534 let name = self.consume(TokenType::Identifier)?.value;
8535 let mut node = VoiceDefinition {
8536 name,
8537 loc: Loc { line: tok.line, column: tok.column },
8538 ..Default::default()
8539 };
8540 self.consume(TokenType::LBrace)?;
8541 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8542 let key = self.consume_any_ident_or_kw()?.value;
8543 self.consume(TokenType::Colon)?;
8544 match key.as_str() {
8545 "stt" => node.stt = Some(self.parse_upstream_ref()?),
8547 "tts" => node.tts = Some(self.parse_upstream_ref()?),
8548 "realtime" => node.realtime = Some(self.parse_upstream_ref()?),
8549 "carrier" => node.carrier = self.consume_any_ident_or_kw()?.value,
8550 "interruptible" => {
8551 let v = self.consume_any_ident_or_kw()?.value;
8552 node.interruptible = v == "true";
8553 }
8554 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
8555 "persona" => node.persona = Some(self.consume(TokenType::Identifier)?.value),
8556 "context" => node.context = Some(self.consume(TokenType::Identifier)?.value),
8557 other => return Err(self.error(&format!("unknown voice field `{other}`"))),
8558 }
8559 if self.check(TokenType::Comma) {
8560 self.consume(TokenType::Comma)?;
8561 }
8562 }
8563 self.consume(TokenType::RBrace)?;
8564 Ok(node)
8565 }
8566
8567 fn parse_upstream_ref(&mut self) -> Result<String, ParseError> {
8570 let base = self.consume(TokenType::Identifier)?.value;
8571 if self.check(TokenType::At) {
8572 self.advance();
8573 let version = self.consume_any_ident_or_kw()?.value;
8574 Ok(format!("{base}@{version}"))
8575 } else {
8576 Ok(base)
8577 }
8578 }
8579
8580 fn parse_upstream_map(&mut self) -> Result<Vec<UpstreamMapRule>, ParseError> {
8586 self.consume(TokenType::LBracket)?;
8587 let mut rules = Vec::new();
8588 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8589 let dir_tok = self.current().clone();
8590 let direction = match dir_tok.ttype {
8591 TokenType::Send => "send",
8592 TokenType::Receive => "receive",
8593 _ => {
8594 return Err(self.error(&format!(
8595 "upstream map rule must start with `send` or `receive`, got `{}`",
8596 dir_tok.value
8597 )))
8598 }
8599 };
8600 self.advance();
8601 let message = self.consume(TokenType::Identifier)?.value;
8602 self.consume(TokenType::As)?;
8603 let framing = self.consume_any_ident_or_kw()?.value;
8604 let mut rule = UpstreamMapRule {
8605 direction: direction.to_string(),
8606 message,
8607 framing,
8608 loc: Loc { line: dir_tok.line, column: dir_tok.column },
8609 ..Default::default()
8610 };
8611 if self.current().value == "tag" {
8613 self.advance();
8614 rule.tag = Some(self.consume(TokenType::StringLit)?.value);
8615 } else if self.current().value == "when" {
8616 self.advance();
8621 rule.when_field = Some(self.consume(TokenType::StringLit)?.value);
8622 if self.check(TokenType::Assign) {
8623 self.advance();
8624 rule.when_value = Some(self.consume(TokenType::StringLit)?.value);
8625 }
8626 }
8627 rules.push(rule);
8628 if self.check(TokenType::Comma) {
8629 self.advance();
8630 }
8631 }
8632 self.consume(TokenType::RBracket)?;
8633 Ok(rules)
8634 }
8635
8636 fn parse_upstream_reconnect(&mut self) -> Result<UpstreamReconnect, ParseError> {
8640 self.consume(TokenType::LBrace)?;
8641 let mut backoff_ms: Option<i64> = None;
8642 let mut max_attempts: Option<i64> = None;
8643 let mut on_exhausted: Option<String> = None;
8644 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8645 let key = self.consume_any_ident_or_kw()?.value;
8646 self.consume(TokenType::Colon)?;
8647 match key.as_str() {
8648 "backoff_ms" => {
8649 backoff_ms = Some(
8650 self.consume(TokenType::Integer)?
8651 .value
8652 .parse::<i64>()
8653 .map_err(|_| self.error("backoff_ms must be an integer"))?,
8654 )
8655 }
8656 "max_attempts" => {
8657 max_attempts = Some(
8658 self.consume(TokenType::Integer)?
8659 .value
8660 .parse::<i64>()
8661 .map_err(|_| self.error("max_attempts must be an integer"))?,
8662 )
8663 }
8664 "on_exhausted" => on_exhausted = Some(self.consume_any_ident_or_kw()?.value),
8665 other => return Err(self.error(&format!("unknown reconnect field `{other}`"))),
8666 }
8667 if self.check(TokenType::Comma) {
8668 self.consume(TokenType::Comma)?;
8669 }
8670 }
8671 self.consume(TokenType::RBrace)?;
8672 match (backoff_ms, max_attempts, on_exhausted) {
8673 (Some(b), Some(m), Some(o)) => Ok(UpstreamReconnect { backoff_ms: b, max_attempts: m, on_exhausted: o }),
8674 _ => Err(self.error(
8675 "reconnect requires all of `backoff_ms:`, `max_attempts:`, `on_exhausted:` — a reconnection policy with a hole is not a policy",
8676 )),
8677 }
8678 }
8679
8680 fn parse_session_steps(&mut self) -> Result<Vec<SessionStep>, ParseError> {
8682 self.consume(TokenType::LBracket)?;
8683 let mut steps = Vec::new();
8684 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8685 steps.push(self.parse_session_step()?);
8686 if self.check(TokenType::Comma) {
8687 self.advance();
8688 }
8689 }
8690 self.consume(TokenType::RBracket)?;
8691 Ok(steps)
8692 }
8693
8694 fn parse_session_step_block(&mut self) -> Result<Vec<SessionStep>, ParseError> {
8698 self.consume(TokenType::LBrace)?;
8699 let mut steps = Vec::new();
8700 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8701 steps.push(self.parse_session_step()?);
8702 if self.check(TokenType::Comma) {
8703 self.advance();
8704 }
8705 }
8706 self.consume(TokenType::RBrace)?;
8707 Ok(steps)
8708 }
8709
8710 fn parse_session_step(&mut self) -> Result<SessionStep, ParseError> {
8711 let tok = self.current().clone();
8712 let loc = Loc { line: tok.line, column: tok.column };
8713 match tok.ttype {
8714 TokenType::Send => {
8715 self.advance();
8716 let msg = self.consume_any_ident_or_kw()?;
8717 Ok(SessionStep { op: "send".into(), message_type: msg.value, loc, ..Default::default() })
8718 }
8719 TokenType::Receive => {
8720 self.advance();
8721 let msg = self.consume_any_ident_or_kw()?;
8722 Ok(SessionStep { op: "receive".into(), message_type: msg.value, loc, ..Default::default() })
8723 }
8724 TokenType::Loop => {
8725 self.advance();
8726 Ok(SessionStep { op: "loop".into(), loc, ..Default::default() })
8727 }
8728 TokenType::End => {
8729 self.advance();
8730 Ok(SessionStep { op: "end".into(), loc, ..Default::default() })
8731 }
8732 TokenType::Identifier if tok.value == "select" || tok.value == "branch" => {
8735 self.parse_session_choice(&tok.value, loc)
8736 }
8737 TokenType::Identifier if tok.value == "interrupt" => {
8740 self.parse_session_interrupt(loc)
8741 }
8742 TokenType::Identifier if tok.value == "resume" => {
8746 self.advance();
8747 Ok(SessionStep { op: "resume".into(), loc, ..Default::default() })
8748 }
8749 _ => Err(ParseError {
8750 message: format!(
8751 "Invalid session step '{}' — expected send | receive | loop | end | select | branch | interrupt | resume",
8752 tok.value
8753 ),
8754 line: tok.line,
8755 column: tok.column,
8756 ..Default::default()
8757 }),
8758 }
8759 }
8760
8761 fn consume_contextual(&mut self, kw: &str) -> Result<(), ParseError> {
8766 let t = self.current().clone();
8767 if t.value != kw {
8768 return Err(ParseError {
8769 message: format!("expected `{kw}` in interrupt step, got `{}`", t.value),
8770 line: t.line,
8771 column: t.column,
8772 ..Default::default()
8773 });
8774 }
8775 self.advance();
8776 Ok(())
8777 }
8778
8779 fn parse_session_interrupt(&mut self, loc: Loc) -> Result<SessionStep, ParseError> {
8787 self.advance(); let body = self.parse_session_step_block()?;
8791 self.consume_contextual("on")?;
8793 let signal = self.consume_any_ident_or_kw()?;
8794 self.consume_contextual("as")?;
8796 let binder = self.consume_any_ident_or_kw()?;
8797 self.consume_contextual("resumable")?;
8799 let handler = self.parse_session_step_block()?;
8800 Ok(SessionStep {
8801 op: "interrupt".into(),
8802 message_type: signal.value,
8803 branches: vec![
8804 SessionBranch { label: "body".into(), steps: body, loc: loc.clone() },
8805 SessionBranch { label: "handler".into(), steps: handler, loc: loc.clone() },
8806 ],
8807 binder: binder.value,
8808 resumable: true,
8809 loc,
8810 })
8811 }
8812
8813 fn parse_session_choice(&mut self, op: &str, loc: Loc) -> Result<SessionStep, ParseError> {
8816 self.advance(); self.consume(TokenType::LBrace)?;
8818 let mut branches = Vec::new();
8819 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8820 let label_tok = self.consume_any_ident_or_kw()?;
8821 self.consume(TokenType::Colon)?;
8822 let steps = self.parse_session_steps()?;
8823 branches.push(SessionBranch {
8824 label: label_tok.value,
8825 steps,
8826 loc: Loc { line: label_tok.line, column: label_tok.column },
8827 });
8828 if self.check(TokenType::Comma) {
8829 self.advance();
8830 }
8831 }
8832 self.consume(TokenType::RBrace)?;
8833 Ok(SessionStep { op: op.to_string(), branches, loc, ..Default::default() })
8834 }
8835
8836 fn parse_topology(&mut self) -> Result<TopologyDefinition, ParseError> {
8838 let tok = self.consume(TokenType::Topology)?;
8839 let name = self.consume(TokenType::Identifier)?.value;
8840 let mut node = TopologyDefinition {
8841 name,
8842 nodes: Vec::new(),
8843 edges: Vec::new(),
8844 loc: Loc {
8845 line: tok.line,
8846 column: tok.column,
8847 },
8848 leading_trivia: Vec::new(),
8849 trailing_trivia: Vec::new(),
8850 };
8851 self.consume(TokenType::LBrace)?;
8852 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8853 let field_name = self.current().value.clone();
8854 self.advance();
8855 if !self.check(TokenType::Colon) {
8856 if self.check(TokenType::LBrace) {
8857 self.skip_braced_block()?;
8858 }
8859 continue;
8860 }
8861 self.advance();
8862 match field_name.as_str() {
8863 "nodes" => node.nodes = self.parse_bracketed_identifiers()?,
8864 "edges" => node.edges = self.parse_topology_edges()?,
8865 _ => self.skip_value(),
8866 }
8867 }
8868 self.consume(TokenType::RBrace)?;
8869 Ok(node)
8870 }
8871
8872 fn parse_topology_edges(&mut self) -> Result<Vec<TopologyEdge>, ParseError> {
8873 self.consume(TokenType::LBracket)?;
8874 let mut edges = Vec::new();
8875 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8876 edges.push(self.parse_topology_edge()?);
8877 if self.check(TokenType::Comma) {
8878 self.advance();
8879 }
8880 }
8881 self.consume(TokenType::RBracket)?;
8882 Ok(edges)
8883 }
8884
8885 fn parse_topology_edge(&mut self) -> Result<TopologyEdge, ParseError> {
8886 let src_tok = self.consume_any_ident_or_kw()?;
8887 self.consume(TokenType::Arrow)?;
8888 let tgt_tok = self.consume_any_ident_or_kw()?;
8889 self.consume(TokenType::Colon)?;
8890 let sess_tok = self.consume_any_ident_or_kw()?;
8891 Ok(TopologyEdge {
8892 source: src_tok.value,
8893 target: tgt_tok.value,
8894 session_ref: sess_tok.value,
8895 loc: Loc {
8896 line: src_tok.line,
8897 column: src_tok.column,
8898 },
8899 })
8900 }
8901
8902 fn parse_immune(&mut self) -> Result<ImmuneDefinition, ParseError> {
8906 let tok = self.consume(TokenType::Immune)?;
8907 let name = self.consume(TokenType::Identifier)?.value;
8908 let mut node = ImmuneDefinition {
8909 name,
8910 watch: Vec::new(),
8911 sensitivity: None,
8912 baseline: "learned".to_string(),
8913 window: 100,
8914 scope: String::new(),
8915 tau: String::new(),
8916 decay: "exponential".to_string(),
8917 loc: Loc {
8918 line: tok.line,
8919 column: tok.column,
8920 },
8921 leading_trivia: Vec::new(),
8922 trailing_trivia: Vec::new(),
8923 };
8924 self.consume(TokenType::LBrace)?;
8925 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8926 let field_name = self.current().value.clone();
8927 self.advance();
8928 if !self.check(TokenType::Colon) {
8929 if self.check(TokenType::LBrace) {
8930 self.skip_braced_block()?;
8931 }
8932 continue;
8933 }
8934 self.advance();
8935 match field_name.as_str() {
8936 "watch" => node.watch = self.parse_bracketed_identifiers()?,
8937 "sensitivity" => node.sensitivity = self.parse_optional_float(),
8938 "baseline" => node.baseline = self.consume_any_ident_or_kw()?.value,
8939 "window" => {
8940 if let Some(v) = self.parse_optional_int() {
8941 node.window = v;
8942 }
8943 }
8944 "scope" => {
8945 let s_tok = self.consume_any_ident_or_kw()?;
8946 let s = s_tok.value;
8947 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
8948 return Err(ParseError {
8949 message: format!(
8950 "Invalid scope '{s}' in immune '{}' — \
8951 expected tenant | flow | global",
8952 node.name
8953 ),
8954 line: s_tok.line,
8955 column: s_tok.column,
8956 ..Default::default()
8957 });
8958 }
8959 node.scope = s;
8960 }
8961 "tau" => {
8962 let t = self.current().clone();
8963 match t.ttype {
8964 TokenType::Duration | TokenType::StringLit => {
8965 self.advance();
8966 node.tau = t.value;
8967 }
8968 _ => node.tau = self.consume_any_ident_or_kw()?.value,
8969 }
8970 }
8971 "decay" => {
8972 let d_tok = self.consume_any_ident_or_kw()?;
8973 let d = d_tok.value;
8974 if !matches!(d.as_str(), "exponential" | "linear" | "none") {
8975 return Err(ParseError {
8976 message: format!(
8977 "Invalid decay '{d}' in immune '{}' — \
8978 expected exponential | linear | none",
8979 node.name
8980 ),
8981 line: d_tok.line,
8982 column: d_tok.column,
8983 ..Default::default()
8984 });
8985 }
8986 node.decay = d;
8987 }
8988 _ => self.skip_value(),
8989 }
8990 }
8991 self.consume(TokenType::RBrace)?;
8992 Ok(node)
8993 }
8994
8995 fn parse_reflex(&mut self) -> Result<ReflexDefinition, ParseError> {
8997 let tok = self.consume(TokenType::Reflex)?;
8998 let name = self.consume(TokenType::Identifier)?.value;
8999 let mut node = ReflexDefinition {
9000 name,
9001 trigger: String::new(),
9002 on_level: "doubt".to_string(),
9003 action: String::new(),
9004 scope: String::new(),
9005 sla: String::new(),
9006 loc: Loc {
9007 line: tok.line,
9008 column: tok.column,
9009 },
9010 leading_trivia: Vec::new(),
9011 trailing_trivia: Vec::new(),
9012 };
9013 self.consume(TokenType::LBrace)?;
9014 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9015 let field_name = self.current().value.clone();
9016 self.advance();
9017 if !self.check(TokenType::Colon) {
9018 if self.check(TokenType::LBrace) {
9019 self.skip_braced_block()?;
9020 }
9021 continue;
9022 }
9023 self.advance();
9024 match field_name.as_str() {
9025 "trigger" => node.trigger = self.consume_any_ident_or_kw()?.value,
9026 "on_level" => {
9027 let l_tok = self.consume_any_ident_or_kw()?;
9028 let l = l_tok.value;
9029 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
9030 return Err(ParseError {
9031 message: format!(
9032 "Invalid on_level '{l}' in reflex '{}' — \
9033 expected know | believe | speculate | doubt",
9034 node.name
9035 ),
9036 line: l_tok.line,
9037 column: l_tok.column,
9038 ..Default::default()
9039 });
9040 }
9041 node.on_level = l;
9042 }
9043 "action" => {
9044 let a_tok = self.consume_any_ident_or_kw()?;
9045 let a = a_tok.value;
9046 if !matches!(
9047 a.as_str(),
9048 "drop"
9049 | "revoke"
9050 | "emit"
9051 | "redact"
9052 | "quarantine"
9053 | "terminate"
9054 | "alert"
9055 ) {
9056 return Err(ParseError {
9057 message: format!(
9058 "Invalid action '{a}' in reflex '{}' — \
9059 expected drop | revoke | emit | redact | \
9060 quarantine | terminate | alert",
9061 node.name
9062 ),
9063 line: a_tok.line,
9064 column: a_tok.column,
9065 ..Default::default()
9066 });
9067 }
9068 node.action = a;
9069 }
9070 "scope" => {
9071 let s_tok = self.consume_any_ident_or_kw()?;
9072 let s = s_tok.value;
9073 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
9074 return Err(ParseError {
9075 message: format!(
9076 "Invalid scope '{s}' in reflex '{}' — \
9077 expected tenant | flow | global",
9078 node.name
9079 ),
9080 line: s_tok.line,
9081 column: s_tok.column,
9082 ..Default::default()
9083 });
9084 }
9085 node.scope = s;
9086 }
9087 "sla" => {
9088 let t = self.current().clone();
9089 match t.ttype {
9090 TokenType::Duration | TokenType::StringLit => {
9091 self.advance();
9092 node.sla = t.value;
9093 }
9094 _ => node.sla = self.consume_any_ident_or_kw()?.value,
9095 }
9096 }
9097 _ => self.skip_value(),
9098 }
9099 }
9100 self.consume(TokenType::RBrace)?;
9101 Ok(node)
9102 }
9103
9104 fn parse_heal(&mut self) -> Result<HealDefinition, ParseError> {
9106 let tok = self.consume(TokenType::Heal)?;
9107 let name = self.consume(TokenType::Identifier)?.value;
9108 let mut node = HealDefinition {
9109 name,
9110 source: String::new(),
9111 on_level: "doubt".to_string(),
9112 mode: "human_in_loop".to_string(),
9113 scope: String::new(),
9114 review_sla: String::new(),
9115 shield_ref: String::new(),
9116 max_patches: 3,
9117 loc: Loc {
9118 line: tok.line,
9119 column: tok.column,
9120 },
9121 leading_trivia: Vec::new(),
9122 trailing_trivia: Vec::new(),
9123 };
9124 self.consume(TokenType::LBrace)?;
9125 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9126 let field_name = self.current().value.clone();
9127 self.advance();
9128 if !self.check(TokenType::Colon) {
9129 if self.check(TokenType::LBrace) {
9130 self.skip_braced_block()?;
9131 }
9132 continue;
9133 }
9134 self.advance();
9135 match field_name.as_str() {
9136 "source" => node.source = self.consume_any_ident_or_kw()?.value,
9137 "on_level" => {
9138 let l_tok = self.consume_any_ident_or_kw()?;
9139 let l = l_tok.value;
9140 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
9141 return Err(ParseError {
9142 message: format!(
9143 "Invalid on_level '{l}' in heal '{}' — \
9144 expected know | believe | speculate | doubt",
9145 node.name
9146 ),
9147 line: l_tok.line,
9148 column: l_tok.column,
9149 ..Default::default()
9150 });
9151 }
9152 node.on_level = l;
9153 }
9154 "mode" => {
9155 let m_tok = self.consume_any_ident_or_kw()?;
9156 let m = m_tok.value;
9157 if !matches!(m.as_str(), "audit_only" | "human_in_loop" | "adversarial") {
9158 return Err(ParseError {
9159 message: format!(
9160 "Invalid mode '{m}' in heal '{}' — \
9161 expected audit_only | human_in_loop | adversarial",
9162 node.name
9163 ),
9164 line: m_tok.line,
9165 column: m_tok.column,
9166 ..Default::default()
9167 });
9168 }
9169 node.mode = m;
9170 }
9171 "scope" => {
9172 let s_tok = self.consume_any_ident_or_kw()?;
9173 let s = s_tok.value;
9174 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
9175 return Err(ParseError {
9176 message: format!(
9177 "Invalid scope '{s}' in heal '{}' — \
9178 expected tenant | flow | global",
9179 node.name
9180 ),
9181 line: s_tok.line,
9182 column: s_tok.column,
9183 ..Default::default()
9184 });
9185 }
9186 node.scope = s;
9187 }
9188 "review_sla" => {
9189 let t = self.current().clone();
9190 match t.ttype {
9191 TokenType::Duration | TokenType::StringLit => {
9192 self.advance();
9193 node.review_sla = t.value;
9194 }
9195 _ => node.review_sla = self.consume_any_ident_or_kw()?.value,
9196 }
9197 }
9198 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
9199 "max_patches" => {
9200 if let Some(v) = self.parse_optional_int() {
9201 node.max_patches = v;
9202 }
9203 }
9204 _ => self.skip_value(),
9205 }
9206 }
9207 self.consume(TokenType::RBrace)?;
9208 Ok(node)
9209 }
9210
9211 fn parse_component(&mut self) -> Result<ComponentDefinition, ParseError> {
9215 let tok = self.consume(TokenType::Component)?;
9216 let name = self.consume(TokenType::Identifier)?.value;
9217 let mut node = ComponentDefinition {
9218 name,
9219 renders: String::new(),
9220 via_shield: String::new(),
9221 on_interact: String::new(),
9222 render_hint: "custom".to_string(),
9223 loc: Loc {
9224 line: tok.line,
9225 column: tok.column,
9226 },
9227 leading_trivia: Vec::new(),
9228 trailing_trivia: Vec::new(),
9229 };
9230 self.consume(TokenType::LBrace)?;
9231 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9232 let field_name = self.current().value.clone();
9233 self.advance();
9234 if !self.check(TokenType::Colon) {
9235 if self.check(TokenType::LBrace) {
9236 self.skip_braced_block()?;
9237 }
9238 continue;
9239 }
9240 self.advance();
9241 match field_name.as_str() {
9242 "renders" => node.renders = self.consume_any_ident_or_kw()?.value,
9243 "via_shield" => node.via_shield = self.consume_any_ident_or_kw()?.value,
9244 "on_interact" => node.on_interact = self.consume_any_ident_or_kw()?.value,
9245 "render_hint" => {
9246 let h_tok = self.consume_any_ident_or_kw()?;
9247 let h = h_tok.value;
9248 if !matches!(h.as_str(), "card" | "list" | "form" | "chart" | "custom") {
9249 return Err(ParseError {
9250 message: format!(
9251 "Invalid render_hint '{h}' in component '{}' — \
9252 expected card | list | form | chart | custom",
9253 node.name
9254 ),
9255 line: h_tok.line,
9256 column: h_tok.column,
9257 ..Default::default()
9258 });
9259 }
9260 node.render_hint = h;
9261 }
9262 _ => self.skip_value(),
9263 }
9264 }
9265 self.consume(TokenType::RBrace)?;
9266 Ok(node)
9267 }
9268
9269 fn parse_view(&mut self) -> Result<ViewDefinition, ParseError> {
9271 let tok = self.consume(TokenType::View)?;
9272 let name = self.consume(TokenType::Identifier)?.value;
9273 let mut node = ViewDefinition {
9274 name,
9275 title: String::new(),
9276 components: Vec::new(),
9277 route: String::new(),
9278 loc: Loc {
9279 line: tok.line,
9280 column: tok.column,
9281 },
9282 leading_trivia: Vec::new(),
9283 trailing_trivia: Vec::new(),
9284 };
9285 self.consume(TokenType::LBrace)?;
9286 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9287 let field_name = self.current().value.clone();
9288 self.advance();
9289 if !self.check(TokenType::Colon) {
9290 if self.check(TokenType::LBrace) {
9291 self.skip_braced_block()?;
9292 }
9293 continue;
9294 }
9295 self.advance();
9296 match field_name.as_str() {
9297 "title" => node.title = self.consume(TokenType::StringLit)?.value,
9298 "components" => node.components = self.parse_bracketed_identifiers()?,
9299 "route" => node.route = self.consume(TokenType::StringLit)?.value,
9300 _ => self.skip_value(),
9301 }
9302 }
9303 self.consume(TokenType::RBrace)?;
9304 Ok(node)
9305 }
9306
9307 fn parse_axonendpoint(&mut self) -> Result<AxonEndpointDefinition, ParseError> {
9308 let tok = self.consume(TokenType::AxonEndpoint)?;
9309 let name = self.consume(TokenType::Identifier)?.value;
9310 let mut node = AxonEndpointDefinition {
9311 name,
9312 method: String::new(),
9313 path: String::new(),
9314 body_type: String::new(),
9315 execute_flow: String::new(),
9316 output_type: String::new(),
9317 shield_ref: String::new(),
9318 cors_ref: String::new(),
9321 retries: None,
9322 timeout: String::new(),
9323 compliance: Vec::new(),
9324 transport: "json".to_string(),
9326 keepalive: String::new(),
9327 transport_explicit: false,
9333 implicit_transport: String::new(),
9334 requires_capabilities: Vec::new(),
9336 public: false,
9339 replay_explicit: false,
9343 replay: false,
9344 transport_dialect: String::new(),
9349 has_algebraic_stream_effect: false,
9355 backend: String::new(),
9360 path_params: Vec::new(),
9365 query_params: Vec::new(),
9370 loc: Loc {
9371 line: tok.line,
9372 column: tok.column,
9373 },
9374 leading_trivia: Vec::new(),
9375 trailing_trivia: Vec::new(),
9376 };
9377 self.consume(TokenType::LBrace)?;
9378 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9379 let field_name = self.current().value.clone();
9380 self.advance();
9381 if self.check(TokenType::Colon) {
9382 self.advance();
9383 match field_name.as_str() {
9384 "method" => {
9385 let value_tok = self.consume_any_ident_or_kw()?;
9392 let value_upper = value_tok.value.to_uppercase();
9393 if !axonendpoint_is_valid_method(&value_upper) {
9394 let hint = crate::smart_suggest::suggest_for(
9395 &value_upper,
9396 AXONENDPOINT_METHOD_VALUES,
9397 );
9398 let base = format!(
9399 "Invalid method '{}' in axonendpoint '{}'.",
9400 value_tok.value, node.name
9401 );
9402 let message = if hint.is_empty() {
9403 format!(
9404 "{base} expected GET | POST | PUT | DELETE | PATCH, found {}",
9405 value_tok.value
9406 )
9407 } else {
9408 format!(
9409 "{base} {hint} (expected GET | POST | PUT | DELETE | PATCH, found {})",
9410 value_tok.value
9411 )
9412 };
9413 return Err(ParseError {
9414 message,
9415 line: value_tok.line,
9416 column: value_tok.column,
9417 ..Default::default()
9418 });
9419 }
9420 node.method = value_upper;
9421 }
9422 "path" => {
9423 node.path = self.consume(TokenType::StringLit)?.value.clone();
9424 match extract_path_param_names(&node.path) {
9432 Ok(names) => node.path_params = names,
9433 Err(dup) => {
9434 let cur = self.current().clone();
9435 return Err(ParseError {
9436 message: format!(
9437 "axonendpoint '{}' declares path '{}' \
9438 containing duplicate placeholder '{{{}}}'. \
9439 Each `{{name}}` in a `path:` must be \
9440 unique — the runtime cannot bind two \
9441 path segments to the same name (Fase 37.y D1).",
9442 node.name, node.path, dup,
9443 ),
9444 line: cur.line,
9445 column: cur.column,
9446 ..Default::default()
9447 });
9448 }
9449 }
9450 },
9451 "body" => node.body_type = self.consume_any_ident_or_kw()?.value.clone(),
9452 "query" => {
9453 let lbrace_tok = self.consume(TokenType::LBrace)?;
9472 let block_line = lbrace_tok.line;
9473 if !node.query_params.is_empty() {
9474 return Err(ParseError {
9475 message: format!(
9476 "axonendpoint '{}' declares `query: {{ … }}` \
9477 more than once. The query-parameter block \
9478 is unique per endpoint; combine all params \
9479 into a single block (Fase 37.y D2).",
9480 node.name,
9481 ),
9482 line: lbrace_tok.line,
9483 column: lbrace_tok.column,
9484 ..Default::default()
9485 });
9486 }
9487 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9488 let name_tok = self.consume(TokenType::Identifier)?;
9489 let field_name = name_tok.value.clone();
9490 if node
9492 .query_params
9493 .iter()
9494 .any(|f| f.name == field_name)
9495 {
9496 return Err(ParseError {
9497 message: format!(
9498 "axonendpoint '{}' declares duplicate \
9499 query param '{}' inside `query: {{ … }}`. \
9500 Each name must appear at most once \
9501 (Fase 37.y D2).",
9502 node.name, field_name,
9503 ),
9504 line: name_tok.line,
9505 column: name_tok.column,
9506 ..Default::default()
9507 });
9508 }
9509 self.consume(TokenType::Colon)?;
9510 let type_expr = self.parse_type_expr()?;
9511 if !type_expr.generic_param.is_empty() {
9523 let canonical_hint = if type_expr.name == "Optional" {
9524 format!(
9525 " Use `{}?` (the `?` suffix) for an \
9526 optional query param instead of \
9527 `Optional<{}>`.",
9528 type_expr.generic_param,
9529 type_expr.generic_param,
9530 )
9531 } else if type_expr.name == "List" {
9532 " Multi-value query params (e.g. `?tag=a&tag=b`) \
9533 are honest-deferred from v1.38.5; bind a \
9534 single-value `Text` query param and parse \
9535 the value inside the flow."
9536 .to_string()
9537 } else {
9538 String::new()
9539 };
9540 return Err(ParseError {
9541 message: format!(
9542 "axonendpoint '{}' query param '{}' uses \
9543 a generic type `{}<{}>`. Query params \
9544 take a primitive type from the closed \
9545 catalog ({}); the `?` suffix marks \
9546 optional.{} (Fase 37.y D2).",
9547 node.name,
9548 field_name,
9549 type_expr.name,
9550 type_expr.generic_param,
9551 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | "),
9552 canonical_hint,
9553 ),
9554 line: type_expr.loc.line,
9555 column: type_expr.loc.column,
9556 ..Default::default()
9557 });
9558 }
9559 if !axonendpoint_is_valid_query_param_type(&type_expr.name) {
9563 let hint = crate::smart_suggest::suggest_for(
9570 &type_expr.name,
9571 AXONENDPOINT_QUERY_PARAM_TYPES,
9572 );
9573 let hint_text = if hint.is_empty() {
9574 format!(
9575 " Expected one of: {}.",
9576 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
9577 )
9578 } else {
9579 format!(
9580 " {} Expected one of: {}.",
9581 hint,
9582 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
9583 )
9584 };
9585 return Err(ParseError {
9586 message: format!(
9587 "axonendpoint '{}' query param '{}' has \
9588 unsupported type '{}'.{} (Fase 37.y D2).",
9589 node.name, field_name, type_expr.name,
9590 hint_text,
9591 ),
9592 line: type_expr.loc.line,
9593 column: type_expr.loc.column,
9594 ..Default::default()
9595 });
9596 }
9597 node.query_params.push(TypeField {
9598 name: field_name,
9599 type_expr,
9600 loc: Loc {
9601 line: name_tok.line,
9602 column: name_tok.column,
9603 },
9604 });
9605 if self.check(TokenType::Comma) {
9611 self.advance();
9612 }
9613 let _ = block_line; }
9615 self.consume(TokenType::RBrace)?;
9616 },
9617 "execute" => node.execute_flow = self.consume_any_ident_or_kw()?.value.clone(),
9618 "output" => {
9619 node.output_type = self.parse_output_type_string()?;
9637 }
9638 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
9639 "cors" => node.cors_ref = self.consume_any_ident_or_kw()?.value.clone(),
9641 "retries" => node.retries = self.parse_optional_int(),
9642 "timeout" => {
9643 let t = self.current().clone();
9644 self.advance();
9645 node.timeout = t.value.clone();
9646 }
9647 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
9648 "replay" => {
9649 let value_tok = self.consume(TokenType::Bool)?;
9656 node.replay = value_tok.value.eq_ignore_ascii_case("true");
9657 node.replay_explicit = true;
9658 }
9659 "public" => {
9665 let value_tok = self.consume(TokenType::Bool)?;
9666 node.public = value_tok.value.eq_ignore_ascii_case("true");
9667 }
9668 "requires" => {
9669 let bracket_tok = self.current().clone();
9676 let items = self.parse_bracketed_dot_identifiers()?;
9677 for slug in &items {
9678 if !is_valid_capability_slug(slug) {
9679 return Err(ParseError {
9680 message: format!(
9681 "Invalid capability slug '{slug}' in axonendpoint '{}' \
9682 `requires:`. Capability slugs must match \
9683 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
9684 lowercase identifiers starting with a letter. Examples: \
9685 `admin`, `legal.read`, `hipaa.phi.read`.",
9686 node.name
9687 ),
9688 line: bracket_tok.line,
9689 column: bracket_tok.column,
9690 ..Default::default()
9691 });
9692 }
9693 }
9694 node.requires_capabilities = items;
9695 }
9696 "transport" => {
9700 let value_tok = self.consume_any_ident_or_kw()?;
9701 let value = &value_tok.value;
9702 if !axonendpoint_is_valid_transport(value) {
9703 let hint = crate::smart_suggest::suggest_for(
9704 value,
9705 AXONENDPOINT_TRANSPORT_VALUES,
9706 );
9707 let base = format!(
9708 "Invalid transport '{}' in axonendpoint '{}'.",
9709 value, node.name
9710 );
9711 let message = if hint.is_empty() {
9712 format!("{base} expected json | sse | ndjson, found {value}")
9713 } else {
9714 format!(
9715 "{base} {hint} (expected json | sse | ndjson, found {value})"
9716 )
9717 };
9718 return Err(ParseError {
9719 message,
9720 line: value_tok.line,
9721 column: value_tok.column,
9722 ..Default::default()
9723 });
9724 }
9725 node.transport = value.clone();
9726 node.transport_explicit = true;
9731 if self.check(TokenType::LParen) {
9738 if value != "sse" {
9739 let tok = self.current().clone();
9740 return Err(ParseError {
9741 message: format!(
9742 "Dialect parametrization \
9743 `transport: {value}(<dialect>)` is \
9744 only valid for `sse`; got \
9745 `{value}` in axonendpoint '{}'.",
9746 node.name
9747 ),
9748 line: tok.line,
9749 column: tok.column,
9750 ..Default::default()
9751 });
9752 }
9753 self.advance(); let dialect_tok = self.consume_any_ident_or_kw()?;
9755 let dialect = dialect_tok.value.clone();
9756 if !AXONENDPOINT_TRANSPORT_DIALECTS
9757 .iter()
9758 .any(|&d| d == dialect)
9759 {
9760 let hint = crate::smart_suggest::suggest_for(
9761 &dialect,
9762 AXONENDPOINT_TRANSPORT_DIALECTS,
9763 );
9764 let base = format!(
9765 "Invalid SSE dialect '{dialect}' in axonendpoint '{}'.",
9766 node.name
9767 );
9768 let message = if hint.is_empty() {
9769 format!(
9770 "{base} expected axon | openai | kimi | glm | anthropic, found {dialect}"
9771 )
9772 } else {
9773 format!(
9774 "{base} {hint} (expected axon | openai | kimi | glm | anthropic, found {dialect})"
9775 )
9776 };
9777 return Err(ParseError {
9778 message,
9779 line: dialect_tok.line,
9780 column: dialect_tok.column,
9781 ..Default::default()
9782 });
9783 }
9784 let rparen_tok = self.current().clone();
9786 if !self.check(TokenType::RParen) {
9787 return Err(ParseError {
9788 message: format!(
9789 "Expected `)` after dialect name \
9790 in axonendpoint '{}' \
9791 (transport: sse(<dialect>) grammar).",
9792 node.name
9793 ),
9794 line: rparen_tok.line,
9795 column: rparen_tok.column,
9796 ..Default::default()
9797 });
9798 }
9799 self.advance(); node.transport_dialect = dialect;
9801 }
9802 }
9803 "keepalive" => {
9804 let value_tok = self.current().clone();
9808 self.advance();
9809 let value = &value_tok.value;
9810 if !axonendpoint_is_valid_keepalive(value) {
9811 let hint = crate::smart_suggest::suggest_for(
9812 value,
9813 AXONENDPOINT_KEEPALIVE_VALUES,
9814 );
9815 let base = format!(
9816 "Invalid keepalive '{}' in axonendpoint '{}'.",
9817 value, node.name
9818 );
9819 let message = if hint.is_empty() {
9820 format!("{base} expected 5s | 15s | 30s | 60s, found {value}")
9821 } else {
9822 format!(
9823 "{base} {hint} (expected 5s | 15s | 30s | 60s, found {value})"
9824 )
9825 };
9826 return Err(ParseError {
9827 message,
9828 line: value_tok.line,
9829 column: value_tok.column,
9830 ..Default::default()
9831 });
9832 }
9833 node.keepalive = value.clone();
9834 }
9835 "backend" => {
9836 let value_tok = self.consume_any_ident_or_kw()?;
9844 let value = &value_tok.value;
9845 if !axonendpoint_is_valid_backend(value) {
9846 let hint = crate::smart_suggest::suggest_for(
9847 value,
9848 AXONENDPOINT_BACKEND_VALUES,
9849 );
9850 let expected = AXONENDPOINT_BACKEND_VALUES.join(" | ");
9851 let base = format!(
9852 "Invalid backend '{}' in axonendpoint '{}'.",
9853 value, node.name
9854 );
9855 let message = if hint.is_empty() {
9856 format!("{base} expected {expected}, found {value}")
9857 } else {
9858 format!(
9859 "{base} {hint} (expected {expected}, found {value})"
9860 )
9861 };
9862 return Err(ParseError {
9863 message,
9864 line: value_tok.line,
9865 column: value_tok.column,
9866 ..Default::default()
9867 });
9868 }
9869 node.backend = value.clone();
9870 }
9871 _ => self.skip_value(),
9872 }
9873 } else if self.check(TokenType::LBrace) {
9874 self.skip_braced_block()?;
9875 }
9876 }
9877 self.consume(TokenType::RBrace)?;
9878 Ok(node)
9879 }
9880
9881 fn parse_optional_int(&mut self) -> Option<i64> {
9884 let tok = self.current().clone();
9885 match tok.ttype {
9886 TokenType::Integer => {
9887 self.advance();
9888 tok.value.parse::<i64>().ok()
9889 }
9890 _ => {
9891 self.advance();
9892 None
9893 }
9894 }
9895 }
9896
9897 fn parse_optional_float(&mut self) -> Option<f64> {
9898 let tok = self.current().clone();
9899 match tok.ttype {
9900 TokenType::Float | TokenType::Integer => {
9901 self.advance();
9902 tok.value.parse::<f64>().ok()
9903 }
9904 _ => {
9905 self.advance();
9906 None
9907 }
9908 }
9909 }
9910
9911 fn parse_lambda_data(&mut self) -> Result<LambdaDataDefinition, ParseError> {
9914 let tok = self.consume(TokenType::Lambda)?;
9915 let name = self.consume(TokenType::Identifier)?;
9916 self.consume(TokenType::LBrace)?;
9917
9918 let mut node = LambdaDataDefinition {
9919 name: name.value.clone(),
9920 ontology: String::new(),
9921 certainty: 1.0,
9922 temporal_frame_start: String::new(),
9923 temporal_frame_end: String::new(),
9924 provenance: String::new(),
9925 derivation: String::new(),
9926 loc: Loc {
9927 line: tok.line,
9928 column: tok.column,
9929 },
9930 leading_trivia: Vec::new(),
9931 trailing_trivia: Vec::new(),
9932 };
9933
9934 while !self.check(TokenType::RBrace) {
9935 let field = self.current().clone();
9936 match field.ttype {
9937 TokenType::Ontology => {
9938 self.advance();
9939 self.consume(TokenType::Colon)?;
9940 node.ontology = self.consume(TokenType::StringLit)?.value.clone();
9941 }
9942 TokenType::Certainty => {
9943 self.advance();
9944 self.consume(TokenType::Colon)?;
9945 let val = self.current().clone();
9946 match val.ttype {
9947 TokenType::Float => {
9948 self.advance();
9949 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
9950 }
9951 TokenType::Integer => {
9952 self.advance();
9953 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
9954 }
9955 _ => {
9956 return Err(ParseError {
9957 message: format!(
9958 "Expected number for certainty, got '{}'",
9959 val.value
9960 ),
9961 line: val.line,
9962 column: val.column,
9963 ..Default::default()
9964 });
9965 }
9966 }
9967 }
9968 TokenType::TemporalFrame => {
9969 self.advance();
9970 self.consume(TokenType::Colon)?;
9971 node.temporal_frame_start = self.consume(TokenType::StringLit)?.value.clone();
9972 if self.check(TokenType::StringLit) {
9974 node.temporal_frame_end = self.consume(TokenType::StringLit)?.value.clone();
9975 }
9976 }
9977 TokenType::Provenance => {
9978 self.advance();
9979 self.consume(TokenType::Colon)?;
9980 node.provenance = self.consume(TokenType::StringLit)?.value.clone();
9981 }
9982 TokenType::Derivation => {
9983 self.advance();
9984 self.consume(TokenType::Colon)?;
9985 let d = self.current().clone();
9986 self.advance();
9987 node.derivation = d.value.clone();
9988 }
9989 _ => {
9990 self.advance();
9992 if self.check(TokenType::Colon) {
9993 self.advance();
9994 self.skip_value();
9995 }
9996 }
9997 }
9998 }
9999
10000 self.consume(TokenType::RBrace)?;
10001 Ok(node)
10002 }
10003
10004 fn parse_lambda_data_apply(&mut self) -> Result<LambdaDataApplyNode, ParseError> {
10005 let tok = self.consume(TokenType::Lambda)?;
10006 let lambda_name = self.consume(TokenType::Identifier)?;
10007
10008 let on_tok = self.current().clone();
10010 self.advance();
10011 if on_tok.value != "on" {
10012 return Err(ParseError {
10013 message: format!(
10014 "Expected 'on' after lambda data name in flow step, got '{}'",
10015 on_tok.value
10016 ),
10017 line: on_tok.line,
10018 column: on_tok.column,
10019 ..Default::default()
10020 });
10021 }
10022
10023 let target = self.current().clone();
10024 self.advance();
10025
10026 let mut output_type = String::new();
10027 if self.check(TokenType::Arrow) {
10028 self.advance();
10029 output_type = self.consume(TokenType::Identifier)?.value.clone();
10030 }
10031
10032 Ok(LambdaDataApplyNode {
10033 lambda_data_name: lambda_name.value.clone(),
10034 target: target.value.clone(),
10035 output_type,
10036 loc: Loc {
10037 line: tok.line,
10038 column: tok.column,
10039 },
10040 })
10041 }
10042
10043 fn parse_generic_declaration(&mut self) -> Result<Declaration, ParseError> {
10046 let kw_tok = self.current().clone();
10047 self.advance(); let name = if self.current().ttype == TokenType::Identifier {
10051 let n = self.current().value.clone();
10052 self.advance();
10053 n
10054 } else if !self.check(TokenType::LBrace)
10055 && !self.check(TokenType::LParen)
10056 && !self.check(TokenType::Eof)
10057 && self
10058 .current()
10059 .value
10060 .chars()
10061 .all(|c| c.is_alphanumeric() || c == '_')
10062 {
10063 let n = self.current().value.clone();
10064 self.advance();
10065 n
10066 } else {
10067 String::new()
10068 };
10069
10070 if self.check(TokenType::LParen) {
10072 self.advance();
10073 let mut depth = 1u32;
10074 while depth > 0 && !self.check(TokenType::Eof) {
10075 if self.check(TokenType::LParen) {
10076 depth += 1;
10077 } else if self.check(TokenType::RParen) {
10078 depth -= 1;
10079 }
10080 self.advance();
10081 }
10082 }
10083
10084 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
10086 if self.check(TokenType::Eof) {
10087 break;
10088 }
10089 self.advance();
10090 }
10091
10092 if self.check(TokenType::LBrace) {
10094 self.skip_braced_block()?;
10095 }
10096
10097 Ok(Declaration::Generic(GenericDeclaration {
10098 keyword: kw_tok.value,
10099 name,
10100 loc: Loc {
10101 line: kw_tok.line,
10102 column: kw_tok.column,
10103 },
10104 leading_trivia: Vec::new(),
10105 trailing_trivia: Vec::new(),
10106 }))
10107 }
10108
10109 fn parse_channel(&mut self) -> Result<ChannelDefinition, ParseError> {
10117 let tok = self.consume(TokenType::Channel)?;
10118 let name = self.consume(TokenType::Identifier)?.value;
10119 let mut node = ChannelDefinition {
10120 name: name.clone(),
10121 message: String::new(),
10122 qos: "at_least_once".to_string(),
10123 lifetime: "affine".to_string(),
10124 persistence: "ephemeral".to_string(),
10125 shield_ref: String::new(),
10126 loc: Loc {
10127 line: tok.line,
10128 column: tok.column,
10129 },
10130 leading_trivia: Vec::new(),
10131 trailing_trivia: Vec::new(),
10132 };
10133 self.consume(TokenType::LBrace)?;
10134 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10135 let field_tok = self.current().clone();
10136 let field_name = field_tok.value.clone();
10137 self.advance();
10138 if !self.check(TokenType::Colon) {
10139 if self.check(TokenType::LBrace) {
10140 self.skip_braced_block()?;
10141 }
10142 continue;
10143 }
10144 self.advance();
10145 match field_name.as_str() {
10146 "message" => node.message = self.parse_channel_message_type()?,
10147 "qos" => {
10148 let q_tok = self.consume_any_ident_or_kw()?;
10149 if !matches!(
10150 q_tok.value.as_str(),
10151 "at_most_once" | "at_least_once" | "exactly_once" | "broadcast" | "queue"
10152 ) {
10153 return Err(ParseError {
10154 message: format!(
10155 "Invalid qos '{}' in channel '{}' — \
10156 expected at_most_once | at_least_once | \
10157 exactly_once | broadcast | queue",
10158 q_tok.value, name
10159 ),
10160 line: q_tok.line,
10161 column: q_tok.column,
10162 ..Default::default()
10163 });
10164 }
10165 node.qos = q_tok.value;
10166 }
10167 "lifetime" => {
10168 let lt_tok = self.consume_any_ident_or_kw()?;
10169 if !matches!(lt_tok.value.as_str(), "linear" | "affine" | "persistent") {
10170 return Err(ParseError {
10171 message: format!(
10172 "Invalid lifetime '{}' in channel '{}' — \
10173 expected linear | affine | persistent",
10174 lt_tok.value, name
10175 ),
10176 line: lt_tok.line,
10177 column: lt_tok.column,
10178 ..Default::default()
10179 });
10180 }
10181 node.lifetime = lt_tok.value;
10182 }
10183 "persistence" => {
10184 let p_tok = self.consume_any_ident_or_kw()?;
10185 if !matches!(p_tok.value.as_str(), "ephemeral" | "persistent_axonstore") {
10186 return Err(ParseError {
10187 message: format!(
10188 "Invalid persistence '{}' in channel '{}' — \
10189 expected ephemeral | persistent_axonstore",
10190 p_tok.value, name
10191 ),
10192 line: p_tok.line,
10193 column: p_tok.column,
10194 ..Default::default()
10195 });
10196 }
10197 node.persistence = p_tok.value;
10198 }
10199 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
10200 _ => self.skip_value(),
10201 }
10202 }
10203 self.consume(TokenType::RBrace)?;
10204 Ok(node)
10205 }
10206
10207 fn parse_channel_message_type(&mut self) -> Result<String, ParseError> {
10210 let head = self.consume(TokenType::Identifier)?;
10211 let mut spelling = head.value;
10212 if self.check(TokenType::Lt) {
10213 self.advance();
10214 let inner = self.parse_channel_message_type()?;
10215 self.consume(TokenType::Gt)?;
10216 spelling = format!("{}<{}>", spelling, inner);
10217 }
10218 Ok(spelling)
10219 }
10220
10221 fn parse_emit_step(&mut self) -> Result<FlowStep, ParseError> {
10227 let tok = self.consume(TokenType::Emit)?;
10228 let channel = self.consume(TokenType::Identifier)?.value;
10229 self.consume(TokenType::LParen)?;
10230 let value = self.parse_emit_value_ref()?;
10231 self.consume(TokenType::RParen)?;
10232 Ok(FlowStep::Emit(EmitStatement {
10233 channel_ref: channel,
10234 value_ref: value,
10235 loc: Loc {
10236 line: tok.line,
10237 column: tok.column,
10238 },
10239 }))
10240 }
10241
10242 fn parse_mint_step(&mut self) -> Result<FlowStep, ParseError> {
10248 let tok = self.consume(TokenType::Mint)?;
10249 let credential_ref = self.consume(TokenType::Identifier)?.value;
10250 self.consume(TokenType::As)?;
10251 let binding = self.consume(TokenType::Identifier)?.value;
10252 Ok(FlowStep::Mint(MintStep {
10253 credential_ref,
10254 binding,
10255 loc: Loc {
10256 line: tok.line,
10257 column: tok.column,
10258 },
10259 }))
10260 }
10261
10262 fn parse_rotate_step(&mut self) -> Result<FlowStep, ParseError> {
10277 let tok = self.consume(TokenType::Rotate)?;
10278 let store_ref = self.consume(TokenType::Identifier)?.value;
10279 let mut where_expr = String::new();
10280 if self.check(TokenType::Where) {
10281 self.advance();
10282 where_expr = self.consume(TokenType::StringLit)?.value.clone();
10283 }
10284 let with_tok = self.current().clone();
10285 if with_tok.value != "with" {
10286 return Err(ParseError {
10287 message: format!(
10288 "Expected `with <Tool>` after `rotate {store_ref}{}`, found '{}'. \
10289 A rotation names the tool that performs the renewal exchange: \
10290 `rotate {store_ref} [where \"<filter>\"] with <Tool> as <binding>`.",
10291 if where_expr.is_empty() { "" } else { " where …" },
10292 with_tok.value
10293 ),
10294 line: with_tok.line,
10295 column: with_tok.column,
10296 ..Default::default()
10297 });
10298 }
10299 self.advance();
10300 let tool_ref = self.consume(TokenType::Identifier)?.value;
10301 self.consume(TokenType::As)?;
10302 let binding = self.consume(TokenType::Identifier)?.value;
10303 Ok(FlowStep::Rotate(RotateStep {
10304 store_ref,
10305 where_expr,
10306 tool_ref,
10307 binding,
10308 loc: Loc {
10309 line: tok.line,
10310 column: tok.column,
10311 },
10312 }))
10313 }
10314
10315 fn parse_emit_value_ref(&mut self) -> Result<String, ParseError> {
10331 let head = self.consume(TokenType::Identifier)?.value;
10332 let mut parts = vec![head];
10333 while self.check(TokenType::Dot) {
10334 self.advance(); let next_tok = self.current().clone();
10336 let valid = !next_tok.value.is_empty()
10337 && next_tok.value.as_bytes()[0].is_ascii_alphabetic()
10338 || next_tok.value.starts_with('_');
10339 if !valid {
10340 return Err(ParseError {
10341 message: format!(
10342 "Expected identifier or keyword after '.' in dotted \
10343 access, found {:?}",
10344 next_tok.value
10345 ),
10346 line: next_tok.line,
10347 column: next_tok.column,
10348 ..Default::default()
10349 });
10350 }
10351 self.advance();
10352 parts.push(next_tok.value);
10353 }
10354 Ok(parts.join("."))
10355 }
10356
10357 fn parse_publish_step(&mut self) -> Result<FlowStep, ParseError> {
10359 let tok = self.consume(TokenType::Publish)?;
10360 let channel = self.consume(TokenType::Identifier)?.value;
10361 self.consume(TokenType::Within)?;
10362 let shield = self.consume(TokenType::Identifier)?.value;
10363 Ok(FlowStep::Publish(PublishStatement {
10364 channel_ref: channel,
10365 shield_ref: shield,
10366 loc: Loc {
10367 line: tok.line,
10368 column: tok.column,
10369 },
10370 }))
10371 }
10372
10373 fn parse_discover_step(&mut self) -> Result<FlowStep, ParseError> {
10375 let tok = self.consume(TokenType::Discover)?;
10376 let cap = self.consume(TokenType::Identifier)?.value;
10377 self.consume(TokenType::As)?;
10378 let alias = self.consume(TokenType::Identifier)?.value;
10379 Ok(FlowStep::Discover(DiscoverStatement {
10380 capability_ref: cap,
10381 alias,
10382 loc: Loc {
10383 line: tok.line,
10384 column: tok.column,
10385 },
10386 }))
10387 }
10388}
10389
10390#[cfg(test)]
10393mod fase13_parser_tests {
10394 use super::*;
10395 use crate::lexer::Lexer;
10396
10397 fn parse(src: &str) -> Result<Program, ParseError> {
10398 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
10399 Parser::new(tokens).parse()
10400 }
10401
10402 #[test]
10403 fn channel_full_parses() {
10404 let src = r#"channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }"#;
10405 let prog = parse(src).expect("parse");
10406 match &prog.declarations[0] {
10407 Declaration::Channel(c) => {
10408 assert_eq!(c.name, "C");
10409 assert_eq!(c.message, "Order");
10410 assert_eq!(c.qos, "at_least_once");
10411 assert_eq!(c.lifetime, "affine");
10412 assert_eq!(c.persistence, "ephemeral");
10413 assert_eq!(c.shield_ref, "Gate");
10414 }
10415 _ => panic!("expected ChannelDefinition"),
10416 }
10417 }
10418
10419 #[test]
10420 fn channel_defaults_match_paper_d1() {
10421 let prog = parse("channel C { message: Order }").expect("parse");
10422 if let Declaration::Channel(c) = &prog.declarations[0] {
10423 assert_eq!(c.qos, "at_least_once"); assert_eq!(c.lifetime, "affine"); assert_eq!(c.persistence, "ephemeral");
10426 assert_eq!(c.shield_ref, "");
10427 } else {
10428 panic!("expected ChannelDefinition");
10429 }
10430 }
10431
10432 #[test]
10433 fn channel_second_order_message_type_parses() {
10434 let prog = parse("channel C { message: Channel<Order> }").expect("parse");
10435 if let Declaration::Channel(c) = &prog.declarations[0] {
10436 assert_eq!(c.message, "Channel<Order>");
10437 } else {
10438 panic!("expected ChannelDefinition");
10439 }
10440 }
10441
10442 #[test]
10443 fn channel_nested_channel_message_type_parses() {
10444 let prog = parse("channel C { message: Channel<Channel<Order>> }").expect("parse");
10445 if let Declaration::Channel(c) = &prog.declarations[0] {
10446 assert_eq!(c.message, "Channel<Channel<Order>>");
10447 } else {
10448 panic!("expected ChannelDefinition");
10449 }
10450 }
10451
10452 #[test]
10453 fn channel_invalid_qos_rejected() {
10454 let err = parse("channel C { message: T qos: bogus }").unwrap_err();
10455 assert!(err.message.contains("Invalid qos"), "got {}", err.message);
10456 }
10457
10458 #[test]
10459 fn channel_invalid_lifetime_rejected() {
10460 let err = parse("channel C { message: T lifetime: eternal }").unwrap_err();
10461 assert!(
10462 err.message.contains("Invalid lifetime"),
10463 "got {}",
10464 err.message
10465 );
10466 }
10467
10468 #[test]
10469 fn channel_invalid_persistence_rejected() {
10470 let err = parse("channel C { message: T persistence: forever }").unwrap_err();
10471 assert!(
10472 err.message.contains("Invalid persistence"),
10473 "got {}",
10474 err.message
10475 );
10476 }
10477
10478 #[test]
10479 fn emit_value_parses() {
10480 let src = "flow f() -> Out { emit C(payload) }";
10481 let prog = parse(src).expect("parse");
10482 if let Declaration::Flow(f) = &prog.declarations[0] {
10483 match &f.body[0] {
10484 FlowStep::Emit(e) => {
10485 assert_eq!(e.channel_ref, "C");
10486 assert_eq!(e.value_ref, "payload");
10487 }
10488 other => panic!("expected Emit, got {:?}", other),
10489 }
10490 } else {
10491 panic!("expected Flow");
10492 }
10493 }
10494
10495 #[test]
10496 fn publish_within_shield_parses() {
10497 let src = "flow f() -> Cap { publish C within Gate }";
10498 let prog = parse(src).expect("parse");
10499 if let Declaration::Flow(f) = &prog.declarations[0] {
10500 match &f.body[0] {
10501 FlowStep::Publish(p) => {
10502 assert_eq!(p.channel_ref, "C");
10503 assert_eq!(p.shield_ref, "Gate");
10504 }
10505 other => panic!("expected Publish, got {:?}", other),
10506 }
10507 } else {
10508 panic!("expected Flow");
10509 }
10510 }
10511
10512 #[test]
10513 fn discover_with_alias_parses() {
10514 let src = "flow f() -> Out { discover C as ch }";
10515 let prog = parse(src).expect("parse");
10516 if let Declaration::Flow(f) = &prog.declarations[0] {
10517 match &f.body[0] {
10518 FlowStep::Discover(d) => {
10519 assert_eq!(d.capability_ref, "C");
10520 assert_eq!(d.alias, "ch");
10521 }
10522 other => panic!("expected Discover, got {:?}", other),
10523 }
10524 } else {
10525 panic!("expected Flow");
10526 }
10527 }
10528
10529 #[test]
10530 fn listen_typed_ref_sets_flag_true() {
10531 let src = "daemon D() { goal: \"x\" listen C as ev { } }";
10532 let prog = parse(src).expect("parse");
10533 if let Declaration::Daemon(d) = &prog.declarations[0] {
10534 assert_eq!(d.listeners.len(), 1);
10535 assert_eq!(d.listeners[0].channel, "C");
10536 assert!(d.listeners[0].channel_is_ref, "typed ref ⇒ true");
10537 } else {
10538 panic!("expected Daemon");
10539 }
10540 }
10541
10542 #[test]
10543 fn listen_string_topic_legacy_flag_false() {
10544 let src = "daemon D() { goal: \"x\" listen \"orders\" as ev { } }";
10545 let prog = parse(src).expect("parse");
10546 if let Declaration::Daemon(d) = &prog.declarations[0] {
10547 assert_eq!(d.listeners.len(), 1);
10548 assert_eq!(d.listeners[0].channel, "orders");
10549 assert!(!d.listeners[0].channel_is_ref, "string topic ⇒ false");
10550 } else {
10551 panic!("expected Daemon");
10552 }
10553 }
10554
10555 fn extract_first_emit(prog: &Program) -> &EmitStatement {
10558 if let Declaration::Flow(f) = &prog.declarations[0] {
10559 if let FlowStep::Emit(e) = &f.body[0] {
10560 return e;
10561 }
10562 }
10563 panic!("expected emit statement at flow body[0]");
10564 }
10565
10566 #[test]
10567 fn emit_accepts_bare_identifier_value_ref() {
10568 let prog = parse("flow f() -> Out { emit Hello(payload) }").expect("parse");
10570 let emit = extract_first_emit(&prog);
10571 assert_eq!(emit.channel_ref, "Hello");
10572 assert_eq!(emit.value_ref, "payload");
10573 }
10574
10575 #[test]
10576 fn emit_accepts_two_segment_dotted_value_ref() {
10577 let prog = parse("flow f() -> Out { emit Hello(Build.output) }").expect("parse");
10579 let emit = extract_first_emit(&prog);
10580 assert_eq!(emit.value_ref, "Build.output");
10581 }
10582
10583 #[test]
10584 fn emit_accepts_three_segment_nested_dotted_value_ref() {
10585 let prog = parse("flow f() -> Out { emit Score(Analyze.result.score) }").expect("parse");
10586 let emit = extract_first_emit(&prog);
10587 assert_eq!(emit.value_ref, "Analyze.result.score");
10588 }
10589
10590 #[test]
10591 fn emit_dotted_with_trailing_dot_fails() {
10592 let result = parse("flow f() -> Out { emit Hello(Build.) }");
10594 assert!(result.is_err(), "expected parse error for trailing dot");
10595 }
10596}
10597
10598#[cfg(test)]
10601mod fase14a_declaration_trivia_tests {
10602 use super::*;
10603 use crate::lexer::Lexer;
10604 use crate::tokens::TriviaKind;
10605
10606 fn parse(src: &str) -> Program {
10607 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10608 Parser::new(toks).parse().expect("parse")
10609 }
10610
10611 #[test]
10612 fn no_comments_means_empty_trivia_per_decl() {
10613 let prog = parse("flow F() -> Out { }");
10614 assert_eq!(prog.declarations.len(), 1);
10615 assert_eq!(prog.declaration_trivia.len(), 1);
10616 assert!(prog.declaration_trivia[0].leading.is_empty());
10617 assert!(prog.declaration_trivia[0].trailing.is_empty());
10618 }
10619
10620 #[test]
10621 fn doc_line_comment_attaches_as_leading() {
10622 let prog = parse("/// Documents F\nflow F() -> Out { }");
10623 let triv = &prog.declaration_trivia[0];
10624 assert_eq!(triv.leading.len(), 1);
10625 assert_eq!(triv.leading[0].kind, TriviaKind::DocLine);
10626 assert!(triv.leading[0].is_doc());
10627 assert_eq!(triv.leading[0].text, "/// Documents F");
10628 }
10629
10630 #[test]
10631 fn regular_line_comment_attaches_as_leading() {
10632 let prog = parse("// header\nflow F() -> Out { }");
10633 let triv = &prog.declaration_trivia[0];
10634 assert_eq!(triv.leading.len(), 1);
10635 assert_eq!(triv.leading[0].kind, TriviaKind::Line);
10636 assert!(!triv.leading[0].is_doc());
10637 }
10638
10639 #[test]
10640 fn block_doc_comment_attaches_as_leading() {
10641 let prog = parse("/** Doc block */\nflow F() -> Out { }");
10642 let triv = &prog.declaration_trivia[0];
10643 assert_eq!(triv.leading[0].kind, TriviaKind::DocBlock);
10644 assert!(triv.leading[0].is_doc());
10645 }
10646
10647 #[test]
10648 fn multiple_comments_collected_in_source_order() {
10649 let src = "/// First\n/// Second\nflow F() -> Out { }";
10650 let prog = parse(src);
10651 let triv = &prog.declaration_trivia[0];
10652 assert_eq!(triv.leading.len(), 2);
10653 assert_eq!(triv.leading[0].text, "/// First");
10654 assert_eq!(triv.leading[1].text, "/// Second");
10655 }
10656
10657 #[test]
10658 fn three_decls_each_get_own_leading() {
10659 let src = "/// for A\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }\n/// for C\nflow C() -> Out { }";
10660 let prog = parse(src);
10661 assert_eq!(prog.declarations.len(), 3);
10662 assert_eq!(prog.declaration_trivia.len(), 3);
10663 for (idx, name) in ["A", "B", "C"].iter().enumerate() {
10664 let triv = &prog.declaration_trivia[idx];
10665 assert_eq!(triv.leading.len(), 1);
10666 assert_eq!(triv.leading[0].text, format!("/// for {name}"));
10667 }
10668 }
10669
10670 #[test]
10671 fn trailing_comment_attaches_to_last_token_of_decl() {
10672 let prog = parse("flow F() -> Out { } // tail");
10674 let triv = &prog.declaration_trivia[0];
10675 assert_eq!(triv.trailing.len(), 1);
10676 assert_eq!(triv.trailing[0].text, "// tail");
10677 }
10678
10679 #[test]
10680 fn mixed_doc_and_regular_preserve_order_between_decls() {
10681 let src = "/// doc for A\nflow A() -> Out { }\n\n// header line\n/// doc for B\nflow B() -> Out { }";
10682 let prog = parse(src);
10683 assert_eq!(prog.declarations.len(), 2);
10684 assert_eq!(prog.declaration_trivia[0].leading.len(), 1);
10686 assert_eq!(prog.declaration_trivia[1].leading.len(), 2);
10688 assert_eq!(prog.declaration_trivia[1].leading[0].text, "// header line");
10689 assert_eq!(prog.declaration_trivia[1].leading[1].text, "/// doc for B");
10690 }
10691
10692 #[test]
10693 fn parser_unaffected_by_comments_in_grammar_path() {
10694 let src =
10699 "// before flow\nflow /* between flow and name */ F() -> Out {\n // body comment\n}";
10700 let prog = parse(src);
10701 assert_eq!(prog.declarations.len(), 1);
10702 if let Declaration::Flow(f) = &prog.declarations[0] {
10703 assert_eq!(f.name, "F");
10704 } else {
10705 panic!("expected Flow declaration");
10706 }
10707 }
10708}
10709
10710#[cfg(test)]
10719mod fase14b_per_struct_trivia_tests {
10720 use super::*;
10721 use crate::lexer::Lexer;
10722 use crate::tokens::TriviaKind;
10723
10724 fn parse(src: &str) -> Program {
10725 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10726 Parser::new(toks).parse().expect("parse")
10727 }
10728
10729 #[test]
10730 fn flow_definition_carries_leading_trivia_directly() {
10731 let prog = parse("/// documents F\nflow F() -> Out { }");
10732 if let Declaration::Flow(f) = &prog.declarations[0] {
10733 assert_eq!(f.leading_trivia.len(), 1);
10734 assert_eq!(f.leading_trivia[0].kind, TriviaKind::DocLine);
10735 assert_eq!(f.leading_trivia[0].text, "/// documents F");
10736 assert!(f.trailing_trivia.is_empty());
10737 } else {
10738 panic!("expected Flow declaration");
10739 }
10740 }
10741
10742 #[test]
10743 fn flow_definition_carries_trailing_trivia_directly() {
10744 let prog = parse("flow F() -> Out { } // tail comment");
10745 if let Declaration::Flow(f) = &prog.declarations[0] {
10746 assert_eq!(f.trailing_trivia.len(), 1);
10747 assert_eq!(f.trailing_trivia[0].text, "// tail comment");
10748 } else {
10749 panic!("expected Flow declaration");
10750 }
10751 }
10752
10753 #[test]
10754 fn channel_definition_carries_trivia_directly() {
10755 let src = concat!(
10758 "/// inbound order events\n",
10759 "channel Orders {\n",
10760 " message: Order\n",
10761 " qos: at_least_once\n",
10762 " lifetime: affine\n",
10763 " persistence: ephemeral\n",
10764 " shield: Broker\n",
10765 "}",
10766 );
10767 let prog = parse(src);
10768 if let Declaration::Channel(ch) = &prog.declarations[0] {
10769 assert_eq!(ch.leading_trivia.len(), 1);
10770 assert!(ch.leading_trivia[0].is_doc());
10771 assert_eq!(ch.leading_trivia[0].text, "/// inbound order events");
10772 } else {
10773 panic!("expected Channel declaration");
10774 }
10775 }
10776
10777 #[test]
10778 fn per_struct_fields_match_side_channel() {
10779 let src = "/// for A\n// header for B\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }";
10782 let prog = parse(src);
10783 for (idx, decl) in prog.declarations.iter().enumerate() {
10784 let side = &prog.declaration_trivia[idx];
10785 let (per_lead, per_trail) = match decl {
10786 Declaration::Flow(f) => (&f.leading_trivia, &f.trailing_trivia),
10787 _ => panic!("unexpected variant"),
10788 };
10789 assert_eq!(per_lead.len(), side.leading.len());
10790 assert_eq!(per_trail.len(), side.trailing.len());
10791 for (a, b) in per_lead.iter().zip(side.leading.iter()) {
10792 assert_eq!(a.text, b.text);
10793 assert_eq!(a.kind, b.kind);
10794 }
10795 }
10796 }
10797
10798 #[test]
10799 fn comment_free_program_yields_empty_per_struct_fields() {
10800 let prog = parse("flow F() -> Out { }");
10801 if let Declaration::Flow(f) = &prog.declarations[0] {
10802 assert!(f.leading_trivia.is_empty());
10803 assert!(f.trailing_trivia.is_empty());
10804 } else {
10805 panic!("expected Flow declaration");
10806 }
10807 }
10808}
10809
10810#[cfg(test)]
10819mod fase14c_inner_doc_tests {
10820 use super::*;
10821 use crate::lexer::Lexer;
10822 use crate::tokens::TriviaKind;
10823
10824 fn parse(src: &str) -> Program {
10825 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10826 Parser::new(toks).parse().expect("parse")
10827 }
10828
10829 #[test]
10830 fn inner_doc_line_reaches_leading_trivia() {
10831 let src = "//! file-level docs\nflow F() -> Out { }";
10832 let prog = parse(src);
10833 let triv = &prog.declaration_trivia[0];
10834 assert_eq!(triv.leading.len(), 1);
10835 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocLine);
10836 assert!(triv.leading[0].is_doc());
10837 assert!(triv.leading[0].is_inner_doc());
10838 assert_eq!(triv.leading[0].text, "//! file-level docs");
10839 assert_eq!(triv.leading[0].stripped_text(), " file-level docs");
10840 }
10841
10842 #[test]
10843 fn inner_doc_block_reaches_leading_trivia() {
10844 let src = "/*! module-level docs */\nflow F() -> Out { }";
10845 let prog = parse(src);
10846 let triv = &prog.declaration_trivia[0];
10847 assert_eq!(triv.leading.len(), 1);
10848 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocBlock);
10849 assert!(triv.leading[0].is_inner_doc());
10850 assert_eq!(triv.leading[0].stripped_text(), " module-level docs ");
10851 }
10852
10853 #[test]
10854 fn outer_and_inner_doc_can_coexist() {
10855 let src = "//! file docs\n/// docs F\nflow F() -> Out { }";
10859 let prog = parse(src);
10860 let triv = &prog.declaration_trivia[0];
10861 assert_eq!(triv.leading.len(), 2);
10862 assert!(triv.leading[0].is_inner_doc());
10863 assert!(triv.leading[1].is_doc());
10864 assert!(!triv.leading[1].is_inner_doc());
10865 }
10866
10867 #[test]
10868 fn inner_doc_reaches_per_struct_fields() {
10869 let src = "//! intro\nflow F() -> Out { }";
10871 let prog = parse(src);
10872 if let Declaration::Flow(f) = &prog.declarations[0] {
10873 assert_eq!(f.leading_trivia.len(), 1);
10874 assert!(f.leading_trivia[0].is_inner_doc());
10875 } else {
10876 panic!("expected Flow declaration");
10877 }
10878 }
10879}
10880
10881#[cfg(test)]
10899mod fase28_recovery_tests {
10900 use super::*;
10901 use crate::lexer::Lexer;
10902
10903 fn lex(src: &str) -> Vec<Token> {
10906 Lexer::new(src, "<test>").tokenize().expect("lex")
10907 }
10908
10909 fn recover(src: &str) -> ParseResult {
10912 Parser::new(lex(src)).parse_with_recovery()
10913 }
10914
10915 fn strict(src: &str) -> Result<Program, ParseError> {
10917 Parser::new(lex(src)).parse()
10918 }
10919
10920 #[test]
10923 fn strict_parse_unchanged_for_clean_source() {
10924 let src = "intent I {}";
10927 let prog = strict(src).expect("clean parse");
10928 assert_eq!(prog.declarations.len(), 1);
10929 }
10930
10931 #[test]
10932 fn strict_parse_still_raises_on_first_error() {
10933 let src = "flow F() { } not_a_keyword flow G() { }";
10938 let _ = strict(src).expect_err("must error fast in strict mode");
10939 }
10940
10941 #[test]
10942 fn recovery_clean_source_yields_no_errors() {
10943 let src = "flow F() { } flow G() { }";
10944 let pr = recover(src);
10945 assert!(pr.is_clean(), "errors: {:?}", pr.errors);
10946 assert_eq!(pr.program.declarations.len(), 2);
10947 }
10948
10949 #[test]
10952 fn single_unknown_top_level_token_recovers() {
10953 let src = "garbage_token flow F() { } flow G() { }";
10955 let pr = recover(src);
10956 assert_eq!(pr.errors.len(), 1, "errors: {:?}", pr.errors);
10957 assert_eq!(pr.program.declarations.len(), 2);
10958 }
10959
10960 #[test]
10961 fn error_in_first_decl_does_not_block_second() {
10962 let src = "flow F() { not_a_step nope } flow G() { }";
10965 let pr = recover(src);
10966 assert!(pr.has_errors(), "expected at least one error");
10967 let names: Vec<&str> = pr
10969 .program
10970 .declarations
10971 .iter()
10972 .filter_map(|d| match d {
10973 Declaration::Flow(f) => Some(f.name.as_str()),
10974 _ => None,
10975 })
10976 .collect();
10977 assert!(names.contains(&"G"), "G not found among {names:?}");
10978 }
10979
10980 #[test]
10981 fn malformed_declaration_then_clean_intent_recovers() {
10982 let src = "flow @ () { } intent I {}";
10983 let pr = recover(src);
10984 assert!(pr.has_errors());
10985 let kinds: Vec<&str> = pr
10986 .program
10987 .declarations
10988 .iter()
10989 .map(|d| match d {
10990 Declaration::Intent(_) => "intent",
10991 Declaration::Flow(_) => "flow",
10992 _ => "other",
10993 })
10994 .collect();
10995 assert!(kinds.contains(&"intent"), "kinds: {kinds:?}");
10996 }
10997
10998 #[test]
10999 fn recovery_does_not_double_count_a_single_error() {
11000 let src = "flow F() { not_a_step }";
11008 let pr = recover(src);
11009 let outer_ghosts = pr
11010 .errors
11011 .iter()
11012 .filter(|e| e.message.contains("at top level"))
11013 .count();
11014 assert_eq!(outer_ghosts, 0, "ghost errors: {:?}", pr.errors);
11015 }
11016
11017 #[test]
11020 fn three_independent_errors_yield_three_entries() {
11021 let src =
11022 "garbage1 flow F() { } garbage2 flow G() { } garbage3 flow H() { }";
11023 let pr = recover(src);
11024 assert_eq!(pr.errors.len(), 3, "errors: {:?}", pr.errors);
11025 assert_eq!(pr.program.declarations.len(), 3);
11026 }
11027
11028 #[test]
11029 fn all_errors_no_valid_declarations() {
11030 let src = "foo bar baz qux";
11031 let pr = recover(src);
11032 assert!(pr.has_errors());
11033 assert!(pr.program.declarations.is_empty());
11034 }
11035
11036 #[test]
11037 fn errors_recorded_in_source_order() {
11038 let src = "x flow A() { } y flow B() { } z flow C() { }";
11039 let pr = recover(src);
11040 assert_eq!(pr.errors.len(), 3);
11041 let lines: Vec<u32> = pr.errors.iter().map(|e| e.line).collect();
11042 assert!(
11045 lines.windows(2).all(|w| w[0] <= w[1]),
11046 "errors out of order: {lines:?}"
11047 );
11048 }
11049
11050 #[test]
11053 fn sync_to_flow_keyword() {
11054 let src = "garbage flow F() { }";
11055 let pr = recover(src);
11056 assert_eq!(pr.program.declarations.len(), 1);
11057 }
11058
11059 #[test]
11060 fn sync_to_intent_keyword() {
11061 let src = "garbage intent I {}";
11062 let pr = recover(src);
11063 assert_eq!(pr.program.declarations.len(), 1);
11064 }
11065
11066 #[test]
11067 fn sync_to_persona_keyword() {
11068 let src = "garbage persona P { name: \"P\" role: \"R\" }";
11069 let pr = recover(src);
11070 assert!(
11071 pr.program
11072 .declarations
11073 .iter()
11074 .any(|d| matches!(d, Declaration::Persona(_))),
11075 "persona not recovered: decls = {:?}",
11076 pr.program.declarations.len()
11077 );
11078 }
11079
11080 #[test]
11081 fn sync_to_run_keyword() {
11082 let src = "garbage run R { input: { user_message: \"hi\" } }";
11083 let pr = recover(src);
11084 assert!(pr.has_errors());
11086 }
11087
11088 #[test]
11091 fn parse_result_has_errors_and_is_clean_invert() {
11092 let pr_clean = recover("flow F() { }");
11093 assert!(pr_clean.is_clean());
11094 assert!(!pr_clean.has_errors());
11095
11096 let pr_err = recover("garbage");
11097 assert!(!pr_err.is_clean());
11098 assert!(pr_err.has_errors());
11099 }
11100
11101 #[test]
11102 fn parse_result_program_field_holds_partial_program() {
11103 let pr = recover("garbage flow F() { }");
11104 assert!(!pr.program.declarations.is_empty());
11105 }
11106
11107 #[test]
11108 fn parse_result_errors_carry_line_and_column() {
11109 let pr = recover("garbage");
11110 assert!(!pr.errors.is_empty());
11111 let e = &pr.errors[0];
11112 assert!(e.line >= 1);
11113 let _ = e.column;
11116 assert!(!e.message.is_empty());
11117 }
11118
11119 #[test]
11120 fn parse_result_debug_renders() {
11121 let pr = recover("flow F() { }");
11122 let s = format!("{pr:?}");
11123 assert!(s.contains("ParseResult"));
11124 }
11125
11126 #[test]
11129 fn empty_source_is_clean() {
11130 let pr = recover("");
11131 assert!(pr.is_clean());
11132 assert!(pr.program.declarations.is_empty());
11133 }
11134
11135 #[test]
11136 fn whitespace_only_source_is_clean() {
11137 let pr = recover(" \n\n\t \n");
11138 assert!(pr.is_clean());
11139 assert!(pr.program.declarations.is_empty());
11140 }
11141
11142 #[test]
11143 fn only_garbage_does_not_crash() {
11144 let pr = recover("foo bar baz { qux quux } corge { grault }");
11146 assert!(pr.has_errors());
11147 }
11148
11149 #[test]
11150 fn unbalanced_close_brace_does_not_crash() {
11151 let pr = recover("} flow F() { }");
11152 let names: Vec<&str> = pr
11154 .program
11155 .declarations
11156 .iter()
11157 .filter_map(|d| match d {
11158 Declaration::Flow(f) => Some(f.name.as_str()),
11159 _ => None,
11160 })
11161 .collect();
11162 assert!(names.contains(&"F"), "F not recovered: {names:?}");
11163 }
11164
11165 #[test]
11166 fn error_at_eof_does_not_loop() {
11167 let pr = recover("flow F() { ");
11169 let _ = pr.errors.len();
11171 }
11172
11173 #[test]
11174 fn nested_braces_inside_error_still_balance() {
11175 let src = "flow F() { not_a_step { inner } } flow G() { }";
11178 let pr = recover(src);
11179 let names: Vec<&str> = pr
11180 .program
11181 .declarations
11182 .iter()
11183 .filter_map(|d| match d {
11184 Declaration::Flow(f) => Some(f.name.as_str()),
11185 _ => None,
11186 })
11187 .collect();
11188 assert!(names.contains(&"G"), "G not recovered: {names:?}");
11189 }
11190
11191 #[derive(Clone, Copy)]
11200 struct Xorshift(u64);
11201 impl Xorshift {
11202 fn next(&mut self) -> u64 {
11203 let mut x = self.0;
11204 x ^= x << 13;
11205 x ^= x >> 7;
11206 x ^= x << 17;
11207 self.0 = x;
11208 x
11209 }
11210 fn pick<T: Copy>(&mut self, slice: &[T]) -> T {
11211 slice[(self.next() as usize) % slice.len()]
11212 }
11213 }
11214
11215 fn mutate(src: &str, rng: &mut Xorshift) -> String {
11216 let mut bytes: Vec<u8> = src.bytes().collect();
11217 if bytes.is_empty() {
11218 return src.to_string();
11219 }
11220 let op = rng.next() % 4;
11221 let pos = (rng.next() as usize) % bytes.len();
11222 let safe: &[u8] = b"abcdefghijklmnopqrstuvwxyz {}();:,_0123456789";
11226 match op {
11227 0 => {
11228 bytes.remove(pos);
11229 }
11230 1 => {
11231 let b = rng.pick(safe);
11232 bytes.insert(pos, b);
11233 }
11234 2 if pos + 1 < bytes.len() => {
11235 bytes.swap(pos, pos + 1);
11236 }
11237 _ => {
11238 let b = rng.pick(safe);
11239 bytes[pos] = b;
11240 }
11241 }
11242 bytes.retain(|b| b.is_ascii());
11245 String::from_utf8_lossy(&bytes).into_owned()
11246 }
11247
11248 #[test]
11249 fn fuzz_recovery_never_crashes() {
11250 let seed_bases = [
11251 "flow F() { }",
11252 "intent I { }",
11253 "persona P { name: \"P\" role: \"R\" }",
11254 "intent J { ask: \"a\" }",
11255 "type T = String",
11256 ];
11257 for (bucket, base) in (0..100u64).zip(seed_bases.iter().cycle()) {
11259 let mut rng = Xorshift(0x1234_5678_9abc_def0_u64.wrapping_add(bucket));
11260 let mut current = (*base).to_string();
11261 for _ in 0..10 {
11262 current = mutate(¤t, &mut rng);
11263 let toks = match Lexer::new(¤t, "<fuzz>").tokenize() {
11266 Ok(t) => t,
11267 Err(_) => continue,
11268 };
11269 let _pr = Parser::new(toks).parse_with_recovery();
11271 }
11272 }
11273 }
11274
11275 #[test]
11278 fn missing_colon_hint_preserved_under_recovery() {
11279 let src = "flow F() { run R { input { user_message: \"hi\" } } }";
11283 let pr = recover(src);
11284 if !pr.errors.is_empty() {
11288 let any_msg = pr.errors.iter().any(|e| !e.message.is_empty());
11289 assert!(any_msg);
11290 }
11291 }
11292
11293 #[test]
11296 fn recovered_declarations_appear_in_source_order() {
11297 let src = "flow A() { } garbage flow B() { } garbage flow C() { }";
11298 let pr = recover(src);
11299 let names: Vec<&str> = pr
11300 .program
11301 .declarations
11302 .iter()
11303 .filter_map(|d| match d {
11304 Declaration::Flow(f) => Some(f.name.as_str()),
11305 _ => None,
11306 })
11307 .collect();
11308 assert_eq!(names, vec!["A", "B", "C"]);
11309 }
11310}
11311
11312#[cfg(test)]
11320mod fase28_source_context_tests {
11321 use super::*;
11322 use crate::lexer::Lexer;
11323
11324 fn snippet(source: &str, line: u32, column: u32, filename: &str) -> String {
11325 SourceSnippet::new(
11326 source.to_string(),
11327 line,
11328 column,
11329 filename.to_string(),
11330 )
11331 .render()
11332 }
11333
11334 #[test]
11337 fn rustc_style_block_for_middle_line() {
11338 let src = "line one\nline two\nline three\nline four\nline five";
11339 let out = snippet(src, 3, 6, "x.axon");
11340 assert!(out.contains("--> x.axon:3:6"));
11341 assert!(out.contains("1 | line one"));
11342 assert!(out.contains("2 | line two"));
11343 assert!(out.contains("3 | line three"));
11344 assert!(out.contains("4 | line four"));
11345 assert!(out.contains("5 | line five"));
11346 assert!(out.contains("\n | ^"), "out:\n{out}");
11348 }
11349
11350 #[test]
11351 fn caret_column_one_renders_correctly() {
11352 let out = snippet("abc\n", 1, 1, "<source>");
11353 assert!(out.contains("\n | ^"));
11354 }
11355
11356 #[test]
11357 fn first_line_clamps_context_before_to_zero() {
11358 let src = "first\nsecond\nthird\nfourth\nfifth";
11359 let out = snippet(src, 1, 1, "<source>");
11360 assert!(out.contains("1 | first"));
11361 assert!(out.contains("2 | second"));
11362 assert!(out.contains("3 | third"));
11363 assert!(!out.contains("4 | fourth"));
11364 }
11365
11366 #[test]
11367 fn last_line_clamps_context_after_to_eof() {
11368 let src = "first\nsecond\nthird\nfourth\nfifth";
11369 let out = snippet(src, 5, 2, "<source>");
11370 assert!(out.contains("5 | fifth"));
11371 assert!(out.contains("3 | third"));
11372 assert!(out.contains("4 | fourth"));
11373 assert!(!out.contains("2 | second"));
11374 }
11375
11376 #[test]
11377 fn gutter_width_grows_with_line_count() {
11378 let src: String = (1..=12).map(|i| format!("line{i}")).collect::<Vec<_>>().join("\n");
11379 let out = snippet(&src, 12, 1, "<source>");
11380 assert!(out.contains("12 | line12"));
11381 assert!(out.contains("10 | line10"));
11382 }
11383
11384 #[test]
11387 fn empty_source_returns_empty() {
11388 assert_eq!(snippet("", 1, 1, "<source>"), "");
11389 }
11390
11391 #[test]
11392 fn zero_line_returns_empty() {
11393 assert_eq!(snippet("hi", 0, 1, "<source>"), "");
11394 }
11395
11396 #[test]
11397 fn out_of_range_line_returns_empty() {
11398 assert_eq!(snippet("hi", 99, 1, "<source>"), "");
11399 }
11400
11401 #[test]
11402 fn caret_clamps_past_eol() {
11403 let out = snippet("hello", 1, 50, "<source>");
11404 assert!(out.contains("\n | ^"), "out:\n{out}");
11405 }
11406
11407 #[test]
11408 fn unicode_codepoint_count_for_caret_clamp() {
11409 let out = snippet("héllo", 1, 99, "<source>");
11411 assert!(out.contains("\n | ^"), "out:\n{out}");
11412 }
11413
11414 #[test]
11415 fn trailing_newline_does_not_create_phantom_last_line() {
11416 let out = snippet("first\nsecond\n", 2, 1, "<source>");
11417 assert!(!out.contains("3 |"));
11418 assert!(out.contains("2 | second"));
11419 }
11420
11421 fn lex(src: &str) -> Vec<Token> {
11424 Lexer::new(src, "<test>").tokenize().expect("lex")
11425 }
11426
11427 #[test]
11428 fn strict_parse_attaches_snippet_when_source_given() {
11429 let src = "garbage_token\nflow F() { }";
11430 let err = Parser::new(lex(src))
11431 .with_source(src, "x.axon")
11432 .parse()
11433 .expect_err("must error");
11434 assert!(err.source_snippet.is_some());
11435 let display = format!("{err}");
11436 assert!(display.contains("--> x.axon:"), "display: {display}");
11437 }
11438
11439 #[test]
11440 fn strict_parse_no_snippet_when_no_source() {
11441 let src = "garbage_token";
11442 let err = Parser::new(lex(src)).parse().expect_err("must error");
11443 assert!(err.source_snippet.is_none());
11444 let display = format!("{err}");
11445 assert!(!display.contains("\n -->"));
11446 }
11447
11448 #[test]
11449 fn every_recovered_error_has_snippet() {
11450 let src = "garbage1\nflow F() { }\ngarbage2\nflow G() { }";
11451 let result = Parser::new(lex(src))
11452 .with_source(src, "multi.axon")
11453 .parse_with_recovery();
11454 assert!(!result.errors.is_empty());
11455 for err in &result.errors {
11456 assert!(err.source_snippet.is_some());
11457 let display = format!("{err}");
11458 assert!(
11459 display.contains("--> multi.axon:"),
11460 "display: {display}"
11461 );
11462 }
11463 }
11464
11465 #[test]
11466 fn recovery_no_snippet_when_no_source() {
11467 let src = "garbage1 garbage2";
11468 let result = Parser::new(lex(src)).parse_with_recovery();
11469 for err in &result.errors {
11470 assert!(err.source_snippet.is_none());
11471 }
11472 }
11473
11474 #[test]
11475 fn snippet_points_at_correct_line_for_each_error() {
11476 let src = "garbage_a\nflow F() { }\ngarbage_b\nflow G() { }";
11477 let result = Parser::new(lex(src))
11478 .with_source(src, "x")
11479 .parse_with_recovery();
11480 for err in &result.errors {
11481 let sn = err.source_snippet.as_ref().expect("snippet");
11482 assert_eq!(sn.line, err.line);
11483 }
11484 }
11485
11486 #[test]
11489 fn legacy_constructor_still_works() {
11490 let src = "flow F() { }";
11491 let prog = Parser::new(lex(src)).parse().expect("clean");
11492 assert_eq!(prog.declarations.len(), 1);
11493 }
11494
11495 #[test]
11496 fn attach_source_idempotent() {
11497 let err = ParseError {
11498 message: "bad".to_string(),
11499 line: 2,
11500 column: 3,
11501 ..Default::default()
11502 };
11503 let err2 = err.clone().attach_source("a\nb\nc\n", "f.axon");
11504 let first = format!("{err2}");
11505 let err3 = err.attach_source("a\nb\nc\n", "f.axon");
11506 let second = format!("{err3}");
11507 assert_eq!(first, second);
11508 }
11509
11510 #[test]
11511 fn attach_source_noop_when_line_zero() {
11512 let err = ParseError {
11513 message: "bad".to_string(),
11514 line: 0,
11515 column: 0,
11516 ..Default::default()
11517 };
11518 let err = err.attach_source("a\nb\nc\n", "f.axon");
11519 assert!(err.source_snippet.is_none());
11520 }
11521
11522 #[test]
11528 fn golden_simple_three_line_block() {
11529 let src = "alpha\nbeta\ngamma";
11530 let out = snippet(src, 2, 3, "g.axon");
11531 let expected = concat!(
11535 " --> g.axon:2:3\n",
11536 " |\n",
11537 "1 | alpha\n",
11538 "2 | beta\n",
11539 " | ^\n",
11540 "3 | gamma",
11541 );
11542 assert_eq!(out, expected);
11543 }
11544
11545 #[test]
11546 fn golden_first_line_caret() {
11547 let src = "abc\ndef\n";
11548 let out = snippet(src, 1, 1, "x");
11549 let expected = concat!(
11550 " --> x:1:1\n",
11551 " |\n",
11552 "1 | abc\n",
11553 " | ^\n",
11554 "2 | def",
11555 );
11556 assert_eq!(out, expected);
11557 }
11558
11559 #[test]
11560 fn golden_two_digit_gutter() {
11561 let src: String = (1..=11)
11562 .map(|i| format!("L{i}"))
11563 .collect::<Vec<_>>()
11564 .join("\n");
11565 let out = snippet(&src, 10, 2, "big");
11566 let expected = concat!(
11567 " --> big:10:2\n",
11568 " |\n",
11569 " 8 | L8\n",
11570 " 9 | L9\n",
11571 "10 | L10\n",
11572 " | ^\n",
11573 "11 | L11",
11574 );
11575 assert_eq!(out, expected);
11576 }
11577}
11578
11579#[cfg(test)]
11586mod fase28_smart_suggest_parser_tests {
11587 use super::*;
11588 use crate::lexer::Lexer;
11589
11590 fn lex(src: &str) -> Vec<Token> {
11591 Lexer::new(src, "<test>").tokenize().expect("lex")
11592 }
11593
11594 #[test]
11595 fn top_level_typo_suggests_flow() {
11596 let src = "flwo F() { }";
11597 let err = Parser::new(lex(src)).parse().expect_err("must error");
11598 assert!(
11599 err.message.contains("Did you mean `flow`?"),
11600 "msg: {}",
11601 err.message
11602 );
11603 }
11604
11605 #[test]
11606 fn top_level_unknown_far_no_suggestion() {
11607 let src = "qwerty F() { }";
11608 let err = Parser::new(lex(src)).parse().expect_err("must error");
11609 assert!(
11610 !err.message.contains("Did you mean"),
11611 "msg: {}",
11612 err.message
11613 );
11614 }
11615
11616 #[test]
11617 fn flow_body_typo_suggests_step() {
11618 let src = "flow F() { stepp S {} }";
11619 let err = Parser::new(lex(src)).parse().expect_err("must error");
11620 assert!(
11621 err.message.contains("Did you mean `step`"),
11622 "msg: {}",
11623 err.message
11624 );
11625 }
11626
11627 #[test]
11628 fn flow_body_typo_suggests_reason() {
11629 let src = "flow F() { reasn R {} }";
11630 let err = Parser::new(lex(src)).parse().expect_err("must error");
11631 assert!(
11632 err.message.contains("Did you mean `reason`?"),
11633 "msg: {}",
11634 err.message
11635 );
11636 }
11637
11638 #[test]
11639 fn recovery_mode_carries_hint() {
11640 let src = "flwo F() { }";
11641 let result = Parser::new(lex(src)).parse_with_recovery();
11642 assert!(
11643 result
11644 .errors
11645 .iter()
11646 .any(|e| e.message.contains("Did you mean `flow`?")),
11647 "errors: {:?}",
11648 result.errors
11649 );
11650 }
11651}
11652
11653#[cfg(test)]
11656mod fase35m_mutate_purge_where_tests {
11657 use super::*;
11658
11659 fn parse(src: &str) -> Program {
11660 let tokens = crate::lexer::Lexer::new(src, "<test>")
11661 .tokenize()
11662 .expect("lex");
11663 Parser::new(tokens).parse().expect("parse")
11664 }
11665
11666 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11667 for d in &prog.declarations {
11668 if let Declaration::Flow(f) = d {
11669 if f.name == flow {
11670 return f.body.first().expect("flow has at least one step");
11671 }
11672 }
11673 }
11674 panic!("flow `{flow}` not found");
11675 }
11676
11677 #[test]
11678 fn mutate_captures_its_where_clause() {
11679 let prog =
11682 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
11683 match first_step(&prog, "F") {
11684 FlowStep::Mutate(m) => {
11685 assert_eq!(m.store_name, "accounts");
11686 assert_eq!(m.where_expr, "id = 1");
11687 }
11688 other => panic!("expected Mutate, got {other:?}"),
11689 }
11690 }
11691
11692 #[test]
11693 fn purge_captures_its_where_clause() {
11694 let prog =
11695 parse("flow F() -> Unit { purge logs { where: \"ts < 100\" } }");
11696 match first_step(&prog, "F") {
11697 FlowStep::Purge(p) => {
11698 assert_eq!(p.store_name, "logs");
11699 assert_eq!(p.where_expr, "ts < 100");
11700 }
11701 other => panic!("expected Purge, got {other:?}"),
11702 }
11703 }
11704
11705 #[test]
11706 fn mutate_without_a_where_block_is_a_whole_store_op() {
11707 let prog = parse("flow F() -> Unit { mutate accounts }");
11710 match first_step(&prog, "F") {
11711 FlowStep::Mutate(m) => {
11712 assert_eq!(m.store_name, "accounts");
11713 assert_eq!(m.where_expr, "");
11714 }
11715 other => panic!("expected Mutate, got {other:?}"),
11716 }
11717 }
11718}
11719
11720#[cfg(test)]
11723mod fase35o_persist_fields_tests {
11724 use super::*;
11725
11726 fn parse(src: &str) -> Program {
11727 let tokens = crate::lexer::Lexer::new(src, "<test>")
11728 .tokenize()
11729 .expect("lex");
11730 Parser::new(tokens).parse().expect("parse")
11731 }
11732
11733 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11734 for d in &prog.declarations {
11735 if let Declaration::Flow(f) = d {
11736 if f.name == flow {
11737 return f.body.first().expect("flow has at least one step");
11738 }
11739 }
11740 }
11741 panic!("flow `{flow}` not found");
11742 }
11743
11744 #[test]
11745 fn persist_captures_its_field_block() {
11746 let prog = parse(
11750 "flow F() -> Unit { persist into chat_history { \
11751 session_id: \"${session_id}\" sender: \"user\" \
11752 content: \"${message}\" } }",
11753 );
11754 match first_step(&prog, "F") {
11755 FlowStep::Persist(p) => {
11756 assert_eq!(p.store_name, "chat_history");
11757 assert_eq!(
11758 p.fields,
11759 vec![
11760 ("session_id".to_string(), "${session_id}".to_string()),
11761 ("sender".to_string(), "user".to_string()),
11762 ("content".to_string(), "${message}".to_string()),
11763 ]
11764 );
11765 }
11766 other => panic!("expected Persist, got {other:?}"),
11767 }
11768 }
11769
11770 #[test]
11771 fn persist_without_a_block_keeps_the_user_bindings_fallback() {
11772 let prog = parse("flow F() -> Unit { persist events }");
11775 match first_step(&prog, "F") {
11776 FlowStep::Persist(p) => {
11777 assert_eq!(p.store_name, "events");
11778 assert!(p.fields.is_empty());
11779 }
11780 other => panic!("expected Persist, got {other:?}"),
11781 }
11782 }
11783
11784 #[test]
11785 fn persist_accepts_the_optional_into_connector() {
11786 let with =
11789 parse("flow F() -> Unit { persist into accounts { id: \"1\" } }");
11790 let without =
11791 parse("flow F() -> Unit { persist accounts { id: \"1\" } }");
11792 for prog in [&with, &without] {
11793 match first_step(prog, "F") {
11794 FlowStep::Persist(p) => assert_eq!(p.store_name, "accounts"),
11795 other => panic!("expected Persist, got {other:?}"),
11796 }
11797 }
11798 }
11799
11800 #[test]
11801 fn persist_into_without_a_block_resolves_the_store_name() {
11802 let prog = parse("flow F() -> Unit { persist into events }");
11805 match first_step(&prog, "F") {
11806 FlowStep::Persist(p) => {
11807 assert_eq!(p.store_name, "events");
11808 assert!(p.fields.is_empty());
11809 }
11810 other => panic!("expected Persist, got {other:?}"),
11811 }
11812 }
11813
11814 #[test]
11815 fn persist_fields_lower_into_the_ir() {
11816 let prog = parse(
11819 "flow F() -> Unit { persist into chat { content: \"${msg}\" } }",
11820 );
11821 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
11822 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
11823 match flow.steps.first().expect("one step") {
11824 crate::ir_nodes::IRFlowNode::Persist(p) => {
11825 assert_eq!(p.store_name, "chat");
11826 assert_eq!(
11827 p.fields,
11828 vec![("content".to_string(), "${msg}".to_string())]
11829 );
11830 }
11831 other => panic!("expected IRFlowNode::Persist, got {other:?}"),
11832 }
11833 }
11834}
11835
11836#[cfg(test)]
11839mod fase35p_mutate_fields_tests {
11840 use super::*;
11841
11842 fn parse(src: &str) -> Program {
11843 let tokens = crate::lexer::Lexer::new(src, "<test>")
11844 .tokenize()
11845 .expect("lex");
11846 Parser::new(tokens).parse().expect("parse")
11847 }
11848
11849 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11850 for d in &prog.declarations {
11851 if let Declaration::Flow(f) = d {
11852 if f.name == flow {
11853 return f.body.first().expect("flow has at least one step");
11854 }
11855 }
11856 }
11857 panic!("flow `{flow}` not found");
11858 }
11859
11860 #[test]
11861 fn mutate_captures_its_set_field_block() {
11862 let prog = parse(
11866 "flow F() -> Unit { mutate accounts { where: \"id = ${id}\" \
11867 balance: \"${new_balance}\" status: \"active\" } }",
11868 );
11869 match first_step(&prog, "F") {
11870 FlowStep::Mutate(m) => {
11871 assert_eq!(m.store_name, "accounts");
11872 assert_eq!(m.where_expr, "id = ${id}");
11873 assert_eq!(
11874 m.fields,
11875 vec![
11876 ("balance".to_string(), "${new_balance}".to_string()),
11877 ("status".to_string(), "active".to_string()),
11878 ]
11879 );
11880 }
11881 other => panic!("expected Mutate, got {other:?}"),
11882 }
11883 }
11884
11885 #[test]
11886 fn mutate_where_only_block_has_no_set_fields() {
11887 let prog =
11890 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
11891 match first_step(&prog, "F") {
11892 FlowStep::Mutate(m) => {
11893 assert_eq!(m.where_expr, "id = 1");
11894 assert!(m.fields.is_empty());
11895 }
11896 other => panic!("expected Mutate, got {other:?}"),
11897 }
11898 }
11899
11900 #[test]
11901 fn mutate_with_no_block_is_a_whole_store_op() {
11902 let prog = parse("flow F() -> Unit { mutate accounts }");
11905 match first_step(&prog, "F") {
11906 FlowStep::Mutate(m) => {
11907 assert_eq!(m.store_name, "accounts");
11908 assert_eq!(m.where_expr, "");
11909 assert!(m.fields.is_empty());
11910 }
11911 other => panic!("expected Mutate, got {other:?}"),
11912 }
11913 }
11914
11915 #[test]
11916 fn mutate_fields_lower_into_the_ir() {
11917 let prog = parse(
11918 "flow F() -> Unit { mutate t { where: \"id = 1\" v: \"${x}\" } }",
11919 );
11920 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
11921 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
11922 match flow.steps.first().expect("one step") {
11923 crate::ir_nodes::IRFlowNode::Mutate(m) => {
11924 assert_eq!(m.where_expr, "id = 1");
11925 assert_eq!(
11926 m.fields,
11927 vec![("v".to_string(), "${x}".to_string())]
11928 );
11929 }
11930 other => panic!("expected IRFlowNode::Mutate, got {other:?}"),
11931 }
11932 }
11933}
11934