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 secret: String::new(),
2523 secret_partition: String::new(),
2524 target: None,
2525 risk: None,
2526 argv: Vec::new(),
2527 cache: String::new(),
2528 scrape: None,
2529 loc,
2530 leading_trivia: Vec::new(),
2531 trailing_trivia: Vec::new(),
2532 };
2533
2534 let mut unknown_fields: Vec<(String, u32, u32)> = Vec::new();
2541
2542 while !self.check(TokenType::RBrace) {
2543 let field_tok = self.current().clone();
2544 let field_name = field_tok.value.clone();
2545 self.advance();
2546 self.consume(TokenType::Colon)?;
2547
2548 match field_name.as_str() {
2549 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
2550 "max_results" => {
2551 node.max_results = Some(
2552 self.consume(TokenType::Integer)?
2553 .value
2554 .parse::<i64>()
2555 .unwrap_or(0),
2556 )
2557 }
2558 "filter" => node.filter_expr = self.parse_filter_expression()?,
2559 "timeout" => node.timeout = self.consume(TokenType::Duration)?.value,
2560 "runtime" => node.runtime = self.consume_any_ident_or_kw()?.value,
2561 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
2565 "sandbox" => node.sandbox = Some(self.parse_bool()?),
2566 "effects" => node.effects = Some(self.parse_effect_row()?),
2567 "parameters" => node.parameters = self.parse_tool_param_schema()?,
2569 "output_type" => node.output_type = Some(self.parse_output_type_string()?),
2570 "secret" => node.secret = self.parse_dotted_identifier()?,
2574 "secret_partition" => {
2580 node.secret_partition = self.consume_any_ident_or_kw()?.value
2581 }
2582 "target" => node.target = Some(self.consume_any_ident_or_kw()?.value),
2584 "risk" => node.risk = Some(self.consume_any_ident_or_kw()?.value),
2585 "argv" => node.argv = self.parse_bracketed_strings()?,
2589 "cache" => node.cache = self.consume_any_ident_or_kw()?.value,
2592 "scrape" => node.scrape = Some(self.parse_scrape_spec()?),
2595 _ => {
2596 unknown_fields.push((field_name, field_tok.line, field_tok.column));
2597 self.skip_value();
2598 }
2599 }
2600 }
2601 self.consume(TokenType::RBrace)?;
2602
2603 if node.target.is_some() || node.scrape.is_some() {
2611 if let Some((field_name, line, column)) = unknown_fields.into_iter().next() {
2612 let (surface, valid) = if node.target.is_some() {
2613 (
2614 "technician tool (§Fase 84 D84.13)",
2615 "provider, parameters, output_type, timeout, effects, target, risk, argv",
2616 )
2617 } else {
2618 (
2619 "web-acquisition tool (§Fase 98 D98.2)",
2620 "provider, parameters, output_type, timeout, effects, secret, \
2621 secret_partition, cache, scrape",
2622 )
2623 };
2624 return Err(ParseError {
2625 message: format!(
2626 "unknown field `{field_name}` in {surface} `{}` — this tool uses \
2627 strict field checking; valid fields: {valid}",
2628 node.name
2629 ),
2630 line,
2631 column,
2632 ..Default::default()
2633 });
2634 }
2635 }
2636 Ok(node)
2637 }
2638
2639 fn parse_scrape_spec(&mut self) -> Result<crate::ast::ScrapeSpec, ParseError> {
2644 let open = self.consume(TokenType::LBrace)?;
2645 let loc = self.loc_of(&open);
2646 let mut spec = crate::ast::ScrapeSpec {
2647 loc,
2648 ..Default::default()
2649 };
2650 while !self.check(TokenType::RBrace) {
2651 let field_tok = self.current().clone();
2652 let field_name = field_tok.value.clone();
2653 self.advance();
2654 self.consume(TokenType::Colon)?;
2655 match field_name.as_str() {
2656 "engine" => spec.engine = Some(self.consume_any_ident_or_kw()?.value),
2657 "impersonate" => spec.impersonate = Some(self.consume_any_ident_or_kw()?.value),
2658 "render_wait" => spec.render_wait = Some(self.consume(TokenType::Duration)?.value),
2659 "proxy" => spec.proxy = self.parse_dotted_identifier()?,
2660 "respect_robots" => spec.respect_robots = Some(self.parse_bool()?),
2661 "extract" => spec.extract = self.parse_bracketed_strings()?,
2662 "adaptive" => spec.adaptive = Some(self.parse_bool()?),
2663 "similarity_floor" => spec.similarity_floor = self.parse_optional_float(),
2664 "follow" => spec.follow = self.consume(TokenType::StringLit)?.value,
2665 "max_depth" => {
2666 spec.max_depth =
2667 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2668 }
2669 "max_pages" => {
2670 spec.max_pages =
2671 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2672 }
2673 "concurrency" => {
2674 spec.concurrency =
2675 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2676 }
2677 "politeness" => spec.politeness = self.consume_any_ident_or_kw()?.value,
2678 "checkpoint" => spec.checkpoint = self.consume_any_ident_or_kw()?.value,
2679 other => {
2680 return Err(self.error(&format!(
2681 "unknown scrape field `{other}` — the `scrape: {{ … }}` block is a \
2682 closed catalog (§Fase 98 D98.2); valid fields: engine, impersonate, \
2683 render_wait, proxy, respect_robots, extract, adaptive, \
2684 similarity_floor, follow, max_depth, max_pages, concurrency, \
2685 politeness, checkpoint"
2686 )));
2687 }
2688 }
2689 }
2690 self.consume(TokenType::RBrace)?;
2691 Ok(spec)
2692 }
2693
2694 fn parse_tool_param_schema(&mut self) -> Result<Vec<Parameter>, ParseError> {
2700 self.consume(TokenType::LBrace)?;
2701 let mut params = Vec::new();
2702 while !self.check(TokenType::RBrace) {
2703 let name = self.consume_any_ident_or_kw()?;
2707 let ploc = self.loc_of(&name);
2708 self.consume(TokenType::Colon)?;
2709 let type_expr = self.parse_type_expr()?;
2710 params.push(Parameter {
2711 name: name.value,
2712 type_expr,
2713 loc: ploc,
2714 });
2715 if self.check(TokenType::Comma) {
2716 self.advance();
2717 } else {
2718 break;
2719 }
2720 }
2721 self.consume(TokenType::RBrace)?;
2722 Ok(params)
2723 }
2724
2725 fn parse_filter_expression(&mut self) -> Result<String, ParseError> {
2726 let name = self.consume_any_ident_or_kw()?.value;
2727 if self.check(TokenType::LParen) {
2728 self.advance();
2729 let mut parts = vec![name, "(".to_string()];
2730 while !self.check(TokenType::RParen) {
2731 parts.push(self.advance().value.clone());
2732 }
2733 self.consume(TokenType::RParen)?;
2734 parts.push(")".to_string());
2735 Ok(parts.join(""))
2736 } else {
2737 Ok(name)
2738 }
2739 }
2740
2741 fn parse_effect_row(&mut self) -> Result<EffectRow, ParseError> {
2742 let tok = self.consume(TokenType::Lt)?;
2743 let loc = self.loc_of(&tok);
2744 let mut effects = Vec::new();
2745 let mut epistemic_level = String::new();
2746
2747 while !self.check(TokenType::Gt) {
2748 let name = self.consume_any_ident_or_kw()?.value;
2749 if self.check(TokenType::Colon) {
2750 self.advance();
2751 let level = self.parse_qualifier_value()?;
2768 if name == "epistemic" {
2769 epistemic_level = level;
2770 } else {
2771 effects.push(format!("{name}:{level}"));
2772 }
2773 } else {
2774 effects.push(name);
2775 }
2776 if self.check(TokenType::Comma) {
2777 self.advance();
2778 }
2779 }
2780 self.consume(TokenType::Gt)?;
2781
2782 Ok(EffectRow {
2783 effects,
2784 epistemic_level,
2785 loc,
2786 })
2787 }
2788
2789 fn parse_qualifier_value(&mut self) -> Result<String, ParseError> {
2798 let mut buf = self.consume_dotted_slug_segment()?;
2799 loop {
2800 let sep = if self.check(TokenType::Dot) {
2801 '.'
2802 } else if self.check(TokenType::Colon) {
2803 ':'
2804 } else {
2805 break;
2806 };
2807 self.advance();
2808 let part = self.consume_dotted_slug_segment()?;
2809 buf.push(sep);
2810 buf.push_str(&part);
2811 }
2812 Ok(buf)
2813 }
2814
2815 fn consume_dotted_slug_segment(&mut self) -> Result<String, ParseError> {
2827 let first = self.consume_any_ident_or_kw()?;
2828 let mut buf = first.value.clone();
2829 let mut next_line = first.line;
2830 let mut next_col = first.column + first.value.chars().count() as u32;
2831 loop {
2832 let cur = self.current();
2833 let is_segment_token = matches!(cur.ttype, TokenType::Identifier | TokenType::Integer,);
2834 if !is_segment_token {
2835 break;
2836 }
2837 if cur.line != next_line || cur.column != next_col {
2838 break;
2839 }
2840 buf.push_str(&cur.value);
2841 next_col = cur.column + cur.value.chars().count() as u32;
2842 next_line = cur.line;
2843 self.pos += 1;
2844 }
2845 Ok(buf)
2846 }
2847
2848 fn parse_type_def(&mut self) -> Result<TypeDefinition, ParseError> {
2851 let tok = self.consume(TokenType::Type)?;
2852 let loc = self.loc_of(&tok);
2853 let name = self.consume(TokenType::Identifier)?.value;
2854
2855 let mut node = TypeDefinition {
2856 name,
2857 fields: Vec::new(),
2858 range_constraint: None,
2859 where_clause: None,
2860 compliance: Vec::new(),
2861 loc: loc.clone(),
2862 leading_trivia: Vec::new(),
2863 trailing_trivia: Vec::new(),
2864 };
2865
2866 if self.check(TokenType::LParen) {
2868 self.advance();
2869 let min_val = self.consume_number()?;
2870 self.consume(TokenType::DotDot)?;
2871 let max_val = self.consume_number()?;
2872 self.consume(TokenType::RParen)?;
2873 node.range_constraint = Some(RangeConstraint {
2874 min_value: min_val,
2875 max_value: max_val,
2876 loc: loc.clone(),
2877 });
2878 }
2879
2880 if self.check(TokenType::Where) {
2882 self.advance();
2883 let mut expr_parts = Vec::new();
2884 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
2885 if self.check(TokenType::Eof) {
2886 break;
2887 }
2888 expr_parts.push(self.advance().value.clone());
2889 }
2890 node.where_clause = Some(WhereClause {
2891 expression: expr_parts.join(" "),
2892 loc: loc.clone(),
2893 });
2894 }
2895
2896 if self.check(TokenType::Identifier) && self.current().value == "compliance" {
2899 self.advance();
2900 node.compliance = self.parse_bracketed_identifiers()?;
2901 }
2902
2903 if self.check(TokenType::LBrace) {
2905 self.advance();
2906 while !self.check(TokenType::RBrace) {
2907 let field_name = self.consume(TokenType::Identifier)?;
2908 let field_loc = self.loc_of(&field_name);
2909 self.consume(TokenType::Colon)?;
2910 let type_expr = self.parse_type_expr()?;
2911 node.fields.push(TypeField {
2912 name: field_name.value,
2913 type_expr,
2914 loc: field_loc,
2915 });
2916 if self.check(TokenType::Comma) {
2917 self.advance();
2918 }
2919 }
2920 self.consume(TokenType::RBrace)?;
2921 }
2922
2923 Ok(node)
2924 }
2925
2926 fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
2927 let name_tok = self.consume(TokenType::Identifier)?;
2928 let loc = self.loc_of(&name_tok);
2929 let mut generic_param = String::new();
2930 let mut optional = false;
2931
2932 if self.check(TokenType::Lt) {
2933 self.advance();
2934 let inner = self.parse_type_expr()?;
2944 generic_param = if inner.generic_param.is_empty() {
2945 inner.name
2946 } else {
2947 format!("{}<{}>", inner.name, inner.generic_param)
2948 };
2949 self.consume(TokenType::Gt)?;
2950 }
2951 if self.check(TokenType::LBracket) {
2955 self.advance();
2956 if matches!(self.current().ttype, TokenType::Integer | TokenType::Float) {
2957 generic_param = self.advance().value.clone();
2958 } else {
2959 let inner = self.parse_type_expr()?;
2960 generic_param = if inner.generic_param.is_empty() {
2961 inner.name
2962 } else {
2963 format!("{}[{}]", inner.name, inner.generic_param)
2964 };
2965 }
2966 self.consume(TokenType::RBracket)?;
2967 }
2968 if self.check(TokenType::Question) {
2969 self.advance();
2970 optional = true;
2971 }
2972
2973 Ok(TypeExpr {
2974 name: name_tok.value,
2975 generic_param,
2976 optional,
2977 loc,
2978 })
2979 }
2980
2981 fn parse_output_type_string(&mut self) -> Result<String, ParseError> {
3006 let expr = self.parse_type_expr()?;
3007 let mut s = expr.name;
3008 if !expr.generic_param.is_empty() {
3009 s.push('<');
3010 s.push_str(&expr.generic_param);
3011 s.push('>');
3012 }
3013 if expr.optional {
3014 s.push('?');
3015 }
3016 Ok(s)
3017 }
3018
3019 fn parse_flow(&mut self) -> Result<FlowDefinition, ParseError> {
3022 let tok = self.consume(TokenType::Flow)?;
3023 let loc = self.loc_of(&tok);
3024 let name = self.consume(TokenType::Identifier)?.value;
3025
3026 self.consume(TokenType::LParen)?;
3027 let mut parameters = Vec::new();
3028 if !self.check(TokenType::RParen) {
3029 parameters = self.parse_param_list()?;
3030 }
3031 self.consume(TokenType::RParen)?;
3032
3033 let mut return_type = None;
3034 if self.check(TokenType::Arrow) {
3035 self.advance();
3036 return_type = Some(self.parse_type_expr()?);
3037 }
3038
3039 self.consume(TokenType::LBrace)?;
3040 let mut body = Vec::new();
3041 while !self.check(TokenType::RBrace) {
3042 body.push(self.parse_flow_step()?);
3043 }
3044 self.consume(TokenType::RBrace)?;
3045
3046 Ok(FlowDefinition {
3047 name,
3048 parameters,
3049 return_type,
3050 body,
3051 loc,
3052 leading_trivia: Vec::new(),
3053 trailing_trivia: Vec::new(),
3054 })
3055 }
3056
3057 fn parse_param_list(&mut self) -> Result<Vec<Parameter>, ParseError> {
3058 let mut params = Vec::new();
3059
3060 let name = self.consume(TokenType::Identifier)?;
3061 let ploc = self.loc_of(&name);
3062 self.consume(TokenType::Colon)?;
3063 let type_expr = self.parse_type_expr()?;
3064 params.push(Parameter {
3065 name: name.value,
3066 type_expr,
3067 loc: ploc,
3068 });
3069
3070 while self.check(TokenType::Comma) {
3071 self.advance();
3072 let name = self.consume(TokenType::Identifier)?;
3073 let ploc = self.loc_of(&name);
3074 self.consume(TokenType::Colon)?;
3075 let type_expr = self.parse_type_expr()?;
3076 params.push(Parameter {
3077 name: name.value,
3078 type_expr,
3079 loc: ploc,
3080 });
3081 }
3082 Ok(params)
3083 }
3084
3085 fn parse_flow_step(&mut self) -> Result<FlowStep, ParseError> {
3088 let tok = self.current().clone();
3089
3090 match tok.ttype {
3091 TokenType::Step => self.parse_step().map(FlowStep::Step),
3092 TokenType::If => self.parse_if().map(FlowStep::If),
3093 TokenType::For => self.parse_for_in().map(FlowStep::ForIn),
3094 TokenType::Let => self.parse_let().map(FlowStep::Let),
3095 TokenType::Return => self.parse_return().map(FlowStep::Return),
3096 TokenType::Break => self.parse_break().map(FlowStep::Break),
3097 TokenType::Continue => self.parse_continue().map(FlowStep::Continue),
3098 TokenType::Lambda => self.parse_lambda_data_apply().map(FlowStep::LambdaDataApply),
3099
3100 TokenType::Probe => self.parse_flow_step_simple("probe").map(|l| FlowStep::Probe(ProbeStep { target: l.1, loc: l.0 })),
3102 TokenType::Reason => self.parse_flow_step_simple("reason").map(|l| FlowStep::Reason(ReasonStep { strategy: String::new(), target: l.1, loc: l.0 })),
3103 TokenType::Validate => self.parse_flow_step_simple("validate").map(|l| FlowStep::Validate(ValidateStep { target: l.1, rule: String::new(), loc: l.0 })),
3104 TokenType::Refine => self.parse_flow_step_simple("refine").map(|l| FlowStep::Refine(RefineStep { target: l.1, strategy: String::new(), loc: l.0 })),
3105 TokenType::Weave => self.parse_weave_step(),
3106 TokenType::Use => self.parse_use_step(),
3107 TokenType::Remember => self.parse_remember_step(),
3108 TokenType::Recall => self.parse_recall_step(),
3109 TokenType::Par => self.parse_par_block().map(FlowStep::Par),
3110 TokenType::Hibernate => self.parse_hibernate_step(),
3111 TokenType::Deliberate => self.parse_block_step("deliberate").map(|l| FlowStep::Deliberate(DeliberateBlock { loc: l })),
3112 TokenType::Consensus => self.parse_block_step("consensus").map(|l| FlowStep::Consensus(ConsensusBlock { loc: l })),
3113 TokenType::Forge => self.parse_forge_step().map(FlowStep::Forge),
3114 TokenType::Focus => self.parse_focus_step(),
3115 TokenType::Grad => self.parse_grad_step(),
3116 TokenType::Associate => self.parse_associate_step(),
3117 TokenType::Aggregate => self.parse_aggregate_step(),
3118 TokenType::Explore => self.parse_explore_step(),
3119 TokenType::Ingest => self.parse_ingest_step(),
3120 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 })),
3121 TokenType::Stream => self.parse_stream_block().map(FlowStep::Stream),
3127 TokenType::Navigate => self.parse_navigate_step(),
3128 TokenType::Drill => self.parse_drill_step(),
3129 TokenType::Trail => self.parse_flow_step_simple("trail").map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 })),
3130 TokenType::Corroborate => self.parse_corroborate_step(),
3131 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 })),
3132 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 })),
3133 TokenType::Compute => self.parse_compute_apply().map(FlowStep::ComputeApply),
3138 TokenType::Listen => self.parse_listen_step(),
3139 TokenType::Daemon => self.parse_flow_step_simple("daemon").map(|l| FlowStep::DaemonStep(DaemonStepNode { daemon_ref: l.1, loc: l.0 })),
3140 TokenType::Emit => self.parse_emit_step(),
3142 TokenType::Mint => self.parse_mint_step(),
3144 TokenType::Rotate => self.parse_rotate_step(),
3147 TokenType::Publish => self.parse_publish_step(),
3148 TokenType::Discover => self.parse_discover_step(),
3149 TokenType::Persist => self.parse_persist_step(),
3150 TokenType::Retrieve => self.parse_retrieve_step(),
3151 TokenType::Mutate => self.parse_mutate_step(),
3152 TokenType::Purge => self.parse_store_where_step().map(|(loc, store_name, where_expr)| FlowStep::Purge(PurgeStep { store_name, where_expr, loc })),
3153 TokenType::Transact => self.parse_block_step("transact").map(|l| FlowStep::Transact(TransactBlock { loc: l })),
3154 TokenType::Warden => self.parse_warden().map(FlowStep::Warden),
3156 TokenType::Quant => self.parse_quant().map(FlowStep::Quant),
3158 TokenType::Yield => self.parse_yield().map(FlowStep::Yield),
3160 TokenType::Run => self.parse_run().map(FlowStep::Run),
3164
3165 _ => {
3166 let hint = crate::smart_suggest::suggest_for(
3170 &tok.value,
3171 crate::smart_suggest::FLOW_BODY_KEYWORD_NAMES,
3172 );
3173 let base = format!(
3174 "Unexpected token in flow body: '{}' — expected step, if, for, let, return, ...",
3175 tok.value
3176 );
3177 let message = if hint.is_empty() {
3178 base
3179 } else {
3180 format!("{base}. {hint}")
3181 };
3182 Err(ParseError {
3183 message,
3184 line: tok.line,
3185 column: tok.column,
3186 ..Default::default()
3187 })
3188 }
3189 }
3190 }
3191
3192 fn parse_step(&mut self) -> Result<StepNode, ParseError> {
3195 let tok = self.consume(TokenType::Step)?;
3196 let loc = self.loc_of(&tok);
3197 let name = self.consume(TokenType::Identifier)?.value;
3198
3199 let mut persona_ref = String::new();
3200 if self.check(TokenType::Use) {
3201 self.advance();
3202 persona_ref = self.consume_any_ident_or_kw()?.value;
3203 }
3204
3205 self.consume(TokenType::LBrace)?;
3206
3207 let mut node = StepNode {
3208 name,
3209 persona_ref,
3210 given: String::new(),
3211 ask: String::new(),
3212 output_type: String::new(),
3213 confidence_floor: None,
3214 navigate_ref: String::new(),
3215 apply_ref: String::new(),
3216 requires_context: None,
3217 now_tz: None,
3218 loc,
3219 };
3220
3221 while !self.check(TokenType::RBrace) {
3222 let inner = self.current().clone();
3223
3224 match inner.ttype {
3225 TokenType::Given => {
3226 self.advance();
3227 self.consume(TokenType::Colon)?;
3228 node.given = self.parse_expression_string()?;
3229 }
3230 TokenType::Ask => {
3231 self.advance();
3232 self.consume(TokenType::Colon)?;
3233 node.ask = self.consume(TokenType::StringLit)?.value;
3234 }
3235 TokenType::Output => {
3236 self.advance();
3246 self.consume(TokenType::Colon)?;
3247 node.output_type = self.parse_output_type_string()?;
3248 }
3249 TokenType::Navigate => {
3250 self.advance();
3251 self.consume(TokenType::Colon)?;
3252 node.navigate_ref = self.parse_dotted_identifier()?;
3253 }
3254 TokenType::Identifier if inner.value == "confidence_floor" => {
3255 self.advance();
3256 self.consume(TokenType::Colon)?;
3257 node.confidence_floor = Some(self.consume_number()?);
3258 }
3259 TokenType::Identifier if inner.value == "apply" => {
3260 self.advance();
3261 self.consume(TokenType::Colon)?;
3262 node.apply_ref = self.consume_any_ident_or_kw()?.value;
3263 }
3264 TokenType::Identifier if inner.value == "requires_context" => {
3272 self.advance();
3273 self.consume(TokenType::Colon)?;
3274 let num = self.current().clone();
3275 let bad = |tok: &crate::tokens::Token| ParseError {
3276 message: format!(
3277 "`requires_context:` must be a positive integer token count \
3278 (got '{}')",
3279 tok.value
3280 ),
3281 line: tok.line,
3282 column: tok.column,
3283 ..Default::default()
3284 };
3285 if num.ttype != TokenType::Integer {
3286 return Err(bad(&num));
3287 }
3288 let value = num.value.parse::<u32>().map_err(|_| bad(&num))?;
3289 self.advance();
3290 node.requires_context = Some(value);
3291 }
3292 TokenType::Identifier if inner.value == "now" => {
3297 self.advance();
3298 self.consume(TokenType::Colon)?;
3299 let tz = self.current().clone();
3300 if tz.ttype != TokenType::StringLit {
3301 return Err(ParseError {
3302 message: format!(
3303 "`now:` must be an IANA timezone string literal like \
3304 \"America/Bogota\" or \"UTC\" (got '{}')",
3305 tz.value
3306 ),
3307 line: tz.line,
3308 column: tz.column,
3309 ..Default::default()
3310 });
3311 }
3312 self.advance();
3313 node.now_tz = Some(tz.value);
3314 }
3315 TokenType::Use => {
3325 let tool = self
3326 .tokens
3327 .get(self.pos + 1)
3328 .map(|t| t.value.as_str())
3329 .filter(|v| !v.is_empty())
3330 .unwrap_or("<Tool>");
3331 return Err(ParseError {
3332 message: format!(
3333 "`use` is not valid inside a `step {{ }}` body — the tool dispatch \
3334 would be silently dropped. To invoke a tool, either write the \
3335 flow-level step `use {tool} on <arg>` (outside this block), or bind \
3336 it inside this step with `apply: {tool}`. To attach a persona, put \
3337 it in the step header: `step <name> use <Persona> {{ … }}`."
3338 ),
3339 line: inner.line,
3340 column: inner.column,
3341 ..Default::default()
3342 });
3343 }
3344 TokenType::Probe
3346 | TokenType::Reason
3347 | TokenType::Weave
3348 | TokenType::Stream => {
3349 self.skip_flow_step_structural()?;
3350 }
3351 _ => {
3352 return Err(ParseError {
3353 message: format!(
3354 "Unexpected token in step body: '{}' — expected given, ask, \
3355 probe, reason, weave, stream, output, confidence_floor, navigate, \
3356 apply, requires_context, now",
3357 inner.value
3358 ),
3359 line: inner.line,
3360 column: inner.column,
3361 ..Default::default()
3362 });
3363 }
3364 }
3365 }
3366 self.consume(TokenType::RBrace)?;
3367 Ok(node)
3368 }
3369
3370 fn skip_flow_step_structural(&mut self) -> Result<(), ParseError> {
3372 self.advance();
3374 while !self.check(TokenType::LBrace)
3376 && !self.check(TokenType::RBrace)
3377 && !self.check(TokenType::Eof)
3378 {
3379 let tt = &self.current().ttype;
3381 if matches!(
3382 tt,
3383 TokenType::Step
3384 | TokenType::Given
3385 | TokenType::Ask
3386 | TokenType::Output
3387 | TokenType::Navigate
3388 | TokenType::Use
3389 | TokenType::Probe
3390 | TokenType::Reason
3391 | TokenType::Weave
3392 | TokenType::Stream
3393 | TokenType::If
3394 | TokenType::For
3395 | TokenType::Let
3396 | TokenType::Return
3397 ) {
3398 return Ok(());
3399 }
3400 self.advance();
3401 }
3402 if self.check(TokenType::LBrace) {
3404 self.skip_braced_block()?;
3405 }
3406 Ok(())
3407 }
3408
3409 fn parse_intent(&mut self) -> Result<IntentNode, ParseError> {
3412 let tok = self.consume(TokenType::Intent)?;
3413 let loc = self.loc_of(&tok);
3414 let name = self.consume(TokenType::Identifier)?.value;
3415 self.consume(TokenType::LBrace)?;
3416
3417 let mut node = IntentNode {
3418 name,
3419 given: String::new(),
3420 ask: String::new(),
3421 output_type: None,
3422 confidence_floor: None,
3423 loc,
3424 leading_trivia: Vec::new(),
3425 trailing_trivia: Vec::new(),
3426 };
3427
3428 while !self.check(TokenType::RBrace) {
3429 let field_name = self.current().value.clone();
3430 self.advance();
3431 self.consume(TokenType::Colon)?;
3432
3433 match field_name.as_str() {
3434 "given" => node.given = self.consume(TokenType::Identifier)?.value,
3435 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
3436 "output" => node.output_type = Some(self.parse_type_expr()?),
3437 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
3438 _ => self.skip_value(),
3439 }
3440 }
3441 self.consume(TokenType::RBrace)?;
3442 Ok(node)
3443 }
3444
3445 fn parse_run(&mut self) -> Result<RunStatement, ParseError> {
3448 let tok = self.consume(TokenType::Run)?;
3449 let loc = self.loc_of(&tok);
3450 let flow_name = self.consume(TokenType::Identifier)?.value;
3451
3452 self.consume(TokenType::LParen)?;
3453 let mut arguments = Vec::new();
3454 if !self.check(TokenType::RParen) {
3455 arguments = self.parse_argument_list()?;
3456 }
3457 self.consume(TokenType::RParen)?;
3458
3459 let mut node = RunStatement {
3460 flow_name,
3461 arguments,
3462 persona: String::new(),
3463 context: String::new(),
3464 anchors: Vec::new(),
3465 on_failure: String::new(),
3466 on_failure_params: Vec::new(),
3467 output_to: String::new(),
3468 effort: String::new(),
3469 loc,
3470 leading_trivia: Vec::new(),
3471 trailing_trivia: Vec::new(),
3472 };
3473
3474 while self.check_run_modifier() {
3475 let mod_tok = self.current().clone();
3476 match mod_tok.ttype {
3477 TokenType::As => {
3478 self.advance();
3479 node.persona = self.consume(TokenType::Identifier)?.value;
3480 }
3481 TokenType::Within => {
3482 self.advance();
3483 node.context = self.consume(TokenType::Identifier)?.value;
3484 }
3485 TokenType::ConstrainedBy => {
3486 self.advance();
3487 node.anchors = self.parse_bracketed_identifiers()?;
3488 }
3489 TokenType::OnFailure => {
3490 self.advance();
3491 self.consume(TokenType::Colon)?;
3492 node.on_failure = self.consume_any_ident_or_kw()?.value;
3493 if self.check(TokenType::LParen) {
3495 self.advance();
3496 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3497 let key = self.consume_any_ident_or_kw()?.value;
3498 self.consume(TokenType::Colon)?;
3499 let val = self.consume_any_ident_or_kw()?.value;
3500 node.on_failure_params.push((key, val));
3501 if self.check(TokenType::Comma) {
3502 self.advance();
3503 }
3504 }
3505 if self.check(TokenType::RParen) {
3506 self.advance();
3507 }
3508 }
3509 }
3510 TokenType::OutputTo => {
3511 self.advance();
3512 self.consume(TokenType::Colon)?;
3513 node.output_to = self.consume(TokenType::StringLit)?.value;
3514 }
3515 TokenType::Effort => {
3516 self.advance();
3517 self.consume(TokenType::Colon)?;
3518 node.effort = self.consume_any_ident_or_kw()?.value;
3519 }
3520 _ => break,
3521 }
3522 }
3523
3524 Ok(node)
3525 }
3526
3527 fn parse_epistemic_block(&mut self) -> Result<EpistemicBlock, ParseError> {
3530 let tok = self.current().clone();
3531 let mode = match tok.ttype {
3532 TokenType::Know => "know",
3533 TokenType::Believe => "believe",
3534 TokenType::Speculate => "speculate",
3535 TokenType::Doubt => "doubt",
3536 _ => unreachable!(),
3537 };
3538 self.advance();
3539 let loc = self.loc_of(&tok);
3540
3541 self.consume(TokenType::LBrace)?;
3542 let mut body = Vec::new();
3543 while !self.check(TokenType::RBrace) {
3544 body.push(self.parse_declaration()?);
3545 }
3546 self.consume(TokenType::RBrace)?;
3547
3548 Ok(EpistemicBlock {
3549 mode: mode.to_string(),
3550 body,
3551 loc,
3552 leading_trivia: Vec::new(),
3553 trailing_trivia: Vec::new(),
3554 })
3555 }
3556
3557 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
3565 self.parse_expr_bp(0)
3566 }
3567
3568 fn parse_expr_bp(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
3569 let mut lhs = match self.current().ttype {
3572 TokenType::Minus => {
3573 self.advance();
3574 Expr::Unary(UnOp::Neg, Box::new(self.parse_expr_bp(6)?))
3575 }
3576 TokenType::Not => {
3577 self.advance();
3578 Expr::Unary(UnOp::Not, Box::new(self.parse_expr_bp(6)?))
3579 }
3580 _ => self.parse_postfix()?,
3581 };
3582 while let Some((op, lbp)) = Self::binop_of(self.current().ttype.clone()) {
3584 if lbp < min_bp {
3585 break;
3586 }
3587 self.advance();
3588 let rhs = self.parse_expr_bp(lbp + 1)?;
3589 lhs = Expr::Binary(op, Box::new(lhs), Box::new(rhs));
3590 }
3591 Ok(lhs)
3592 }
3593
3594 fn binop_of(t: TokenType) -> Option<(BinOp, u8)> {
3597 Some(match t {
3598 TokenType::Or => (BinOp::Or, 1),
3599 TokenType::And => (BinOp::And, 2),
3600 TokenType::Eq => (BinOp::Eq, 3),
3601 TokenType::Neq => (BinOp::Ne, 3),
3602 TokenType::Lt => (BinOp::Lt, 3),
3603 TokenType::Lte => (BinOp::Le, 3),
3604 TokenType::Gt => (BinOp::Gt, 3),
3605 TokenType::Gte => (BinOp::Ge, 3),
3606 TokenType::Plus => (BinOp::Add, 4),
3607 TokenType::Minus => (BinOp::Sub, 4),
3608 TokenType::Star => (BinOp::Mul, 5),
3609 TokenType::Slash => (BinOp::Div, 5),
3610 TokenType::Percent => (BinOp::Mod, 5),
3611 _ => return None,
3612 })
3613 }
3614
3615 fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
3621 let mut expr = self.parse_expr_atom()?;
3622 loop {
3623 if self.check(TokenType::Dot) {
3624 self.advance();
3625 let name = self.consume_any_ident_or_kw()?.value;
3626 if let Some(builtin) = Builtin::from_name(&name) {
3627 let mut args = vec![expr];
3628 if self.check(TokenType::LParen) {
3629 self.advance();
3630 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3631 args.push(self.parse_expr_bp(0)?);
3632 if self.check(TokenType::Comma) {
3633 self.advance();
3634 } else {
3635 break;
3636 }
3637 }
3638 self.consume(TokenType::RParen)?;
3639 }
3640 expr = Expr::Call(builtin, args);
3641 } else {
3642 expr = match expr {
3646 Expr::Ref(p) => Expr::Ref(format!("{p}.{name}")),
3647 other => Expr::Field(Box::new(other), name),
3648 };
3649 }
3650 } else if self.check(TokenType::LBracket) {
3651 self.advance();
3653 let index = self.parse_expr_bp(0)?;
3654 self.consume(TokenType::RBracket)?;
3655 expr = Expr::Index(Box::new(expr), Box::new(index));
3656 } else {
3657 break;
3658 }
3659 }
3660 Ok(expr)
3661 }
3662
3663 fn parse_expr_atom(&mut self) -> Result<Expr, ParseError> {
3664 let tok = self.current().clone();
3665 match tok.ttype {
3666 TokenType::Integer => {
3667 self.advance();
3668 let lit = tok
3669 .value
3670 .parse::<i64>()
3671 .map(ExprLit::Int)
3672 .or_else(|_| tok.value.parse::<f64>().map(ExprLit::Float))
3673 .map_err(|_| ParseError {
3674 message: format!("invalid integer literal '{}'", tok.value),
3675 line: tok.line,
3676 column: tok.column,
3677 ..Default::default()
3678 })?;
3679 Ok(Expr::Lit(lit))
3680 }
3681 TokenType::Float => {
3682 self.advance();
3683 let f = tok.value.parse::<f64>().map_err(|_| ParseError {
3684 message: format!("invalid float literal '{}'", tok.value),
3685 line: tok.line,
3686 column: tok.column,
3687 ..Default::default()
3688 })?;
3689 Ok(Expr::Lit(ExprLit::Float(f)))
3690 }
3691 TokenType::Bool => {
3692 self.advance();
3693 Ok(Expr::Lit(ExprLit::Bool(tok.value == "true")))
3694 }
3695 TokenType::StringLit => {
3696 self.advance();
3697 Ok(Expr::Lit(ExprLit::Str(tok.value)))
3698 }
3699 TokenType::LParen => {
3700 self.advance();
3701 let inner = self.parse_expr_bp(0)?;
3702 self.consume(TokenType::RParen)?;
3703 Ok(inner)
3704 }
3705 _ => {
3706 Ok(Expr::Ref(self.consume_any_ident_or_kw()?.value))
3710 }
3711 }
3712 }
3713
3714 fn expr_lit_surface(lit: &ExprLit) -> String {
3719 match lit {
3720 ExprLit::Int(i) => i.to_string(),
3721 ExprLit::Float(f) => f.to_string(),
3722 ExprLit::Bool(b) => b.to_string(),
3723 ExprLit::Str(s) => s.clone(),
3724 }
3725 }
3726
3727 fn expr_leaf_surface(expr: &Expr) -> Option<String> {
3728 match expr {
3729 Expr::Ref(p) => Some(p.clone()),
3730 Expr::Lit(l) => Some(Self::expr_lit_surface(l)),
3731 _ => None,
3732 }
3733 }
3734
3735 fn expr_legacy_leaf(expr: &Expr) -> Option<(String, String, String)> {
3739 match expr {
3740 Expr::Ref(p) => Some((p.clone(), String::new(), String::new())),
3741 Expr::Binary(op, l, r) => {
3742 let op_s = match op {
3743 BinOp::Eq => "==",
3744 BinOp::Ne => "!=",
3745 BinOp::Lt => "<",
3746 BinOp::Le => "<=",
3747 BinOp::Gt => ">",
3748 BinOp::Ge => ">=",
3749 _ => return None,
3750 };
3751 let lhs = match &**l {
3752 Expr::Ref(p) => p.clone(),
3753 _ => return None,
3754 };
3755 let rhs = Self::expr_leaf_surface(r)?;
3756 Some((lhs, op_s.to_string(), rhs))
3757 }
3758 _ => None,
3759 }
3760 }
3761
3762 fn collect_or_leaves(expr: &Expr, out: &mut Vec<(String, String, String)>) -> bool {
3765 match expr {
3766 Expr::Binary(BinOp::Or, l, r) => {
3767 Self::collect_or_leaves(l, out) && Self::collect_or_leaves(r, out)
3768 }
3769 _ => match Self::expr_legacy_leaf(expr) {
3770 Some(t) => {
3771 out.push(t);
3772 true
3773 }
3774 None => false,
3775 },
3776 }
3777 }
3778
3779 #[allow(clippy::type_complexity)]
3785 fn cond_as_legacy(
3786 expr: &Expr,
3787 ) -> Option<(String, String, String, Vec<(String, String, String)>, String)> {
3788 let mut leaves = Vec::new();
3789 if !Self::collect_or_leaves(expr, &mut leaves) || leaves.is_empty() {
3790 return None;
3791 }
3792 let (c0, o0, v0) = leaves[0].clone();
3793 let rest = leaves[1..].to_vec();
3794 let conjunctor = if rest.is_empty() {
3795 String::new()
3796 } else {
3797 "or".to_string()
3798 };
3799 Some((c0, o0, v0, rest, conjunctor))
3800 }
3801
3802 fn parse_if(&mut self) -> Result<ConditionalNode, ParseError> {
3803 let tok = self.consume(TokenType::If)?;
3804 let loc = self.loc_of(&tok);
3805
3806 let expr = self.parse_expr()?;
3811 let (condition, comparison_op, comparison_value, conditions, conjunctor, cond) =
3812 match Self::cond_as_legacy(&expr) {
3813 Some((c, o, v, more, conj)) => (c, o, v, more, conj, None),
3814 None => (
3815 String::new(),
3816 String::new(),
3817 String::new(),
3818 Vec::new(),
3819 String::new(),
3820 Some(expr),
3821 ),
3822 };
3823
3824 let mut then_body = Vec::new();
3825 let mut else_body = Vec::new();
3826
3827 if self.check(TokenType::Arrow) {
3829 self.advance();
3830 then_body.push(self.parse_flow_step()?);
3831 } else if self.check(TokenType::LBrace) {
3832 self.advance();
3833 while !self.check(TokenType::RBrace) {
3834 then_body.push(self.parse_flow_step()?);
3835 }
3836 self.consume(TokenType::RBrace)?;
3837 }
3838
3839 if self.check(TokenType::Else) {
3841 self.advance();
3842 if self.check(TokenType::Arrow) {
3843 self.advance();
3844 else_body.push(self.parse_flow_step()?);
3845 } else if self.check(TokenType::LBrace) {
3846 self.advance();
3847 while !self.check(TokenType::RBrace) {
3848 else_body.push(self.parse_flow_step()?);
3849 }
3850 self.consume(TokenType::RBrace)?;
3851 }
3852 }
3853
3854 Ok(ConditionalNode {
3855 condition,
3856 comparison_op,
3857 comparison_value,
3858 then_body,
3859 else_body,
3860 conditions,
3861 conjunctor,
3862 cond,
3863 loc,
3864 })
3865 }
3866
3867 fn parse_for_in(&mut self) -> Result<ForInStatement, ParseError> {
3870 let tok = self.consume(TokenType::For)?;
3871 let loc = self.loc_of(&tok);
3872 let variable = self.consume(TokenType::Identifier)?.value;
3873 self.consume(TokenType::In)?;
3874 let iterable = self.parse_dotted_identifier()?;
3875
3876 self.consume(TokenType::LBrace)?;
3877 self.loop_depth += 1;
3884 let body_result = (|| -> Result<Vec<FlowStep>, ParseError> {
3885 let mut body = Vec::new();
3886 while !self.check(TokenType::RBrace) {
3887 body.push(self.parse_flow_step()?);
3888 }
3889 Ok(body)
3890 })();
3891 self.loop_depth -= 1;
3892 let body = body_result?;
3893 self.consume(TokenType::RBrace)?;
3894
3895 Ok(ForInStatement {
3896 variable,
3897 iterable,
3898 body,
3899 loc,
3900 })
3901 }
3902
3903 fn parse_break(&mut self) -> Result<BreakStatement, ParseError> {
3906 let tok = self.consume(TokenType::Break)?;
3907 let loc = self.loc_of(&tok);
3908 if self.loop_depth == 0 {
3909 return Err(ParseError {
3910 message: "'break' outside of a for-in loop body".to_string(),
3911 line: tok.line,
3912 column: tok.column,
3913 ..Default::default()
3914 });
3915 }
3916 Ok(BreakStatement { loc })
3917 }
3918
3919 fn parse_continue(&mut self) -> Result<ContinueStatement, ParseError> {
3922 let tok = self.consume(TokenType::Continue)?;
3923 let loc = self.loc_of(&tok);
3924 if self.loop_depth == 0 {
3925 return Err(ParseError {
3926 message: "'continue' outside of a for-in loop body".to_string(),
3927 line: tok.line,
3928 column: tok.column,
3929 ..Default::default()
3930 });
3931 }
3932 Ok(ContinueStatement { loc })
3933 }
3934
3935 fn parse_let(&mut self) -> Result<LetStatement, ParseError> {
3938 let tok = self.consume(TokenType::Let)?;
3939 let loc = self.loc_of(&tok);
3940
3941 let name = self.consume_any_ident_or_kw()?.value;
3943 let type_annotation = if self.check(TokenType::Colon) {
3945 self.advance();
3946 Some(self.parse_type_expr()?)
3947 } else {
3948 None
3949 };
3950 self.consume(TokenType::Assign)?;
3951 self.last_let_value_kind = "literal".to_string();
3954 let (value, value_ast) = self.parse_let_value_expr_with_ast()?;
3955
3956 Ok(LetStatement {
3957 identifier: name,
3958 value_expr: value,
3959 value_kind: self.last_let_value_kind.clone(),
3960 type_annotation,
3961 value_ast,
3962 loc,
3963 leading_trivia: Vec::new(),
3964 trailing_trivia: Vec::new(),
3965 })
3966 }
3967
3968 fn parse_let_value_expr(&mut self) -> Result<String, ParseError> {
3969 let atom = self.parse_let_atom()?;
3970
3971 if matches!(
3973 self.current().ttype,
3974 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3975 ) {
3976 let mut parts = vec![atom];
3977 while matches!(
3978 self.current().ttype,
3979 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
3980 ) {
3981 parts.push(self.advance().value.clone());
3982 parts.push(self.parse_let_atom()?);
3983 }
3984 self.last_let_value_kind = "expression".to_string();
3985 return Ok(parts.join(" "));
3986 }
3987 Ok(atom)
3988 }
3989
3990 fn parse_let_value_expr_with_ast(&mut self) -> Result<(String, Option<Expr>), ParseError> {
4000 if self.check(TokenType::LBracket) {
4001 self.last_let_value_kind = "literal".to_string();
4002 return Ok((self.parse_let_list_literal()?, None));
4003 }
4004 let expr = self.parse_expr()?;
4005 Ok(match expr {
4006 Expr::Lit(lit) => {
4007 self.last_let_value_kind = "literal".to_string();
4008 (Self::expr_lit_surface(&lit), None)
4009 }
4010 Expr::Ref(p) => {
4011 self.last_let_value_kind = "reference".to_string();
4012 (p, None)
4013 }
4014 other => {
4015 self.last_let_value_kind = "expression".to_string();
4016 (Self::render_expr(&other), Some(other))
4017 }
4018 })
4019 }
4020
4021 fn render_expr(e: &Expr) -> String {
4024 match e {
4025 Expr::Lit(l) => Self::expr_lit_surface(l),
4026 Expr::Ref(p) => p.clone(),
4027 Expr::Unary(UnOp::Neg, x) => format!("-{}", Self::render_expr(x)),
4028 Expr::Unary(UnOp::Not, x) => format!("not {}", Self::render_expr(x)),
4029 Expr::Binary(op, l, r) => {
4030 let sym = match op {
4031 BinOp::Add => "+",
4032 BinOp::Sub => "-",
4033 BinOp::Mul => "*",
4034 BinOp::Div => "/",
4035 BinOp::Mod => "%",
4036 BinOp::Eq => "==",
4037 BinOp::Ne => "!=",
4038 BinOp::Lt => "<",
4039 BinOp::Le => "<=",
4040 BinOp::Gt => ">",
4041 BinOp::Ge => ">=",
4042 BinOp::And => "and",
4043 BinOp::Or => "or",
4044 };
4045 format!("({} {sym} {})", Self::render_expr(l), Self::render_expr(r))
4046 }
4047 Expr::Call(b, args) => {
4048 let recv = args.first().map(Self::render_expr).unwrap_or_default();
4049 let rest: Vec<String> = args.iter().skip(1).map(Self::render_expr).collect();
4050 if rest.is_empty() {
4051 format!("{recv}.{}", b.surface())
4052 } else {
4053 format!("{recv}.{}({})", b.surface(), rest.join(", "))
4054 }
4055 }
4056 Expr::Field(b, f) => format!("{}.{f}", Self::render_expr(b)),
4057 Expr::Index(b, i) => format!("{}[{}]", Self::render_expr(b), Self::render_expr(i)),
4058 }
4059 }
4060
4061 fn parse_let_atom(&mut self) -> Result<String, ParseError> {
4062 let tok = self.current().clone();
4063
4064 match tok.ttype {
4065 TokenType::StringLit => {
4066 self.last_let_value_kind = "literal".to_string();
4067 self.advance();
4068 Ok(tok.value)
4069 }
4070 TokenType::Integer | TokenType::Float => {
4071 self.last_let_value_kind = "literal".to_string();
4072 self.advance();
4073 Ok(tok.value)
4074 }
4075 TokenType::Bool => {
4076 self.last_let_value_kind = "literal".to_string();
4077 self.advance();
4078 Ok(tok.value)
4079 }
4080 TokenType::Identifier => {
4081 self.last_let_value_kind = "reference".to_string();
4082 self.parse_dotted_identifier()
4083 }
4084 TokenType::LBracket => {
4085 self.last_let_value_kind = "literal".to_string();
4086 self.parse_let_list_literal()
4087 }
4088 _ => {
4089 if self.pos + 1 < self.tokens.len()
4091 && self.tokens[self.pos + 1].ttype == TokenType::Dot
4092 {
4093 self.last_let_value_kind = "reference".to_string();
4094 return self.parse_dotted_identifier();
4095 }
4096 Err(ParseError {
4097 message: format!(
4098 "Expected value expression, found {:?}('{}')",
4099 tok.ttype, tok.value
4100 ),
4101 line: tok.line,
4102 column: tok.column,
4103 ..Default::default()
4104 })
4105 }
4106 }
4107 }
4108
4109 fn parse_let_list_literal(&mut self) -> Result<String, ParseError> {
4110 self.consume(TokenType::LBracket)?;
4111 let mut items = Vec::new();
4112 if !self.check(TokenType::RBracket) {
4113 items.push(self.parse_let_value_expr()?);
4114 while self.check(TokenType::Comma) {
4115 self.advance();
4116 if self.check(TokenType::RBracket) {
4117 break; }
4119 items.push(self.parse_let_value_expr()?);
4120 }
4121 }
4122 self.consume(TokenType::RBracket)?;
4123 Ok(format!("[{}]", items.join(", ")))
4124 }
4125
4126 fn parse_return(&mut self) -> Result<ReturnStatement, ParseError> {
4129 let tok = self.consume(TokenType::Return)?;
4130 let loc = self.loc_of(&tok);
4131 let value = self.parse_let_value_expr()?;
4132 Ok(ReturnStatement {
4133 value_expr: value,
4134 loc,
4135 })
4136 }
4137
4138 fn parse_flow_step_simple(&mut self, _kw: &str) -> Result<(Loc, String), ParseError> {
4142 let tok = self.current().clone();
4143 self.advance(); let target = if self.at_declaration_start()
4145 || self.check(TokenType::RBrace)
4146 || self.check(TokenType::Eof)
4147 {
4148 String::new()
4149 } else {
4150 self.consume_any_ident_or_kw()?.value.clone()
4151 };
4152 if self.check(TokenType::LBrace) {
4154 self.skip_braced_block()?;
4155 }
4156 Ok((
4157 Loc {
4158 line: tok.line,
4159 column: tok.column,
4160 },
4161 target,
4162 ))
4163 }
4164
4165 fn parse_stream_block(&mut self) -> Result<StreamBlock, ParseError> {
4176 let tok = self.current().clone();
4177 let loc = self.loc_of(&tok);
4178 self.advance(); while !self.check(TokenType::LBrace)
4184 && !self.check(TokenType::RBrace)
4185 && !self.check(TokenType::Eof)
4186 && !self.at_declaration_start()
4187 {
4188 self.advance();
4189 }
4190
4191 let mut body = Vec::new();
4192 if self.check(TokenType::LBrace) {
4193 self.advance();
4194 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4195 body.push(self.parse_flow_step()?);
4196 }
4197 self.consume(TokenType::RBrace)?;
4198 }
4199
4200 Ok(StreamBlock { body, loc })
4201 }
4202
4203 fn parse_block_step(&mut self, _kw: &str) -> Result<Loc, ParseError> {
4204 let tok = self.current().clone();
4205 self.advance();
4206 while !self.check(TokenType::LBrace)
4208 && !self.check(TokenType::RBrace)
4209 && !self.check(TokenType::Eof)
4210 && !self.at_declaration_start()
4211 {
4212 self.advance();
4213 }
4214 if self.check(TokenType::LBrace) {
4215 self.skip_braced_block()?;
4216 }
4217 Ok(Loc {
4218 line: tok.line,
4219 column: tok.column,
4220 })
4221 }
4222
4223 fn parse_forge_step(&mut self) -> Result<ForgeBlock, ParseError> {
4230 let tok = self.consume(TokenType::Forge)?;
4231 let name = self.consume(TokenType::Identifier)?.value;
4232 let mut node = ForgeBlock {
4233 name,
4234 novelty: 0.5,
4235 depth: 1,
4236 branches: 1,
4237 loc: Loc { line: tok.line, column: tok.column },
4238 ..Default::default()
4239 };
4240 self.consume(TokenType::LParen)?;
4242 let arg = self.consume_any_ident_or_kw()?.value;
4243 self.consume(TokenType::Colon)?;
4244 if arg != "seed" {
4245 return Err(self.error(&format!(
4246 "forge '{}' expects `seed:` as its argument, found `{arg}`",
4247 node.name
4248 )));
4249 }
4250 node.seed = self.consume(TokenType::StringLit)?.value;
4251 self.consume(TokenType::RParen)?;
4252 self.consume(TokenType::Arrow)?;
4254 node.output_type = self.consume_any_ident_or_kw()?.value;
4255 self.consume(TokenType::LBrace)?;
4257 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4258 let field = self.consume_any_ident_or_kw()?.value;
4259 self.consume(TokenType::Colon)?;
4260 match field.as_str() {
4261 "mode" => node.mode = self.consume_any_ident_or_kw()?.value,
4262 "novelty" => node.novelty = self.consume_number()?,
4263 "depth" => {
4264 node.depth = self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4265 }
4266 "branches" => {
4267 node.branches =
4268 self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
4269 }
4270 "constraints" => node.constraints_ref = self.consume_any_ident_or_kw()?.value,
4271 other => {
4272 return Err(self.error(&format!("unknown forge field `{other}`")))
4273 }
4274 }
4275 if self.check(TokenType::Comma) {
4276 self.consume(TokenType::Comma)?;
4277 }
4278 }
4279 self.consume(TokenType::RBrace)?;
4280 Ok(node)
4281 }
4282
4283 fn parse_par_block(&mut self) -> Result<ParBlock, ParseError> {
4292 let tok = self.current().clone();
4293 self.advance(); self.consume(TokenType::LBrace)?;
4295 let mut branches: Vec<Vec<FlowStep>> = Vec::new();
4296 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4297 branches.push(vec![self.parse_flow_step()?]);
4298 }
4299 self.consume(TokenType::RBrace)?;
4300 Ok(ParBlock {
4301 branches,
4302 loc: Loc {
4303 line: tok.line,
4304 column: tok.column,
4305 },
4306 })
4307 }
4308
4309 fn parse_warden(&mut self) -> Result<WardenBlock, ParseError> {
4326 let tok = self.consume(TokenType::Warden)?;
4327 self.consume(TokenType::LParen)?;
4329 let target = self.consume_any_ident_or_kw()?.value;
4330 self.consume(TokenType::RParen)?;
4331 self.consume(TokenType::Within)?;
4333 let scope_ref = self.consume(TokenType::Identifier)?.value;
4334 let mut block = WardenBlock {
4335 target,
4336 scope_ref,
4337 body: Vec::new(),
4338 loc: Loc {
4339 line: tok.line,
4340 column: tok.column,
4341 },
4342 };
4343 self.consume(TokenType::LBrace)?;
4345 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4346 block.body.push(self.parse_flow_step()?);
4347 }
4348 self.consume(TokenType::RBrace)?;
4349 Ok(block)
4350 }
4351
4352 fn parse_scope(&mut self) -> Result<ScopeDefinition, ParseError> {
4357 let tok = self.consume(TokenType::Scope)?;
4358 let name = self.consume(TokenType::Identifier)?.value;
4359 let mut node = ScopeDefinition {
4360 name,
4361 loc: Loc {
4362 line: tok.line,
4363 column: tok.column,
4364 },
4365 ..Default::default()
4366 };
4367 self.consume(TokenType::LBrace)?;
4368 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4369 let key = self.consume_any_ident_or_kw()?.value;
4370 self.consume(TokenType::Colon)?;
4371 match key.as_str() {
4372 "targets" => {
4373 self.consume(TokenType::LBracket)?;
4374 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4375 let t = if self.check(TokenType::StringLit) {
4376 self.consume(TokenType::StringLit)?.value
4377 } else {
4378 self.consume_any_ident_or_kw()?.value
4379 };
4380 node.targets.push(t);
4381 if self.check(TokenType::Comma) {
4382 self.advance();
4383 }
4384 }
4385 self.consume(TokenType::RBracket)?;
4386 }
4387 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
4388 "approver" => {
4389 if self.current().value == "requires" {
4391 self.advance();
4392 }
4393 node.approver = self.consume(TokenType::StringLit)?.value;
4394 }
4395 other => {
4396 return Err(self.error(&format!(
4397 "unknown scope field `{other}` in scope `{}` — expected \
4398 `targets` / `depth` / `approver`",
4399 node.name
4400 )))
4401 }
4402 }
4403 if self.check(TokenType::Comma) {
4404 self.consume(TokenType::Comma)?;
4405 }
4406 }
4407 self.consume(TokenType::RBrace)?;
4408 Ok(node)
4409 }
4410
4411 fn parse_quant(&mut self) -> Result<QuantBlock, ParseError> {
4412 let tok = self.current().clone();
4413 self.advance(); let mut block = QuantBlock {
4416 encoding: None,
4417 observable: None,
4418 qubits: None,
4419 depth: None,
4420 bandwidth: None,
4421 reupload: None,
4422 effect: "quant_sim".to_string(),
4425 body: Vec::new(),
4426 loc: Loc {
4427 line: tok.line,
4428 column: tok.column,
4429 },
4430 };
4431
4432 if self.check(TokenType::LParen) {
4434 self.advance();
4435 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
4436 let key = self.consume_any_ident_or_kw()?.value;
4437 self.consume(TokenType::Colon)?;
4438 match key.as_str() {
4439 "encoding" => {
4440 block.encoding = Some(self.consume_any_ident_or_kw()?.value)
4441 }
4442 "observable" => {
4443 block.observable = Some(self.parse_dotted_identifier()?)
4444 }
4445 "qubits" => block.qubits = Some(self.consume_number()? as i64),
4446 "depth" => block.depth = Some(self.consume_number()? as i64),
4447 "bandwidth" => block.bandwidth = Some(self.consume_number()?),
4448 "reupload" => block.reupload = Some(self.consume_number()? as i64),
4450 "backend" => block.effect = self.consume_any_ident_or_kw()?.value,
4452 other => {
4453 return Err(ParseError {
4454 message: format!(
4455 "Unknown `quant` attribute `{other}` — expected one of \
4456 encoding, observable, qubits, depth, bandwidth, reupload, backend"
4457 ),
4458 line: self.current().line,
4459 column: self.current().column,
4460 ..Default::default()
4461 });
4462 }
4463 }
4464 if self.check(TokenType::Comma) {
4466 self.advance();
4467 }
4468 }
4469 self.consume(TokenType::RParen)?;
4470 }
4471
4472 self.consume(TokenType::LBrace)?;
4474 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4475 block.body.push(self.parse_flow_step()?);
4476 }
4477 self.consume(TokenType::RBrace)?;
4478
4479 Ok(block)
4480 }
4481
4482 fn parse_yield(&mut self) -> Result<YieldStatement, ParseError> {
4486 let tok = self.consume(TokenType::Yield)?;
4487 let loc = self.loc_of(&tok);
4488 self.last_let_value_kind = "literal".to_string();
4489 let value_expr = self.parse_let_value_expr()?;
4490 Ok(YieldStatement {
4491 value_expr,
4492 value_kind: self.last_let_value_kind.clone(),
4493 loc,
4494 })
4495 }
4496
4497 fn parse_compute_apply(&mut self) -> Result<ComputeApplyStep, ParseError> {
4504 let tok = self.current().clone();
4505 let loc = self.loc_of(&tok);
4506 self.advance(); let compute_name = self.consume_any_ident_or_kw()?.value.clone();
4508
4509 let mut arguments = Vec::new();
4510 if self.current().value == "on" {
4511 self.advance();
4512 loop {
4513 arguments.push(self.consume_any_ident_or_kw()?.value.clone());
4514 if self.check(TokenType::Comma) {
4515 self.advance();
4516 } else {
4517 break;
4518 }
4519 }
4520 }
4521
4522 let mut output_name = String::new();
4523 if self.check(TokenType::Arrow) {
4524 self.advance();
4525 output_name = self.consume_any_ident_or_kw()?.value.clone();
4526 }
4527
4528 Ok(ComputeApplyStep {
4529 compute_name,
4530 arguments,
4531 output_name,
4532 loc,
4533 })
4534 }
4535
4536 fn parse_apply_step(&mut self, _kw: &str) -> Result<(Loc, String, String, String), ParseError> {
4537 let tok = self.current().clone();
4538 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
4540 let mut target = String::new();
4541 let mut output_type = String::new();
4542 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4544 let next = self.current().clone();
4545 if next.value == "on" {
4546 self.advance();
4547 target = self.consume_any_ident_or_kw()?.value.clone();
4548 }
4549 }
4550 if self.check(TokenType::Arrow) {
4552 self.advance();
4553 output_type = self.consume_any_ident_or_kw()?.value.clone();
4554 }
4555 if self.check(TokenType::LBrace) {
4557 self.skip_braced_block()?;
4558 }
4559 Ok((
4560 Loc {
4561 line: tok.line,
4562 column: tok.column,
4563 },
4564 name,
4565 target,
4566 output_type,
4567 ))
4568 }
4569
4570 fn parse_weave_step(&mut self) -> Result<FlowStep, ParseError> {
4571 let tok = self.current().clone();
4572 self.advance();
4573 let mut node = WeaveStep {
4574 sources: Vec::new(),
4575 target: String::new(),
4576 format_type: String::new(),
4577 priority: Vec::new(),
4578 style: String::new(),
4579 loc: Loc {
4580 line: tok.line,
4581 column: tok.column,
4582 },
4583 };
4584 if self.check(TokenType::LBrace) {
4585 self.advance();
4586 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4587 let f = self.current().value.clone();
4588 self.advance();
4589 if self.check(TokenType::Colon) {
4590 self.advance();
4591 match f.as_str() {
4592 "sources" => node.sources = self.parse_bracketed_identifiers()?,
4593 "target" => node.target = self.consume_any_ident_or_kw()?.value.clone(),
4594 "format" => {
4595 node.format_type = self.consume_any_ident_or_kw()?.value.clone()
4596 }
4597 "priority" => node.priority = self.parse_bracketed_identifiers()?,
4598 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
4599 _ => self.skip_value(),
4600 }
4601 }
4602 }
4603 if self.check(TokenType::RBrace) {
4604 self.advance();
4605 }
4606 }
4607 Ok(FlowStep::Weave(node))
4608 }
4609
4610 fn parse_use_step(&mut self) -> Result<FlowStep, ParseError> {
4611 let tok = self.current().clone();
4612 self.advance();
4613 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
4614 let args = if self.check(TokenType::LParen) {
4625 UseArgs::Named(self.parse_named_arg_list()?)
4626 } else {
4627 let mut argument = String::new();
4628 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4629 let next = self.current().clone();
4630 if next.value == "on" {
4631 self.advance();
4632 argument = self.consume_any_ident_or_kw()?.value.clone();
4633 }
4634 }
4635 UseArgs::LegacyPositional(argument)
4636 };
4637 if self.check(TokenType::LBrace) {
4638 self.skip_braced_block()?;
4639 }
4640 Ok(FlowStep::UseTool(UseToolStep {
4641 tool_name,
4642 args,
4643 loc: Loc {
4644 line: tok.line,
4645 column: tok.column,
4646 },
4647 }))
4648 }
4649
4650 fn parse_named_arg_list(&mut self) -> Result<Vec<(String, String, String)>, ParseError> {
4656 self.consume(TokenType::LParen)?;
4657 let mut args = Vec::new();
4658 while !self.check(TokenType::RParen) {
4659 let name = self.consume_any_ident_or_kw()?.value;
4662 self.consume(TokenType::Assign)?;
4663 let value = self.parse_let_atom()?;
4664 let value_kind = self.last_let_value_kind.clone();
4668 args.push((name, value, value_kind));
4669 if self.check(TokenType::Comma) {
4670 self.advance();
4671 } else {
4672 break;
4673 }
4674 }
4675 self.consume(TokenType::RParen)?;
4676 Ok(args)
4677 }
4678
4679 fn parse_remember_step(&mut self) -> Result<FlowStep, ParseError> {
4680 let tok = self.current().clone();
4681 self.advance();
4682 let expr = self.consume_any_ident_or_kw()?.value.clone();
4683 let mut mem = String::new();
4684 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4685 let next = self.current().clone();
4686 if next.value == "in" || next.ttype == TokenType::In {
4687 self.advance();
4688 mem = self.consume_any_ident_or_kw()?.value.clone();
4689 }
4690 }
4691 Ok(FlowStep::Remember(RememberStep {
4692 expression: expr,
4693 memory_target: mem,
4694 loc: Loc {
4695 line: tok.line,
4696 column: tok.column,
4697 },
4698 }))
4699 }
4700
4701 fn parse_recall_step(&mut self) -> Result<FlowStep, ParseError> {
4702 let tok = self.current().clone();
4703 self.advance();
4704 let query = if self.check(TokenType::StringLit) {
4705 self.consume(TokenType::StringLit)?.value.clone()
4706 } else {
4707 self.consume_any_ident_or_kw()?.value.clone()
4708 };
4709 let mut mem = String::new();
4710 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4711 let next = self.current().clone();
4712 if next.value == "from" || next.ttype == TokenType::From {
4713 self.advance();
4714 mem = self.consume_any_ident_or_kw()?.value.clone();
4715 }
4716 }
4717 Ok(FlowStep::Recall(RecallStep {
4718 query,
4719 memory_source: mem,
4720 loc: Loc {
4721 line: tok.line,
4722 column: tok.column,
4723 },
4724 }))
4725 }
4726
4727 fn parse_hibernate_step(&mut self) -> Result<FlowStep, ParseError> {
4728 let tok = self.current().clone();
4729 self.advance();
4730 let mut event = String::new();
4731 let mut timeout = String::new();
4732 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4733 event = self.consume_any_ident_or_kw()?.value.clone();
4734 }
4735 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4736 let next = self.current().clone();
4737 if next.ttype == TokenType::Duration {
4738 self.advance();
4739 timeout = next.value.clone();
4740 }
4741 }
4742 Ok(FlowStep::Hibernate(HibernateStep {
4743 event_name: event,
4744 timeout,
4745 loc: Loc {
4746 line: tok.line,
4747 column: tok.column,
4748 },
4749 }))
4750 }
4751
4752 fn parse_grad_step(&mut self) -> Result<FlowStep, ParseError> {
4761 let tok = self.current().clone();
4762 self.advance();
4763 let target = self.consume_any_ident_or_kw()?.value.clone();
4764 let mut wrt: Vec<String> = Vec::new();
4765 let mut output = String::new();
4766 if !self.at_declaration_start() && self.current().value == "wrt" {
4767 self.advance();
4768 if self.check(TokenType::LBracket) {
4769 wrt = self.parse_bracketed_identifiers()?;
4770 } else {
4771 wrt.push(self.consume_any_ident_or_kw()?.value.clone());
4772 }
4773 }
4774 if !self.at_declaration_start() && self.current().value == "as" {
4775 self.advance();
4776 output = self.consume_any_ident_or_kw()?.value.clone();
4777 }
4778 Ok(FlowStep::Grad(GradStep {
4779 target,
4780 wrt,
4781 output,
4782 loc: Loc {
4783 line: tok.line,
4784 column: tok.column,
4785 },
4786 }))
4787 }
4788
4789 fn parse_focus_step(&mut self) -> Result<FlowStep, ParseError> {
4790 let tok = self.current().clone();
4791 self.advance();
4792 let expression = if self.at_declaration_start()
4793 || self.check(TokenType::RBrace)
4794 || self.check(TokenType::Eof)
4795 {
4796 String::new()
4797 } else {
4798 self.consume_any_ident_or_kw()?.value.clone()
4799 };
4800 let mut where_expr = String::new();
4801 let mut select: Vec<String> = Vec::new();
4802 let mut output = String::new();
4803 if self.check(TokenType::LBrace) {
4804 self.advance();
4805 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4806 if self.check(TokenType::Comma) {
4807 self.advance();
4808 continue;
4809 }
4810 let f = self.current().value.clone();
4811 self.advance();
4812 if self.check(TokenType::Colon) {
4813 self.advance();
4814 match f.as_str() {
4815 "where" => {
4816 where_expr = self.consume(TokenType::StringLit)?.value.clone()
4817 }
4818 "select" => select = self.parse_bracketed_identifiers()?,
4819 "as" | "alias" => {
4820 output = self.consume_any_ident_or_kw()?.value.clone()
4821 }
4822 _ => self.skip_value(),
4823 }
4824 }
4825 }
4826 if self.check(TokenType::RBrace) {
4827 self.advance();
4828 }
4829 }
4830 Ok(FlowStep::Focus(FocusStep {
4831 expression,
4832 where_expr,
4833 select,
4834 output,
4835 loc: Loc {
4836 line: tok.line,
4837 column: tok.column,
4838 },
4839 }))
4840 }
4841
4842 fn parse_associate_step(&mut self) -> Result<FlowStep, ParseError> {
4843 let tok = self.current().clone();
4844 self.advance();
4845 let left = self.consume_any_ident_or_kw()?.value.clone();
4846 let mut right = String::new();
4847 let mut using = String::new();
4848 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4849 right = self.consume_any_ident_or_kw()?.value.clone();
4850 }
4851 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4852 let next = self.current().clone();
4853 if next.value == "using" {
4854 self.advance();
4855 using = self.consume_any_ident_or_kw()?.value.clone();
4856 }
4857 }
4858 let mut output = String::new();
4859 if self.check(TokenType::LBrace) {
4860 self.advance();
4861 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4862 let f = self.current().value.clone();
4863 self.advance();
4864 if self.check(TokenType::Colon) {
4865 self.advance();
4866 match f.as_str() {
4867 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
4868 _ => self.skip_value(),
4869 }
4870 }
4871 }
4872 if self.check(TokenType::RBrace) {
4873 self.advance();
4874 }
4875 }
4876 Ok(FlowStep::Associate(AssociateStep {
4877 left,
4878 right,
4879 using_field: using,
4880 output,
4881 loc: Loc {
4882 line: tok.line,
4883 column: tok.column,
4884 },
4885 }))
4886 }
4887
4888 fn parse_aggregate_step(&mut self) -> Result<FlowStep, ParseError> {
4889 let tok = self.current().clone();
4890 self.advance();
4891 let target = self.consume_any_ident_or_kw()?.value.clone();
4892 let mut group_by = Vec::new();
4893 let mut alias = String::new();
4894 let mut compute: Vec<String> = Vec::new();
4895 let mut where_expr = String::new();
4896 if self.check(TokenType::LBrace) {
4897 self.advance();
4898 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4899 let f = self.current().value.clone();
4900 self.advance();
4901 if self.check(TokenType::Colon) {
4902 self.advance();
4903 match f.as_str() {
4904 "group_by" => group_by = self.parse_bracketed_identifiers()?,
4905 "alias" | "as" => alias = self.consume_any_ident_or_kw()?.value.clone(),
4906 "compute" => compute = self.parse_bracketed_aggregates()?,
4909 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
4911 _ => self.skip_value(),
4912 }
4913 }
4914 }
4915 if self.check(TokenType::RBrace) {
4916 self.advance();
4917 }
4918 }
4919 Ok(FlowStep::Aggregate(AggregateStep {
4920 target,
4921 group_by,
4922 alias,
4923 compute,
4924 where_expr,
4925 loc: Loc {
4926 line: tok.line,
4927 column: tok.column,
4928 },
4929 }))
4930 }
4931
4932 fn parse_explore_step(&mut self) -> Result<FlowStep, ParseError> {
4933 let tok = self.current().clone();
4934 self.advance();
4935 let target = self.consume_any_ident_or_kw()?.value.clone();
4936 let mut limit = None;
4937 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
4938 if self.current().ttype == TokenType::Integer {
4939 limit = self.current().value.parse::<i64>().ok();
4940 self.advance();
4941 }
4942 }
4943 let mut output = String::new();
4944 if self.check(TokenType::LBrace) {
4945 self.advance();
4946 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
4947 let f = self.current().value.clone();
4948 self.advance();
4949 if self.check(TokenType::Colon) {
4950 self.advance();
4951 match f.as_str() {
4952 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
4953 _ => self.skip_value(),
4954 }
4955 }
4956 }
4957 if self.check(TokenType::RBrace) {
4958 self.advance();
4959 }
4960 }
4961 Ok(FlowStep::ExploreStep(ExploreStepNode {
4962 target,
4963 limit,
4964 output,
4965 loc: Loc {
4966 line: tok.line,
4967 column: tok.column,
4968 },
4969 }))
4970 }
4971
4972 fn parse_bracketed_aggregates(&mut self) -> Result<Vec<String>, ParseError> {
4975 let mut out = Vec::new();
4976 self.consume(TokenType::LBracket)?;
4977 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
4978 let name = self.consume_any_ident_or_kw()?.value.clone();
4979 if self.check(TokenType::LParen) {
4980 self.advance();
4981 let col = self.consume_any_ident_or_kw()?.value.clone();
4982 self.consume(TokenType::RParen)?;
4983 out.push(format!("{name}({col})"));
4984 } else {
4985 out.push(name);
4986 }
4987 if self.check(TokenType::Comma) {
4988 self.advance();
4989 }
4990 }
4991 self.consume(TokenType::RBracket)?;
4992 Ok(out)
4993 }
4994
4995 fn parse_ingest_step(&mut self) -> Result<FlowStep, ParseError> {
5010 let tok = self.current().clone();
5011 self.advance();
5012 let source = self.consume_any_ident_or_kw()?.value.clone();
5013 let mut target = String::new();
5014 let mut format = String::new();
5015 let mut max_bytes: Option<u64> = None;
5016 let mut max_rows: Option<u64> = None;
5017 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5018 let next = self.current().clone();
5019 if next.value == "into" || next.ttype == TokenType::Into {
5020 self.advance();
5021 target = self.consume_any_ident_or_kw()?.value.clone();
5022 }
5023 }
5024 if self.check(TokenType::LBrace) {
5025 self.consume(TokenType::LBrace)?;
5026 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5027 if self.check(TokenType::Comma) {
5029 self.advance();
5030 continue;
5031 }
5032 let entry = self.current().clone();
5033 match entry.value.as_str() {
5034 "format" => {
5035 self.advance();
5036 self.consume(TokenType::Colon)?;
5037 format = self.consume_any_ident_or_kw()?.value.clone();
5038 }
5039 "limits" => {
5040 self.advance();
5041 self.consume(TokenType::LBrace)?;
5042 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5043 let bound = self.current().clone();
5044 self.advance();
5045 self.consume(TokenType::Colon)?;
5046 let num_tok = self.consume(TokenType::Integer)?.clone();
5047 let value = num_tok.value.parse::<u64>().map_err(|_| ParseError {
5048 message: format!(
5049 "ingest `limits` bound `{}` must be a non-negative \
5050 integer byte/row count, got `{}`.",
5051 bound.value, num_tok.value
5052 ),
5053 line: num_tok.line,
5054 column: num_tok.column,
5055 ..Default::default()
5056 })?;
5057 match bound.value.as_str() {
5058 "max_bytes" => max_bytes = Some(value),
5059 "max_rows" => max_rows = Some(value),
5060 other => {
5061 return Err(ParseError {
5062 message: format!(
5063 "Unknown ingest limit `{other}`. The closed \
5064 limits grammar is `max_bytes: <N>` and \
5065 `max_rows: <N>` — bounds enforced on the raw \
5066 stream BEFORE parsing (§100).",
5067 ),
5068 line: bound.line,
5069 column: bound.column,
5070 ..Default::default()
5071 });
5072 }
5073 }
5074 if self.check(TokenType::Comma) {
5075 self.advance();
5076 }
5077 }
5078 self.consume(TokenType::RBrace)?;
5079 }
5080 other => {
5081 return Err(ParseError {
5082 message: format!(
5083 "Unknown entry `{other}` in ingest body. The closed \
5084 grammar is `format: csv|json` and \
5085 `limits {{ max_bytes: <N>, max_rows: <N> }}`.",
5086 ),
5087 line: entry.line,
5088 column: entry.column,
5089 ..Default::default()
5090 });
5091 }
5092 }
5093 }
5094 self.consume(TokenType::RBrace)?;
5095 }
5096 Ok(FlowStep::Ingest(IngestStep {
5097 source,
5098 target,
5099 format,
5100 max_bytes,
5101 max_rows,
5102 loc: Loc {
5103 line: tok.line,
5104 column: tok.column,
5105 },
5106 }))
5107 }
5108
5109 fn parse_navigate_step(&mut self) -> Result<FlowStep, ParseError> {
5110 let tok = self.current().clone();
5111 self.advance();
5112 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
5113 let mut node = NavigateStep {
5114 pix_name,
5115 corpus_name: String::new(),
5116 query_expr: String::new(),
5117 trail_enabled: false,
5118 output_name: String::new(),
5119 seed: String::new(),
5120 budget: None,
5121 where_expr: String::new(),
5122 loc: Loc {
5123 line: tok.line,
5124 column: tok.column,
5125 },
5126 };
5127 if self.check(TokenType::LBrace) {
5128 self.advance();
5129 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5130 let f = self.current().value.clone();
5131 self.advance();
5132 if self.check(TokenType::Colon) {
5133 self.advance();
5134 match f.as_str() {
5135 "corpus" => {
5136 node.corpus_name = self.consume_any_ident_or_kw()?.value.clone()
5137 }
5138 "query" => {
5139 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
5140 }
5141 "trail" => {
5142 node.trail_enabled = self.consume_any_ident_or_kw()?.value == "true"
5143 }
5144 "output" | "as" => {
5145 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5146 }
5147 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
5149 "budget" => node.budget = self.parse_optional_int(),
5150 "where" => {
5158 node.where_expr = self.consume(TokenType::StringLit)?.value.clone()
5159 }
5160 _ => self.skip_value(),
5161 }
5162 }
5163 }
5164 if self.check(TokenType::RBrace) {
5165 self.advance();
5166 }
5167 }
5168 Ok(FlowStep::Navigate(node))
5169 }
5170
5171 fn parse_drill_step(&mut self) -> Result<FlowStep, ParseError> {
5172 let tok = self.current().clone();
5173 self.advance();
5174 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
5175 let mut node = DrillStep {
5176 pix_name,
5177 subtree_path: String::new(),
5178 query_expr: String::new(),
5179 output_name: String::new(),
5180 loc: Loc {
5181 line: tok.line,
5182 column: tok.column,
5183 },
5184 };
5185 if self.check(TokenType::LBrace) {
5186 self.advance();
5187 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5188 let f = self.current().value.clone();
5189 self.advance();
5190 if self.check(TokenType::Colon) {
5191 self.advance();
5192 match f.as_str() {
5193 "subtree" | "path" => {
5194 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
5195 }
5196 "query" => {
5197 node.query_expr = self.consume(TokenType::StringLit)?.value.clone()
5198 }
5199 "output" | "as" => {
5200 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
5201 }
5202 _ => self.skip_value(),
5203 }
5204 }
5205 }
5206 if self.check(TokenType::RBrace) {
5207 self.advance();
5208 }
5209 }
5210 Ok(FlowStep::Drill(node))
5211 }
5212
5213 fn parse_corroborate_step(&mut self) -> Result<FlowStep, ParseError> {
5214 let tok = self.current().clone();
5215 self.advance();
5216 let nav_ref = self.consume_any_ident_or_kw()?.value.clone();
5217 let mut output = String::new();
5218 if self.check(TokenType::Arrow) {
5219 self.advance();
5220 output = self.consume_any_ident_or_kw()?.value.clone();
5221 }
5222 Ok(FlowStep::Corroborate(CorroborateStep {
5223 navigate_ref: nav_ref,
5224 output_name: output,
5225 loc: Loc {
5226 line: tok.line,
5227 column: tok.column,
5228 },
5229 }))
5230 }
5231
5232 fn parse_listen_step(&mut self) -> Result<FlowStep, ParseError> {
5233 let tok = self.current().clone();
5234 self.advance();
5235 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
5239 (self.consume(TokenType::StringLit)?.value.clone(), false)
5240 } else {
5241 (self.consume_any_ident_or_kw()?.value.clone(), true)
5242 };
5243 let mut alias = String::new();
5244 if !self.at_declaration_start()
5245 && !self.check(TokenType::RBrace)
5246 && !self.check(TokenType::LBrace)
5247 {
5248 let next = self.current().clone();
5249 if next.value == "as" || next.ttype == TokenType::As {
5250 self.advance();
5251 alias = self.consume_any_ident_or_kw()?.value.clone();
5252 }
5253 }
5254 let body = self.parse_listener_body()?;
5258 Ok(FlowStep::Listen(ListenStep {
5259 channel,
5260 channel_is_ref,
5261 event_alias: alias,
5262 body,
5263 loc: Loc {
5264 line: tok.line,
5265 column: tok.column,
5266 },
5267 }))
5268 }
5269
5270 fn parse_listener_body(&mut self) -> Result<Vec<FlowStep>, ParseError> {
5275 let mut body = Vec::new();
5276 if self.check(TokenType::LBrace) {
5277 self.advance(); while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5279 body.push(self.parse_flow_step()?);
5280 }
5281 self.consume(TokenType::RBrace)?;
5282 }
5283 Ok(body)
5284 }
5285
5286 fn parse_retrieve_step(&mut self) -> Result<FlowStep, ParseError> {
5287 let tok = self.current().clone();
5288 self.advance();
5289 let store = self.consume_any_ident_or_kw()?.value.clone();
5290 let mut where_expr = String::new();
5291 let mut alias = String::new();
5292 let mut order_by = String::new();
5293 let mut limit_expr = String::new();
5294 let mut aggregate = String::new();
5295 let mut group_by = String::new();
5296 let mut cache = String::new();
5297 if self.check(TokenType::LBrace) {
5298 self.advance();
5299 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5300 let f = self.current().value.clone();
5301 self.advance();
5302 if self.check(TokenType::Colon) {
5303 self.advance();
5304 match f.as_str() {
5305 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
5306 "as" | "alias" => alias = self.consume_any_ident_or_kw()?.value.clone(),
5307 "order_by" => {
5310 order_by = self.consume(TokenType::StringLit)?.value.clone()
5311 }
5312 "limit" => {
5317 let t = self.current().clone();
5318 match t.ttype {
5319 TokenType::Integer | TokenType::StringLit => {
5320 limit_expr = t.value.clone();
5321 self.advance();
5322 }
5323 _ => self.skip_value(),
5324 }
5325 }
5326 "aggregate" => {
5333 aggregate = self.consume(TokenType::StringLit)?.value.clone()
5334 }
5335 "group_by" => {
5336 group_by = self.consume(TokenType::StringLit)?.value.clone()
5337 }
5338 "cache" => cache = self.consume_any_ident_or_kw()?.value.clone(),
5344 _ => self.skip_value(),
5345 }
5346 }
5347 }
5348 if self.check(TokenType::RBrace) {
5349 self.advance();
5350 }
5351 }
5352 Ok(FlowStep::Retrieve(RetrieveStep {
5353 store_name: store,
5354 where_expr,
5355 alias,
5356 order_by,
5357 limit_expr,
5358 aggregate,
5359 group_by,
5360 cache,
5361 loc: Loc {
5362 line: tok.line,
5363 column: tok.column,
5364 },
5365 }))
5366 }
5367
5368 fn parse_store_where_step(
5381 &mut self,
5382 ) -> Result<(Loc, String, String), ParseError> {
5383 let tok = self.current().clone();
5384 self.advance(); let store = if self.at_declaration_start()
5386 || self.check(TokenType::RBrace)
5387 || self.check(TokenType::Eof)
5388 {
5389 String::new()
5390 } else {
5391 self.consume_any_ident_or_kw()?.value.clone()
5392 };
5393 let mut where_expr = String::new();
5394 if self.check(TokenType::LBrace) {
5395 self.advance();
5396 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5397 let field = self.current().value.clone();
5398 self.advance();
5399 if self.check(TokenType::Colon) {
5400 self.advance();
5401 match field.as_str() {
5402 "where" => {
5403 where_expr =
5404 self.consume(TokenType::StringLit)?.value.clone()
5405 }
5406 _ => self.skip_value(),
5407 }
5408 }
5409 }
5410 if self.check(TokenType::RBrace) {
5411 self.advance();
5412 }
5413 }
5414 Ok((
5415 Loc {
5416 line: tok.line,
5417 column: tok.column,
5418 },
5419 store,
5420 where_expr,
5421 ))
5422 }
5423
5424 fn parse_persist_step(&mut self) -> Result<FlowStep, ParseError> {
5443 let tok = self.current().clone();
5444 self.advance(); if self.current().value == "into" && !self.check(TokenType::LBrace) {
5448 self.advance();
5449 }
5450 let store = if self.at_declaration_start()
5451 || self.check(TokenType::LBrace)
5452 || self.check(TokenType::RBrace)
5453 || self.check(TokenType::Eof)
5454 {
5455 String::new()
5456 } else {
5457 self.consume_any_ident_or_kw()?.value.clone()
5458 };
5459 let mut fields: Vec<(String, String)> = Vec::new();
5460 if self.check(TokenType::LBrace) {
5461 self.advance();
5462 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5463 let col = self.current().value.clone();
5464 self.advance();
5465 if self.check(TokenType::Colon) {
5466 self.advance();
5467 let value = if self.check(TokenType::StringLit) {
5468 self.consume(TokenType::StringLit)?.value.clone()
5469 } else if self.check(TokenType::RBrace)
5470 || self.check(TokenType::Eof)
5471 || self.check(TokenType::Colon)
5472 {
5473 String::new()
5474 } else {
5475 let v = self.current().clone();
5476 self.advance();
5477 v.value.clone()
5478 };
5479 fields.push((col, value));
5480 }
5481 }
5482 if self.check(TokenType::RBrace) {
5483 self.advance();
5484 }
5485 }
5486 Ok(FlowStep::Persist(PersistStep {
5487 store_name: store,
5488 fields,
5489 loc: Loc {
5490 line: tok.line,
5491 column: tok.column,
5492 },
5493 }))
5494 }
5495
5496 fn parse_mutate_step(&mut self) -> Result<FlowStep, ParseError> {
5510 let tok = self.current().clone();
5511 self.advance(); let store = if self.at_declaration_start()
5513 || self.check(TokenType::LBrace)
5514 || self.check(TokenType::RBrace)
5515 || self.check(TokenType::Eof)
5516 {
5517 String::new()
5518 } else {
5519 self.consume_any_ident_or_kw()?.value.clone()
5520 };
5521 let mut where_expr = String::new();
5522 let mut fields: Vec<(String, String)> = Vec::new();
5523 if self.check(TokenType::LBrace) {
5524 self.advance();
5525 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5526 let key = self.current().value.clone();
5527 self.advance();
5528 if self.check(TokenType::Colon) {
5529 self.advance();
5530 if key == "where" {
5531 where_expr =
5532 self.consume(TokenType::StringLit)?.value.clone();
5533 } else {
5534 let value = if self.check(TokenType::StringLit) {
5535 self.consume(TokenType::StringLit)?.value.clone()
5536 } else if self.check(TokenType::RBrace)
5537 || self.check(TokenType::Eof)
5538 || self.check(TokenType::Colon)
5539 {
5540 String::new()
5541 } else {
5542 let v = self.current().clone();
5543 self.advance();
5544 v.value.clone()
5545 };
5546 fields.push((key, value));
5547 }
5548 }
5549 }
5550 if self.check(TokenType::RBrace) {
5551 self.advance();
5552 }
5553 }
5554 Ok(FlowStep::Mutate(MutateStep {
5555 store_name: store,
5556 where_expr,
5557 fields,
5558 loc: Loc {
5559 line: tok.line,
5560 column: tok.column,
5561 },
5562 }))
5563 }
5564
5565 fn parse_agent(&mut self) -> Result<AgentDefinition, ParseError> {
5568 let tok = self.consume(TokenType::Agent)?;
5569 let name = self.consume(TokenType::Identifier)?.value;
5570 let mut node = AgentDefinition {
5571 name,
5572 goal: String::new(),
5573 tools: Vec::new(),
5574 memory_ref: String::new(),
5575 strategy: String::new(),
5576 on_stuck: String::new(),
5577 shield_ref: String::new(),
5578 max_iterations: None,
5579 max_tokens: None,
5580 max_time: String::new(),
5581 max_cost: None,
5582 loc: Loc {
5583 line: tok.line,
5584 column: tok.column,
5585 },
5586 leading_trivia: Vec::new(),
5587 trailing_trivia: Vec::new(),
5588 };
5589 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
5591 self.advance();
5592 }
5593 self.consume(TokenType::LBrace)?;
5594 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5595 let field = self.current().clone();
5596 let field_name = field.value.clone();
5597 self.advance();
5598 if self.check(TokenType::Colon) {
5599 self.advance();
5600 match field_name.as_str() {
5601 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
5602 "tools" => node.tools = self.parse_bracketed_identifiers()?,
5603 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
5604 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
5605 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
5606 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
5607 "max_iterations" => node.max_iterations = self.parse_optional_int(),
5608 "max_tokens" => node.max_tokens = self.parse_optional_int(),
5609 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
5610 "max_cost" => node.max_cost = self.parse_optional_float(),
5611 _ => self.skip_value(),
5612 }
5613 } else if self.check(TokenType::LBrace) {
5614 self.skip_braced_block()?;
5615 }
5616 }
5617 self.consume(TokenType::RBrace)?;
5618 Ok(node)
5619 }
5620
5621 fn parse_extension(&mut self) -> Result<ExtensionDefinition, ParseError> {
5626 let tok = self.consume(TokenType::Extension)?;
5627 let name = self.consume(TokenType::Identifier)?.value;
5628 let mut node = ExtensionDefinition {
5629 name,
5630 category: String::new(),
5631 members: Vec::new(),
5632 loc: Loc {
5633 line: tok.line,
5634 column: tok.column,
5635 },
5636 leading_trivia: Vec::new(),
5637 trailing_trivia: Vec::new(),
5638 };
5639 self.consume(TokenType::LBrace)?;
5640 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5641 let field_name = self.current().value.clone();
5642 self.advance();
5643 if self.check(TokenType::Colon) {
5644 self.advance();
5645 match field_name.as_str() {
5646 "category" => {
5647 node.category = self.consume_any_ident_or_kw()?.value.clone()
5648 }
5649 "members" => node.members = self.parse_extension_members()?,
5650 _ => self.skip_value(),
5651 }
5652 } else if self.check(TokenType::LBrace) {
5653 self.skip_braced_block()?;
5654 }
5655 }
5656 self.consume(TokenType::RBrace)?;
5657 Ok(node)
5658 }
5659
5660 fn parse_extension_members(&mut self) -> Result<Vec<ExtensionMember>, ParseError> {
5664 let mut members = Vec::new();
5665 self.consume(TokenType::LBracket)?;
5666 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5667 let name_tok = self.consume(TokenType::StringLit)?;
5668 let mut member = ExtensionMember {
5669 name: name_tok.value.clone(),
5670 semantics: None,
5671 default_confidence: None,
5672 loc: Loc {
5673 line: name_tok.line,
5674 column: name_tok.column,
5675 },
5676 };
5677 if self.check(TokenType::Colon) {
5679 self.advance();
5680 self.consume(TokenType::LBrace)?;
5681 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5682 let mkey = self.current().value.clone();
5683 self.advance();
5684 if self.check(TokenType::Colon) {
5685 self.advance();
5686 match mkey.as_str() {
5687 "semantics" => {
5688 member.semantics =
5689 Some(self.consume(TokenType::StringLit)?.value.clone())
5690 }
5691 "default_confidence" => {
5692 member.default_confidence = self.parse_optional_float()
5693 }
5694 _ => self.skip_value(),
5695 }
5696 }
5697 if self.check(TokenType::Comma) {
5698 self.advance();
5699 }
5700 }
5701 self.consume(TokenType::RBrace)?;
5702 }
5703 members.push(member);
5704 if self.check(TokenType::Comma) {
5705 self.advance();
5706 }
5707 }
5708 self.consume(TokenType::RBracket)?;
5709 Ok(members)
5710 }
5711
5712 fn parse_window(&mut self) -> Result<WindowDefinition, ParseError> {
5715 let tok = self.consume(TokenType::Window)?;
5716 let name = self.consume(TokenType::Identifier)?.value;
5717 let mut node = WindowDefinition {
5718 name,
5719 timezone: String::new(),
5720 allow: Vec::new(),
5721 exclude: Vec::new(),
5722 on_outside: String::new(),
5723 loc: Loc {
5724 line: tok.line,
5725 column: tok.column,
5726 },
5727 leading_trivia: Vec::new(),
5728 trailing_trivia: Vec::new(),
5729 };
5730 self.consume(TokenType::LBrace)?;
5731 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5732 let field_name = self.consume_any_ident_or_kw()?.value;
5733 self.consume(TokenType::Colon)?;
5734 match field_name.as_str() {
5735 "timezone" => node.timezone = self.consume(TokenType::StringLit)?.value,
5736 "allow" => node.allow = self.parse_window_allow()?,
5737 "exclude" => node.exclude = self.parse_window_exclude()?,
5738 "on_outside" => node.on_outside = self.consume_any_ident_or_kw()?.value,
5739 _ => self.skip_value(),
5740 }
5741 }
5742 self.consume(TokenType::RBrace)?;
5743 Ok(node)
5744 }
5745
5746 fn parse_window_allow(&mut self) -> Result<Vec<WindowSpan>, ParseError> {
5748 self.consume(TokenType::LBracket)?;
5749 let mut spans = Vec::new();
5750 if !self.check(TokenType::RBracket) {
5751 spans.push(self.parse_window_span()?);
5752 while self.check(TokenType::Comma) {
5753 self.advance();
5754 if self.check(TokenType::RBracket) {
5755 break; }
5757 spans.push(self.parse_window_span()?);
5758 }
5759 }
5760 self.consume(TokenType::RBracket)?;
5761 Ok(spans)
5762 }
5763
5764 fn parse_window_exclude(&mut self) -> Result<Vec<String>, ParseError> {
5768 self.consume(TokenType::LBracket)?;
5769 let mut dates = Vec::new();
5770 if !self.check(TokenType::RBracket) {
5771 dates.push(self.consume(TokenType::StringLit)?.value);
5772 while self.check(TokenType::Comma) {
5773 self.advance();
5774 if self.check(TokenType::RBracket) {
5775 break; }
5777 dates.push(self.consume(TokenType::StringLit)?.value);
5778 }
5779 }
5780 self.consume(TokenType::RBracket)?;
5781 Ok(dates)
5782 }
5783
5784 fn parse_window_span(&mut self) -> Result<WindowSpan, ParseError> {
5786 let tok = self.consume(TokenType::LBrace)?;
5787 let mut span = WindowSpan {
5788 day_start: String::new(),
5789 day_end: String::new(),
5790 hour_start: 0,
5791 hour_end: 0,
5792 loc: Loc {
5793 line: tok.line,
5794 column: tok.column,
5795 },
5796 };
5797 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5798 let field = self.consume_any_ident_or_kw()?.value;
5799 self.consume(TokenType::Colon)?;
5800 match field.as_str() {
5801 "days" => {
5802 span.day_start = self.consume_any_ident_or_kw()?.value;
5803 self.consume(TokenType::DotDot)?;
5804 span.day_end = self.consume_any_ident_or_kw()?.value;
5805 }
5806 "hours" => {
5807 span.hour_start = self.consume_number()? as i64;
5808 self.consume(TokenType::DotDot)?;
5809 span.hour_end = self.consume_number()? as i64;
5810 }
5811 _ => self.skip_value(),
5812 }
5813 if self.check(TokenType::Comma) {
5814 self.advance();
5815 }
5816 }
5817 self.consume(TokenType::RBrace)?;
5818 Ok(span)
5819 }
5820
5821 fn parse_shield(&mut self) -> Result<ShieldDefinition, ParseError> {
5822 let tok = self.consume(TokenType::Shield)?;
5823 let name = self.consume(TokenType::Identifier)?.value;
5824 let mut node = ShieldDefinition {
5825 name,
5826 scan: Vec::new(),
5827 strategy: String::new(),
5828 on_breach: String::new(),
5829 severity: String::new(),
5830 quarantine: String::new(),
5831 max_retries: None,
5832 confidence_threshold: None,
5833 allow_tools: Vec::new(),
5834 deny_tools: Vec::new(),
5835 sandbox: None,
5836 redact: Vec::new(),
5837 log: String::new(),
5838 deflect_message: String::new(),
5839 taint: String::new(),
5840 compliance: Vec::new(),
5841 sign: String::new(),
5842 unknown_fields: Vec::new(),
5843 loc: Loc {
5844 line: tok.line,
5845 column: tok.column,
5846 },
5847 leading_trivia: Vec::new(),
5848 trailing_trivia: Vec::new(),
5849 };
5850 self.consume(TokenType::LBrace)?;
5851 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5852 let field_name = self.current().value.clone();
5853 let field_loc = Loc {
5854 line: self.current().line,
5855 column: self.current().column,
5856 };
5857 self.advance();
5858 if self.check(TokenType::Colon) {
5859 self.advance();
5860 match field_name.as_str() {
5861 "scan" => node.scan = self.parse_bracketed_identifiers()?,
5862 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
5863 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
5864 "severity" => node.severity = self.consume_any_ident_or_kw()?.value.clone(),
5865 "quarantine" => {
5866 node.quarantine = self.consume(TokenType::StringLit)?.value.clone()
5867 }
5868 "max_retries" => node.max_retries = self.parse_optional_int(),
5869 "confidence_threshold" => {
5870 node.confidence_threshold = self.parse_optional_float()
5871 }
5872 "allow_tools" => node.allow_tools = self.parse_bracketed_identifiers()?,
5873 "deny_tools" => node.deny_tools = self.parse_bracketed_identifiers()?,
5874 "sandbox" => {
5875 node.sandbox = Some(self.consume_any_ident_or_kw()?.value == "true")
5876 }
5877 "redact" => node.redact = self.parse_bracketed_identifiers()?,
5878 "log" => node.log = self.consume_any_ident_or_kw()?.value.clone(),
5879 "deflect_message" => {
5880 node.deflect_message = self.consume(TokenType::StringLit)?.value.clone()
5881 }
5882 "taint" => node.taint = self.consume_any_ident_or_kw()?.value.clone(),
5883 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
5885 "sign" => node.sign = self.consume_any_ident_or_kw()?.value.clone(),
5888 _ => {
5892 node.unknown_fields.push((field_name.clone(), field_loc));
5893 self.skip_value()
5894 }
5895 }
5896 } else if self.check(TokenType::LBrace) {
5897 self.skip_braced_block()?;
5898 }
5899 }
5900 self.consume(TokenType::RBrace)?;
5901 Ok(node)
5902 }
5903
5904 fn parse_pix(&mut self) -> Result<PixDefinition, ParseError> {
5905 let tok = self.consume(TokenType::Pix)?;
5906 let name = self.consume(TokenType::Identifier)?.value;
5907 let mut node = PixDefinition {
5908 name,
5909 source: String::new(),
5910 depth: None,
5911 branching: None,
5912 model: String::new(),
5913 loc: Loc {
5914 line: tok.line,
5915 column: tok.column,
5916 },
5917 leading_trivia: Vec::new(),
5918 trailing_trivia: Vec::new(),
5919 };
5920 self.consume(TokenType::LBrace)?;
5921 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5922 let field_name = self.current().value.clone();
5923 self.advance();
5924 if self.check(TokenType::Colon) {
5925 self.advance();
5926 match field_name.as_str() {
5927 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
5928 "depth" => node.depth = self.parse_optional_int(),
5929 "branching" => node.branching = self.parse_optional_int(),
5930 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
5931 _ => self.skip_value(),
5932 }
5933 } else if self.check(TokenType::LBrace) {
5934 self.skip_braced_block()?;
5935 }
5936 }
5937 self.consume(TokenType::RBrace)?;
5938 Ok(node)
5939 }
5940
5941 fn parse_ledger(&mut self) -> Result<LedgerDefinition, ParseError> {
5947 let tok = self.consume(TokenType::Ledger)?;
5948 let name = self.consume(TokenType::Identifier)?.value;
5949 let mut node = LedgerDefinition {
5950 name,
5951 source: String::new(),
5952 depth: None,
5953 branching: None,
5954 model: String::new(),
5955 loc: Loc {
5956 line: tok.line,
5957 column: tok.column,
5958 },
5959 leading_trivia: Vec::new(),
5960 trailing_trivia: Vec::new(),
5961 };
5962 self.consume(TokenType::LBrace)?;
5963 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5964 let field_name = self.current().value.clone();
5965 self.advance();
5966 if self.check(TokenType::Colon) {
5967 self.advance();
5968 match field_name.as_str() {
5969 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
5970 "depth" => node.depth = self.parse_optional_int(),
5971 "branching" => node.branching = self.parse_optional_int(),
5972 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
5973 _ => self.skip_value(),
5974 }
5975 } else if self.check(TokenType::LBrace) {
5976 self.skip_braced_block()?;
5977 }
5978 }
5979 self.consume(TokenType::RBrace)?;
5980 Ok(node)
5981 }
5982
5983 fn parse_psyche(&mut self) -> Result<PsycheDefinition, ParseError> {
5984 let tok = self.consume(TokenType::Psyche)?;
5985 let name = self.consume(TokenType::Identifier)?.value;
5986 let mut node = PsycheDefinition {
5987 name,
5988 dimensions: Vec::new(),
5989 manifold_noise: None,
5990 manifold_momentum: None,
5991 safety_constraints: Vec::new(),
5992 quantum_enabled: None,
5993 inference_mode: String::new(),
5994 loc: Loc {
5995 line: tok.line,
5996 column: tok.column,
5997 },
5998 leading_trivia: Vec::new(),
5999 trailing_trivia: Vec::new(),
6000 };
6001 self.consume(TokenType::LBrace)?;
6002 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6003 let field_name = self.current().value.clone();
6004 self.advance();
6005 if self.check(TokenType::Colon) {
6006 self.advance();
6007 match field_name.as_str() {
6008 "dimensions" => node.dimensions = self.parse_bracketed_identifiers()?,
6009 "manifold_noise" => node.manifold_noise = self.parse_optional_float(),
6010 "manifold_momentum" => node.manifold_momentum = self.parse_optional_float(),
6011 "safety_constraints" => {
6012 node.safety_constraints = self.parse_bracketed_identifiers()?
6013 }
6014 "quantum_enabled" => {
6015 node.quantum_enabled = Some(self.consume_any_ident_or_kw()?.value == "true")
6016 }
6017 "inference_mode" => {
6018 node.inference_mode = self.consume_any_ident_or_kw()?.value.clone()
6019 }
6020 _ => self.skip_value(),
6021 }
6022 } else if self.check(TokenType::LBrace) {
6023 self.skip_braced_block()?;
6024 }
6025 }
6026 self.consume(TokenType::RBrace)?;
6027 Ok(node)
6028 }
6029
6030 fn parse_corpus(&mut self) -> Result<CorpusDefinition, ParseError> {
6031 let tok = self.consume(TokenType::Corpus)?;
6032 let name = self.consume(TokenType::Identifier)?.value;
6033 let mut node = CorpusDefinition {
6034 name,
6035 documents: Vec::new(),
6036 relations: Vec::new(),
6037 adaptive: false,
6038 mcp_server: String::new(),
6039 mcp_resource_uri: String::new(),
6040 store_source: None,
6041 loc: Loc {
6042 line: tok.line,
6043 column: tok.column,
6044 },
6045 leading_trivia: Vec::new(),
6046 trailing_trivia: Vec::new(),
6047 };
6048 let mut dynamic = false;
6052 if self.check(TokenType::From) {
6053 self.advance();
6054 if self.check(TokenType::AxonStore) {
6055 self.advance();
6056 dynamic = true;
6057 } else {
6058 self.consume(TokenType::Mcp)?;
6059 self.consume(TokenType::LParen)?;
6060 node.mcp_server = self.consume(TokenType::StringLit)?.value.clone();
6061 self.consume(TokenType::Comma)?;
6062 node.mcp_resource_uri = self.consume(TokenType::StringLit)?.value.clone();
6063 self.consume(TokenType::RParen)?;
6064 return Ok(node);
6065 }
6066 }
6067 self.consume(TokenType::LBrace)?;
6068 let mut src = CorpusStoreSource {
6071 doc_store: String::new(),
6072 doc_id_col: String::new(),
6073 doc_title_col: String::new(),
6074 edge_store: String::new(),
6075 edge_from_col: String::new(),
6076 edge_to_col: String::new(),
6077 edge_type_col: String::new(),
6078 edge_weight_col: String::new(),
6079 loc: Loc {
6080 line: tok.line,
6081 column: tok.column,
6082 },
6083 };
6084 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6085 let field_name = self.current().value.clone();
6086 self.advance();
6087 if self.check(TokenType::Colon) {
6088 self.advance();
6089 match field_name.as_str() {
6090 "documents" if dynamic => {
6092 let (store, cols) = self.parse_corpus_store_mapping(2)?;
6093 src.doc_store = store;
6094 src.doc_id_col = cols[0].clone();
6095 src.doc_title_col = cols[1].clone();
6096 }
6097 "documents" => node.documents = self.parse_bracketed_identifiers()?,
6098 "relations" if dynamic => {
6100 let (store, cols) = self.parse_corpus_store_mapping(4)?;
6101 src.edge_store = store;
6102 src.edge_from_col = cols[0].clone();
6103 src.edge_to_col = cols[1].clone();
6104 src.edge_type_col = cols[2].clone();
6105 src.edge_weight_col = cols[3].clone();
6106 }
6107 "relations" => node.relations = self.parse_corpus_relations()?,
6109 "adaptive" => node.adaptive = self.consume_any_ident_or_kw()?.value == "true",
6111 _ => self.skip_value(),
6112 }
6113 } else if self.check(TokenType::LBrace) {
6114 self.skip_braced_block()?;
6115 }
6116 }
6117 self.consume(TokenType::RBrace)?;
6118 if dynamic {
6119 node.store_source = Some(src);
6120 }
6121 Ok(node)
6122 }
6123
6124 fn parse_corpus_store_mapping(&mut self, n: usize) -> Result<(String, Vec<String>), ParseError> {
6131 let store = self.consume(TokenType::Identifier)?.value.clone();
6132 self.consume(TokenType::LParen)?;
6133 let mut cols = Vec::with_capacity(n);
6134 for i in 0..n {
6135 if i > 0 {
6136 self.consume(TokenType::Comma)?;
6137 }
6138 cols.push(self.consume_any_ident_or_kw()?.value.clone());
6139 }
6140 self.consume(TokenType::RParen)?;
6141 Ok((store, cols))
6142 }
6143
6144 fn parse_corpus_relations(&mut self) -> Result<Vec<CorpusRelation>, ParseError> {
6150 let mut out = Vec::new();
6151 self.consume(TokenType::LBracket)?;
6152 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
6153 if self.check(TokenType::Comma) {
6154 self.advance();
6155 continue;
6156 }
6157 let tok = self.current().clone();
6158 let etype = self.consume_any_ident_or_kw()?.value.clone();
6159 self.consume(TokenType::LParen)?;
6160 let from = self.consume_any_ident_or_kw()?.value.clone();
6161 self.consume(TokenType::Comma)?;
6162 let to = self.consume_any_ident_or_kw()?.value.clone();
6163 self.consume(TokenType::Comma)?;
6164 let weight = self.consume_number()?;
6165 self.consume(TokenType::RParen)?;
6166 out.push(CorpusRelation {
6167 etype,
6168 from,
6169 to,
6170 weight,
6171 loc: Loc { line: tok.line, column: tok.column },
6172 });
6173 }
6174 self.consume(TokenType::RBracket)?;
6175 Ok(out)
6176 }
6177
6178 fn parse_dataspace(&mut self) -> Result<DataspaceDefinition, ParseError> {
6195 let tok = self.consume(TokenType::Dataspace)?;
6196 let name = self.consume(TokenType::Identifier)?.value;
6197 let mut node = DataspaceDefinition {
6198 name,
6199 columns: Vec::new(),
6200 loc: Loc {
6201 line: tok.line,
6202 column: tok.column,
6203 },
6204 leading_trivia: Vec::new(),
6205 trailing_trivia: Vec::new(),
6206 };
6207 if self.check(TokenType::LBrace) {
6208 self.consume(TokenType::LBrace)?;
6209 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6210 let entry = self.current().clone();
6211 if entry.value != "column" {
6212 return Err(ParseError {
6213 message: format!(
6214 "Unknown entry `{}` in dataspace `{}`. A dataspace body \
6215 declares its columnar schema: `column <name>: <Type>` \
6216 (one per line, over the closed type catalog — \
6217 Text, Int, Float, Bool, Timestamp, Json).",
6218 entry.value, node.name
6219 ),
6220 line: entry.line,
6221 column: entry.column,
6222 ..Default::default()
6223 });
6224 }
6225 self.advance(); let col_tok = self.current().clone();
6227 let col_name = self.consume_any_ident_or_kw()?.value.clone();
6228 self.consume(TokenType::Colon)?;
6229 let declared_type = self.consume_any_ident_or_kw()?.value.clone();
6230 node.columns.push(crate::ast::DataspaceColumn {
6231 name: col_name,
6232 declared_type,
6233 loc: Loc {
6234 line: col_tok.line,
6235 column: col_tok.column,
6236 },
6237 });
6238 }
6239 self.consume(TokenType::RBrace)?;
6240 }
6241 Ok(node)
6242 }
6243
6244 fn parse_ots(&mut self) -> Result<OtsDefinition, ParseError> {
6245 let tok = self.consume(TokenType::Ots)?;
6246 let name = self.consume(TokenType::Identifier)?.value;
6247 let mut node = OtsDefinition {
6248 name,
6249 teleology: String::new(),
6250 homotopy_search: String::new(),
6251 loss_function: String::new(),
6252 loc: Loc {
6253 line: tok.line,
6254 column: tok.column,
6255 },
6256 leading_trivia: Vec::new(),
6257 trailing_trivia: Vec::new(),
6258 };
6259 if self.check(TokenType::Lt) {
6261 while !self.check(TokenType::Gt) && !self.check(TokenType::Eof) {
6262 self.advance();
6263 }
6264 if self.check(TokenType::Gt) {
6265 self.advance();
6266 }
6267 }
6268 self.consume(TokenType::LBrace)?;
6269 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6270 let field_name = self.current().value.clone();
6271 self.advance();
6272 if self.check(TokenType::Colon) {
6273 self.advance();
6274 match field_name.as_str() {
6275 "teleology" => {
6276 node.teleology = self.consume(TokenType::StringLit)?.value.clone()
6277 }
6278 "homotopy_search" => {
6279 node.homotopy_search = self.consume_any_ident_or_kw()?.value.clone()
6280 }
6281 "loss_function" => {
6282 node.loss_function = self.consume(TokenType::StringLit)?.value.clone()
6283 }
6284 _ => self.skip_value(),
6285 }
6286 } else if self.check(TokenType::LBrace) {
6287 self.skip_braced_block()?;
6288 }
6289 }
6290 self.consume(TokenType::RBrace)?;
6291 Ok(node)
6292 }
6293
6294 fn parse_mandate(&mut self) -> Result<MandateDefinition, ParseError> {
6295 let tok = self.consume(TokenType::Mandate)?;
6296 let name = self.consume(TokenType::Identifier)?.value;
6297 let mut node = MandateDefinition {
6298 name,
6299 constraint: String::new(),
6300 kp: None,
6301 ki: None,
6302 kd: None,
6303 tolerance: None,
6304 max_steps: None,
6305 on_violation: String::new(),
6306 loc: Loc {
6307 line: tok.line,
6308 column: tok.column,
6309 },
6310 leading_trivia: Vec::new(),
6311 trailing_trivia: Vec::new(),
6312 };
6313 self.consume(TokenType::LBrace)?;
6314 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6315 let field_name = self.current().value.clone();
6316 self.advance();
6317 if self.check(TokenType::Colon) {
6318 self.advance();
6319 match field_name.as_str() {
6320 "constraint" => {
6321 node.constraint = self.consume(TokenType::StringLit)?.value.clone()
6322 }
6323 "kp" | "Kp" => node.kp = self.parse_optional_float(),
6324 "ki" | "Ki" => node.ki = self.parse_optional_float(),
6325 "kd" | "Kd" => node.kd = self.parse_optional_float(),
6326 "tolerance" => node.tolerance = self.parse_optional_float(),
6327 "max_steps" => node.max_steps = self.parse_optional_int(),
6328 "on_violation" => {
6329 node.on_violation = self.consume_any_ident_or_kw()?.value.clone()
6330 }
6331 _ => self.skip_value(),
6332 }
6333 } else if self.check(TokenType::LBrace) {
6334 self.skip_braced_block()?;
6335 }
6336 }
6337 self.consume(TokenType::RBrace)?;
6338 Ok(node)
6339 }
6340
6341 fn parse_compute(&mut self) -> Result<ComputeDefinition, ParseError> {
6370 let tok = self.consume(TokenType::Compute)?;
6371 let name = self.consume(TokenType::Identifier)?.value;
6372 let mut node = ComputeDefinition {
6373 name,
6374 shield_ref: String::new(),
6375 parameters: Vec::new(),
6376 return_type: String::new(),
6377 body: None,
6378 loc: Loc {
6379 line: tok.line,
6380 column: tok.column,
6381 },
6382 leading_trivia: Vec::new(),
6383 trailing_trivia: Vec::new(),
6384 };
6385
6386 if self.check(TokenType::LParen) {
6388 self.advance();
6389 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
6390 let ptok = self.current().clone();
6391 let pname = self.consume_any_ident_or_kw()?.value.clone();
6392 self.consume(TokenType::Colon)?;
6393 let ptype = self.parse_type_expr()?;
6394 node.parameters.push(Parameter {
6395 name: pname,
6396 type_expr: ptype,
6397 loc: self.loc_of(&ptok),
6398 });
6399 if self.check(TokenType::Comma) {
6400 self.advance();
6401 }
6402 }
6403 self.consume(TokenType::RParen)?;
6404 }
6405
6406 if self.check(TokenType::Arrow) {
6408 self.advance();
6409 node.return_type = self.consume_any_ident_or_kw()?.value.clone();
6410 }
6411
6412 self.consume(TokenType::LBrace)?;
6413 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6414 let is_field = self
6424 .tokens
6425 .get(self.pos + 1)
6426 .map(|t| t.ttype == TokenType::Colon)
6427 .unwrap_or(false);
6428 if is_field {
6429 let field_name = self.current().value.clone();
6430 self.advance();
6431 self.consume(TokenType::Colon)?;
6432 match field_name.as_str() {
6433 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6434 _ => self.skip_value(),
6435 }
6436 } else {
6437 node.body = Some(self.parse_expr()?);
6438 }
6439 }
6440 self.consume(TokenType::RBrace)?;
6441 Ok(node)
6442 }
6443
6444 fn parse_daemon(&mut self) -> Result<DaemonDefinition, ParseError> {
6445 let tok = self.consume(TokenType::Daemon)?;
6446 let name = self.consume(TokenType::Identifier)?.value;
6447 let mut node = DaemonDefinition {
6448 name,
6449 goal: String::new(),
6450 tools: Vec::new(),
6451 memory_ref: String::new(),
6452 strategy: String::new(),
6453 on_stuck: String::new(),
6454 shield_ref: String::new(),
6455 window_ref: String::new(),
6456 budget: None,
6457 max_tokens: None,
6458 max_time: String::new(),
6459 max_cost: None,
6460 listeners: Vec::new(),
6461 requires_capabilities: Vec::new(),
6462 loc: Loc {
6463 line: tok.line,
6464 column: tok.column,
6465 },
6466 leading_trivia: Vec::new(),
6467 trailing_trivia: Vec::new(),
6468 };
6469 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
6471 self.advance();
6472 }
6473 self.consume(TokenType::LBrace)?;
6474 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6475 let field = self.current().clone();
6476 let field_name = field.value.clone();
6477 self.advance();
6478 if self.check(TokenType::Colon) {
6479 self.advance();
6480 match field_name.as_str() {
6481 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
6482 "tools" => node.tools = self.parse_bracketed_identifiers()?,
6483 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
6484 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
6485 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
6486 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
6487 "window" => node.window_ref = self.consume_any_ident_or_kw()?.value.clone(),
6489 "max_tokens" => node.max_tokens = self.parse_optional_int(),
6490 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
6491 "max_cost" => node.max_cost = self.parse_optional_float(),
6492 "requires" => {
6497 let bracket_tok = self.current().clone();
6498 let items = self.parse_bracketed_dot_identifiers()?;
6499 for slug in &items {
6500 if !is_valid_capability_slug(slug) {
6501 return Err(ParseError {
6502 message: format!(
6503 "Invalid capability slug '{slug}' in daemon '{}' \
6504 `requires:`. Capability slugs must match \
6505 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
6506 lowercase identifiers. Examples: `daemon.run`, \
6507 `memory.write`, `flow.execute`.",
6508 node.name
6509 ),
6510 line: bracket_tok.line,
6511 column: bracket_tok.column,
6512 ..Default::default()
6513 });
6514 }
6515 }
6516 node.requires_capabilities = items;
6517 }
6518 _ => self.skip_value(),
6519 }
6520 } else if field.ttype == TokenType::Listen {
6521 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
6527 (self.consume(TokenType::StringLit)?.value.clone(), false)
6528 } else {
6529 (self.consume_any_ident_or_kw()?.value.clone(), true)
6530 };
6531 let mut alias = String::new();
6532 if !self.at_declaration_start()
6533 && !self.check(TokenType::RBrace)
6534 && !self.check(TokenType::LBrace)
6535 {
6536 let next = self.current().clone();
6537 if next.value == "as" || next.ttype == TokenType::As {
6538 self.advance();
6539 alias = self.consume_any_ident_or_kw()?.value.clone();
6540 }
6541 }
6542 let listen_loc = Loc {
6543 line: field.line,
6544 column: field.column,
6545 };
6546 let body = self.parse_listener_body()?;
6550 node.listeners.push(ListenStep {
6551 channel,
6552 channel_is_ref,
6553 event_alias: alias,
6554 body,
6555 loc: listen_loc,
6556 });
6557 } else if field_name == "budget" && self.check(TokenType::LBrace) {
6558 node.budget = Some(self.parse_budget_block(field.line, field.column)?);
6560 } else if self.check(TokenType::LBrace) {
6561 self.skip_braced_block()?;
6562 }
6563 }
6564 self.consume(TokenType::RBrace)?;
6565 Ok(node)
6566 }
6567
6568 fn parse_top_level_budget(&mut self) -> Result<BudgetBlock, ParseError> {
6577 let kw = self.consume(TokenType::Budget)?; let name = self.consume(TokenType::Identifier)?.value;
6579 let mut block = self.parse_budget_block(kw.line, kw.column)?;
6580 block.name = name;
6581 Ok(block)
6582 }
6583
6584 fn parse_budget_block(&mut self, line: u32, column: u32) -> Result<BudgetBlock, ParseError> {
6586 self.consume(TokenType::LBrace)?;
6587 let mut quotas = Vec::new();
6588 let mut on_exhausted = String::new();
6589 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6590 let field = self.current().clone();
6591 let field_name = self.consume_any_ident_or_kw()?.value;
6592 match field_name.as_str() {
6593 "rate" | "max" => {
6594 quotas.push(self.parse_budget_quota(field_name, field.line, field.column)?);
6595 }
6596 "on_exhausted" => {
6597 self.consume(TokenType::Colon)?;
6598 on_exhausted = self.consume_any_ident_or_kw()?.value;
6599 }
6600 _ => self.skip_value(),
6601 }
6602 }
6603 self.consume(TokenType::RBrace)?;
6604 Ok(BudgetBlock {
6605 name: String::new(),
6606 quotas,
6607 on_exhausted,
6608 loc: Loc { line, column },
6609 leading_trivia: Vec::new(),
6610 trailing_trivia: Vec::new(),
6611 })
6612 }
6613
6614 fn parse_budget_quota(
6617 &mut self,
6618 kind: String,
6619 line: u32,
6620 column: u32,
6621 ) -> Result<BudgetQuota, ParseError> {
6622 self.consume(TokenType::Colon)?;
6623 let limit = self.consume_number()? as i64;
6624 let _per = self.consume_any_ident_or_kw()?; let period = self.consume_any_ident_or_kw()?.value;
6627 let _on = self.consume_any_ident_or_kw()?; let _tool = self.consume_any_ident_or_kw()?; self.consume(TokenType::LParen)?;
6631 let effect = self.consume_any_ident_or_kw()?.value;
6632 self.consume(TokenType::RParen)?;
6633 Ok(BudgetQuota {
6634 kind,
6635 limit,
6636 period,
6637 effect,
6638 loc: Loc { line, column },
6639 })
6640 }
6641
6642 fn parse_axonstore(&mut self) -> Result<AxonStoreDefinition, ParseError> {
6643 let tok = self.consume(TokenType::AxonStore)?;
6644 let name = self.consume(TokenType::Identifier)?.value;
6645 let mut node = AxonStoreDefinition {
6646 name,
6647 backend: String::new(),
6648 connection: String::new(),
6649 resource_ref: String::new(),
6650 confidence_floor: None,
6651 isolation: String::new(),
6652 on_breach: String::new(),
6653 capability: String::new(),
6654 class: String::new(),
6655 column_schema: None,
6656 loc: Loc {
6657 line: tok.line,
6658 column: tok.column,
6659 },
6660 leading_trivia: Vec::new(),
6661 trailing_trivia: Vec::new(),
6662 };
6663 self.consume(TokenType::LBrace)?;
6664 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6665 let field = self.current().clone();
6666 let field_name = field.value.clone();
6667 if field.ttype == TokenType::Schema {
6673 self.advance();
6674 let parsed = self.parse_store_schema_declaration(&node.name, field.line, field.column)?;
6675 node.column_schema = Some(parsed);
6676 continue;
6677 }
6678 self.advance();
6679 if self.check(TokenType::Colon) {
6680 self.advance();
6681 match field_name.as_str() {
6682 "backend" => node.backend = self.consume_any_ident_or_kw()?.value.clone(),
6683 "class" => node.class = self.parse_dotted_identifier()?,
6691 "connection" => {
6692 node.connection = self.consume(TokenType::StringLit)?.value.clone()
6693 }
6694 "resource" => {
6700 node.resource_ref = self.consume_any_ident_or_kw()?.value.clone()
6701 }
6702 "confidence_floor" => node.confidence_floor = self.parse_optional_float(),
6703 "isolation" => node.isolation = self.consume_any_ident_or_kw()?.value.clone(),
6704 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
6705 "capability" => {
6709 let slug_tok = self.consume(TokenType::StringLit)?.clone();
6710 if !is_valid_capability_slug(&slug_tok.value) {
6711 return Err(ParseError {
6712 message: format!(
6713 "Invalid capability slug '{}' in axonstore '{}' \
6714 `capability:`. Capability slugs must match \
6715 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
6716 lowercase identifiers starting with a letter. Examples: \
6717 `admin`, `tenant.read`, `hipaa.phi.read`.",
6718 slug_tok.value, node.name
6719 ),
6720 line: slug_tok.line,
6721 column: slug_tok.column,
6722 ..Default::default()
6723 });
6724 }
6725 node.capability = slug_tok.value.clone();
6726 }
6727 _ => self.skip_value(),
6728 }
6729 } else if self.check(TokenType::LBrace) {
6730 self.skip_braced_block()?;
6731 }
6732 }
6733 self.consume(TokenType::RBrace)?;
6734 Ok(node)
6735 }
6736
6737 fn parse_store_schema_declaration(
6749 &mut self,
6750 store_name: &str,
6751 sch_line: u32,
6752 sch_col: u32,
6753 ) -> Result<crate::store_schema::StoreColumnSchema, ParseError> {
6754 use crate::store_schema::{StoreColumn, StoreColumnSchema, StoreColumnType};
6755
6756 if self.check(TokenType::LBrace) {
6758 self.consume(TokenType::LBrace)?;
6759 let mut columns: Vec<StoreColumn> = Vec::new();
6760 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6761 let col_tok = self.current().clone();
6762 let col_name = self.consume_any_ident_or_kw()?.value.clone();
6763 self.consume(TokenType::Colon)?;
6764 let type_tok = self.consume_any_ident_or_kw()?.clone();
6765 let col_type = StoreColumnType::from_token(&type_tok.value).ok_or_else(|| {
6766 let names = StoreColumnType::all_canonical_names();
6767 let suggestion =
6768 crate::smart_suggest::suggest_for(&type_tok.value, &names);
6769 let suggest_suffix = if suggestion.is_empty() {
6770 String::new()
6771 } else {
6772 format!(" {suggestion}")
6773 };
6774 let known = names.join(", ");
6775 ParseError {
6776 message: format!(
6777 "Unknown column type `{}` for column `{}` in \
6778 axonstore `{}` `schema:` block. The closed \
6779 v1.38.0 column-type catalog (Fase 38.b D1) \
6780 is {{{known}}} (plus common lowercase \
6781 aliases — `int`/`integer`/`int4` for \
6782 `Int`, `bool`/`boolean` for `Bool`, etc.).\
6783 {suggest_suffix}",
6784 type_tok.value, col_name, store_name
6785 ),
6786 line: type_tok.line,
6787 column: type_tok.column,
6788 ..Default::default()
6789 }
6790 })?;
6791
6792 let mut json_shape: Option<String> = None;
6802 if self.check(TokenType::Lt) {
6803 self.advance();
6804 let shape_tok = self.consume_any_ident_or_kw()?.clone();
6805 self.consume(TokenType::Gt)?;
6806 if matches!(col_type, StoreColumnType::Json | StoreColumnType::Jsonb) {
6807 json_shape = Some(shape_tok.value.clone());
6808 } else {
6809 return Err(ParseError {
6810 message: format!(
6811 "axon-T841 a shape lens `<{shape}>` may refine \
6812 only a `Json` / `Jsonb` column, but column \
6813 `{col}` in axonstore `{store}` is `{ty}`. Drop \
6814 the `<{shape}>` (a rigid column already has a \
6815 fixed shape), or change the column type to \
6816 `Json<{shape}>` if it carries open documents.",
6817 shape = shape_tok.value,
6818 col = col_name,
6819 store = store_name,
6820 ty = col_type.canonical_name(),
6821 ),
6822 line: shape_tok.line,
6823 column: shape_tok.column,
6824 ..Default::default()
6825 });
6826 }
6827 }
6828
6829 let mut col = StoreColumn {
6830 name: col_name,
6831 col_type,
6832 json_shape,
6833 primary_key: false,
6834 auto_increment: false,
6835 not_null: false,
6836 unique: false,
6837 indexed: false,
6838 default_value: String::new(),
6839 identity: false,
6844 line: col_tok.line,
6845 column: col_tok.column,
6846 };
6847
6848 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6851 if self.current().ttype != TokenType::Identifier {
6852 break;
6855 }
6856 let constraint = self.current().value.clone();
6857 match constraint.as_str() {
6858 "primary_key" => {
6859 col.primary_key = true;
6860 self.advance();
6861 }
6862 "auto_increment" => {
6863 col.auto_increment = true;
6864 self.advance();
6865 }
6866 "not_null" => {
6867 col.not_null = true;
6868 self.advance();
6869 }
6870 "unique" => {
6871 col.unique = true;
6872 self.advance();
6873 }
6874 "index" => {
6880 col.indexed = true;
6881 self.advance();
6882 }
6883 "identity" => {
6894 col.identity = true;
6895 self.advance();
6896 }
6897 "default" => {
6898 self.advance();
6899 let dv = self.current().clone();
6900 if matches!(
6901 dv.ttype,
6902 TokenType::StringLit
6903 | TokenType::Integer
6904 | TokenType::Float
6905 ) {
6906 col.default_value = dv.value.clone();
6907 self.advance();
6908 } else {
6909 col.default_value =
6910 self.consume_any_ident_or_kw()?.value.clone();
6911 }
6912 }
6913 _ => break,
6914 }
6915 }
6916
6917 columns.push(col);
6918 }
6919 self.consume(TokenType::RBrace)?;
6920 return Ok(StoreColumnSchema::Inline {
6921 columns,
6922 leading_trivia: Vec::new(),
6923 line: sch_line,
6924 column: sch_col,
6925 });
6926 }
6927
6928 if !self.check(TokenType::Colon) {
6930 let cur = self.current().clone();
6931 return Err(ParseError {
6932 message: format!(
6933 "axonstore `{store_name}` `schema:` declaration expects \
6934 `{{ … }}` (inline columns), `: \"manifest.ref\"` \
6935 (manifest reference), or `: env:VAR` (per-tenant schema \
6936 namespace). Got `{}` instead.",
6937 cur.value
6938 ),
6939 line: cur.line,
6940 column: cur.column,
6941 ..Default::default()
6942 });
6943 }
6944 self.consume(TokenType::Colon)?;
6945
6946 if self.check(TokenType::StringLit) {
6948 let lit = self.consume(TokenType::StringLit)?.clone();
6949 let value = lit.value.clone();
6950 if let Some(var) = value.strip_prefix("env:") {
6951 let var = var.trim();
6952 if var.is_empty() {
6953 return Err(ParseError {
6954 message: format!(
6955 "axonstore `{store_name}` `schema: \"env:\"` is \
6956 missing the variable name after the `env:` \
6957 prefix."
6958 ),
6959 line: lit.line,
6960 column: lit.column,
6961 ..Default::default()
6962 });
6963 }
6964 return Ok(StoreColumnSchema::EnvVar {
6965 var_name: var.to_string(),
6966 line: sch_line,
6967 column: sch_col,
6968 });
6969 }
6970 if value.trim().is_empty() {
6972 return Err(ParseError {
6973 message: format!(
6974 "axonstore `{store_name}` `schema:` manifest reference \
6975 is empty. Expected `\"qualified.name\"` — e.g. \
6976 `\"public.tenants\"`."
6977 ),
6978 line: lit.line,
6979 column: lit.column,
6980 ..Default::default()
6981 });
6982 }
6983 return Ok(StoreColumnSchema::ManifestRef {
6984 qualified_name: value,
6985 line: sch_line,
6986 column: sch_col,
6987 });
6988 }
6989
6990 let env_tok = self.current().clone();
6993 if env_tok.value == "env" {
6994 self.advance();
6995 if !self.check(TokenType::Colon) {
6996 return Err(ParseError {
6997 message: format!(
6998 "axonstore `{store_name}` `schema: env` is missing the \
6999 `:` separator. Expected `schema: env:VAR`."
7000 ),
7001 line: env_tok.line,
7002 column: env_tok.column,
7003 ..Default::default()
7004 });
7005 }
7006 self.advance(); let var_tok = self.consume_any_ident_or_kw()?.clone();
7008 if var_tok.value.trim().is_empty() {
7009 return Err(ParseError {
7010 message: format!(
7011 "axonstore `{store_name}` `schema: env:` is missing \
7012 the variable name."
7013 ),
7014 line: var_tok.line,
7015 column: var_tok.column,
7016 ..Default::default()
7017 });
7018 }
7019 return Ok(StoreColumnSchema::EnvVar {
7020 var_name: var_tok.value.clone(),
7021 line: sch_line,
7022 column: sch_col,
7023 });
7024 }
7025
7026 Err(ParseError {
7027 message: format!(
7028 "axonstore `{store_name}` `schema:` declaration expects \
7029 `{{ … }}` (inline columns), `\"manifest.ref\"` (manifest \
7030 reference), or `env:VAR` (per-tenant schema namespace). \
7031 Got `{}` instead.",
7032 env_tok.value
7033 ),
7034 line: env_tok.line,
7035 column: env_tok.column,
7036 ..Default::default()
7037 })
7038 }
7039
7040 fn parse_resource(&mut self) -> Result<ResourceDefinition, ParseError> {
7047 let tok = self.consume(TokenType::Resource)?;
7048 let name = self.consume(TokenType::Identifier)?.value;
7049 let mut node = ResourceDefinition {
7050 name,
7051 kind: String::new(),
7052 endpoint: String::new(),
7053 capacity: None,
7054 lifetime: "affine".to_string(),
7055 certainty_floor: None,
7056 shield_ref: String::new(),
7057 within: String::new(),
7058 loc: Loc {
7059 line: tok.line,
7060 column: tok.column,
7061 },
7062 leading_trivia: Vec::new(),
7063 trailing_trivia: Vec::new(),
7064 };
7065 self.consume(TokenType::LBrace)?;
7066 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7067 let field_tok = self.current().clone();
7068 let field_name = field_tok.value.clone();
7069 self.advance();
7070 if !self.check(TokenType::Colon) {
7071 if self.check(TokenType::LBrace) {
7073 self.skip_braced_block()?;
7074 }
7075 continue;
7076 }
7077 self.advance(); match field_name.as_str() {
7079 "kind" => node.kind = self.consume_any_ident_or_kw()?.value,
7080 "endpoint" => {
7093 node.endpoint = if self.check(TokenType::StringLit) {
7094 self.consume(TokenType::StringLit)?.value
7095 } else {
7096 self.parse_dotted_identifier()?
7097 };
7098 }
7099 "capacity" => {
7100 node.capacity = self.parse_optional_int();
7101 }
7102 "lifetime" => {
7103 let lt_tok = self.consume_any_ident_or_kw()?;
7104 let lt = lt_tok.value;
7105 if !matches!(lt.as_str(), "linear" | "affine" | "persistent") {
7106 return Err(ParseError {
7107 message: format!(
7108 "Invalid lifetime '{lt}' in resource '{}' — \
7109 expected linear | affine | persistent",
7110 node.name
7111 ),
7112 line: lt_tok.line,
7113 column: lt_tok.column,
7114 ..Default::default()
7115 });
7116 }
7117 node.lifetime = lt;
7118 }
7119 "certainty_floor" => {
7120 node.certainty_floor = self.parse_optional_float();
7121 }
7122 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7123 "within" => node.within = self.consume_any_ident_or_kw()?.value,
7127 unknown => {
7137 return Err(ParseError {
7138 message: format!(
7139 "Unknown field '{unknown}' in resource '{}' — expected one of: \
7140 kind, endpoint, capacity, lifetime, certainty_floor, shield, within",
7141 node.name
7142 ),
7143 line: field_tok.line,
7144 column: field_tok.column,
7145 ..Default::default()
7146 });
7147 }
7148 }
7149 }
7150 self.consume(TokenType::RBrace)?;
7151 Ok(node)
7152 }
7153
7154 fn parse_fabric(&mut self) -> Result<FabricDefinition, ParseError> {
7156 let tok = self.consume(TokenType::Fabric)?;
7157 let name = self.consume(TokenType::Identifier)?.value;
7158 let mut node = FabricDefinition {
7159 name,
7160 provider: String::new(),
7161 region: String::new(),
7162 zones: None,
7163 ephemeral: None,
7164 shield_ref: String::new(),
7165 loc: Loc {
7166 line: tok.line,
7167 column: tok.column,
7168 },
7169 leading_trivia: Vec::new(),
7170 trailing_trivia: Vec::new(),
7171 };
7172 self.consume(TokenType::LBrace)?;
7173 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7174 let field_name = self.current().value.clone();
7175 self.advance();
7176 if !self.check(TokenType::Colon) {
7177 if self.check(TokenType::LBrace) {
7178 self.skip_braced_block()?;
7179 }
7180 continue;
7181 }
7182 self.advance(); match field_name.as_str() {
7184 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
7185 "region" => node.region = self.consume(TokenType::StringLit)?.value,
7186 "zones" => node.zones = self.parse_optional_int(),
7187 "ephemeral" => {
7188 let b = self.parse_bool()?;
7189 node.ephemeral = Some(b);
7190 }
7191 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7192 _ => self.skip_value(),
7193 }
7194 }
7195 self.consume(TokenType::RBrace)?;
7196 Ok(node)
7197 }
7198
7199 fn parse_manifest(&mut self) -> Result<ManifestDefinition, ParseError> {
7201 let tok = self.consume(TokenType::Manifest)?;
7202 let name = self.consume(TokenType::Identifier)?.value;
7203 let mut node = ManifestDefinition {
7204 name,
7205 resources: Vec::new(),
7206 fabric_ref: String::new(),
7207 region: String::new(),
7208 zones: None,
7209 compliance: Vec::new(),
7210 loc: Loc {
7211 line: tok.line,
7212 column: tok.column,
7213 },
7214 leading_trivia: Vec::new(),
7215 trailing_trivia: Vec::new(),
7216 };
7217 self.consume(TokenType::LBrace)?;
7218 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7219 let field_name = self.current().value.clone();
7220 self.advance();
7221 if !self.check(TokenType::Colon) {
7222 if self.check(TokenType::LBrace) {
7223 self.skip_braced_block()?;
7224 }
7225 continue;
7226 }
7227 self.advance();
7228 match field_name.as_str() {
7229 "resources" => node.resources = self.parse_bracketed_identifiers()?,
7230 "fabric" => node.fabric_ref = self.consume_any_ident_or_kw()?.value,
7231 "region" => node.region = self.consume(TokenType::StringLit)?.value,
7232 "zones" => node.zones = self.parse_optional_int(),
7233 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
7234 _ => self.skip_value(),
7235 }
7236 }
7237 self.consume(TokenType::RBrace)?;
7238 Ok(node)
7239 }
7240
7241 fn parse_observe(&mut self) -> Result<ObserveDefinition, ParseError> {
7243 let tok = self.consume(TokenType::Observe)?;
7244 let name = self.consume(TokenType::Identifier)?.value;
7245 self.consume(TokenType::From)?;
7247 let target = self.consume(TokenType::Identifier)?.value;
7248 let mut node = ObserveDefinition {
7249 name,
7250 target,
7251 sources: Vec::new(),
7252 quorum: None,
7253 timeout: String::new(),
7254 on_partition: "fail".to_string(),
7255 certainty_floor: None,
7256 loc: Loc {
7257 line: tok.line,
7258 column: tok.column,
7259 },
7260 leading_trivia: Vec::new(),
7261 trailing_trivia: Vec::new(),
7262 };
7263 self.consume(TokenType::LBrace)?;
7264 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7265 let field_name = self.current().value.clone();
7266 self.advance();
7267 if !self.check(TokenType::Colon) {
7268 if self.check(TokenType::LBrace) {
7269 self.skip_braced_block()?;
7270 }
7271 continue;
7272 }
7273 self.advance();
7274 match field_name.as_str() {
7275 "sources" => node.sources = self.parse_bracketed_identifiers()?,
7276 "quorum" => node.quorum = self.parse_optional_int(),
7277 "timeout" => {
7278 let t = self.current().clone();
7279 match t.ttype {
7280 TokenType::Duration | TokenType::StringLit => {
7281 self.advance();
7282 node.timeout = t.value;
7283 }
7284 _ => node.timeout = self.consume_any_ident_or_kw()?.value,
7285 }
7286 }
7287 "on_partition" => {
7288 let p_tok = self.consume_any_ident_or_kw()?;
7289 let p = p_tok.value;
7290 if !matches!(p.as_str(), "fail" | "shield_quarantine") {
7291 return Err(ParseError {
7292 message: format!(
7293 "Invalid on_partition '{p}' in observe '{}' — \
7294 expected fail | shield_quarantine",
7295 node.name
7296 ),
7297 line: p_tok.line,
7298 column: p_tok.column,
7299 ..Default::default()
7300 });
7301 }
7302 node.on_partition = p;
7303 }
7304 "certainty_floor" => node.certainty_floor = self.parse_optional_float(),
7305 _ => self.skip_value(),
7306 }
7307 }
7308 self.consume(TokenType::RBrace)?;
7309 Ok(node)
7310 }
7311
7312 fn parse_reconcile(&mut self) -> Result<ReconcileDefinition, ParseError> {
7316 let tok = self.consume(TokenType::Reconcile)?;
7317 let name = self.consume(TokenType::Identifier)?.value;
7318 let mut node = ReconcileDefinition {
7319 name,
7320 observe_ref: String::new(),
7321 threshold: None,
7322 tolerance: None,
7323 on_drift: "provision".to_string(),
7324 shield_ref: String::new(),
7325 mandate_ref: String::new(),
7326 max_retries: 3,
7327 loc: Loc {
7328 line: tok.line,
7329 column: tok.column,
7330 },
7331 leading_trivia: Vec::new(),
7332 trailing_trivia: Vec::new(),
7333 };
7334 self.consume(TokenType::LBrace)?;
7335 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7336 let field_name = self.current().value.clone();
7337 self.advance();
7338 if !self.check(TokenType::Colon) {
7339 if self.check(TokenType::LBrace) {
7340 self.skip_braced_block()?;
7341 }
7342 continue;
7343 }
7344 self.advance();
7345 match field_name.as_str() {
7346 "observe" => node.observe_ref = self.consume_any_ident_or_kw()?.value,
7347 "threshold" => node.threshold = self.parse_optional_float(),
7348 "tolerance" => node.tolerance = self.parse_optional_float(),
7349 "on_drift" => {
7350 let d_tok = self.consume_any_ident_or_kw()?;
7351 let d = d_tok.value;
7352 if !matches!(d.as_str(), "provision" | "alert" | "refine") {
7353 return Err(ParseError {
7354 message: format!(
7355 "Invalid on_drift '{d}' in reconcile '{}' — \
7356 expected provision | alert | refine",
7357 node.name
7358 ),
7359 line: d_tok.line,
7360 column: d_tok.column,
7361 ..Default::default()
7362 });
7363 }
7364 node.on_drift = d;
7365 }
7366 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
7367 "mandate" => node.mandate_ref = self.consume_any_ident_or_kw()?.value,
7368 "max_retries" => {
7369 if let Some(v) = self.parse_optional_int() {
7370 node.max_retries = v;
7371 }
7372 }
7373 _ => self.skip_value(),
7374 }
7375 }
7376 self.consume(TokenType::RBrace)?;
7377 Ok(node)
7378 }
7379
7380 fn parse_lease(&mut self) -> Result<LeaseDefinition, ParseError> {
7382 let tok = self.consume(TokenType::Lease)?;
7383 let name = self.consume(TokenType::Identifier)?.value;
7384 let mut node = LeaseDefinition {
7385 name,
7386 resource_ref: String::new(),
7387 duration: String::new(),
7388 acquire: "on_start".to_string(),
7389 on_expire: "anchor_breach".to_string(),
7390 loc: Loc {
7391 line: tok.line,
7392 column: tok.column,
7393 },
7394 leading_trivia: Vec::new(),
7395 trailing_trivia: Vec::new(),
7396 };
7397 self.consume(TokenType::LBrace)?;
7398 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7399 let field_name = self.current().value.clone();
7400 self.advance();
7401 if !self.check(TokenType::Colon) {
7402 if self.check(TokenType::LBrace) {
7403 self.skip_braced_block()?;
7404 }
7405 continue;
7406 }
7407 self.advance();
7408 match field_name.as_str() {
7409 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
7410 "duration" => {
7411 let t = self.current().clone();
7412 match t.ttype {
7413 TokenType::Duration | TokenType::StringLit => {
7414 self.advance();
7415 node.duration = t.value;
7416 }
7417 _ => node.duration = self.consume_any_ident_or_kw()?.value,
7418 }
7419 }
7420 "acquire" => {
7421 let a_tok = self.consume_any_ident_or_kw()?;
7422 let a = a_tok.value;
7423 if !matches!(a.as_str(), "on_start" | "on_demand") {
7424 return Err(ParseError {
7425 message: format!(
7426 "Invalid acquire '{a}' in lease '{}' — \
7427 expected on_start | on_demand",
7428 node.name
7429 ),
7430 line: a_tok.line,
7431 column: a_tok.column,
7432 ..Default::default()
7433 });
7434 }
7435 node.acquire = a;
7436 }
7437 "on_expire" => {
7438 let e_tok = self.consume_any_ident_or_kw()?;
7439 let e = e_tok.value;
7440 if !matches!(e.as_str(), "anchor_breach" | "release" | "extend") {
7441 return Err(ParseError {
7442 message: format!(
7443 "Invalid on_expire '{e}' in lease '{}' — \
7444 expected anchor_breach | release | extend",
7445 node.name
7446 ),
7447 line: e_tok.line,
7448 column: e_tok.column,
7449 ..Default::default()
7450 });
7451 }
7452 node.on_expire = e;
7453 }
7454 _ => self.skip_value(),
7455 }
7456 }
7457 self.consume(TokenType::RBrace)?;
7458 Ok(node)
7459 }
7460
7461 fn parse_ensemble(&mut self) -> Result<EnsembleDefinition, ParseError> {
7463 let tok = self.consume(TokenType::Ensemble)?;
7464 let name = self.consume(TokenType::Identifier)?.value;
7465 let mut node = EnsembleDefinition {
7466 name,
7467 observations: Vec::new(),
7468 quorum: None,
7469 aggregation: "majority".to_string(),
7470 certainty_mode: "min".to_string(),
7471 loc: Loc {
7472 line: tok.line,
7473 column: tok.column,
7474 },
7475 leading_trivia: Vec::new(),
7476 trailing_trivia: Vec::new(),
7477 };
7478 self.consume(TokenType::LBrace)?;
7479 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7480 let field_name = self.current().value.clone();
7481 self.advance();
7482 if !self.check(TokenType::Colon) {
7483 if self.check(TokenType::LBrace) {
7484 self.skip_braced_block()?;
7485 }
7486 continue;
7487 }
7488 self.advance();
7489 match field_name.as_str() {
7490 "observations" => node.observations = self.parse_bracketed_identifiers()?,
7491 "quorum" => node.quorum = self.parse_optional_int(),
7492 "aggregation" => {
7493 let a_tok = self.consume_any_ident_or_kw()?;
7494 let a = a_tok.value;
7495 if !matches!(a.as_str(), "majority" | "weighted" | "byzantine") {
7496 return Err(ParseError {
7497 message: format!(
7498 "Invalid aggregation '{a}' in ensemble '{}' — \
7499 expected majority | weighted | byzantine",
7500 node.name
7501 ),
7502 line: a_tok.line,
7503 column: a_tok.column,
7504 ..Default::default()
7505 });
7506 }
7507 node.aggregation = a;
7508 }
7509 "certainty_mode" => {
7510 let c_tok = self.consume_any_ident_or_kw()?;
7511 let c = c_tok.value;
7512 if !matches!(c.as_str(), "min" | "weighted" | "harmonic") {
7513 return Err(ParseError {
7514 message: format!(
7515 "Invalid certainty_mode '{c}' in ensemble '{}' — \
7516 expected min | weighted | harmonic",
7517 node.name
7518 ),
7519 line: c_tok.line,
7520 column: c_tok.column,
7521 ..Default::default()
7522 });
7523 }
7524 node.certainty_mode = c;
7525 }
7526 _ => self.skip_value(),
7527 }
7528 }
7529 self.consume(TokenType::RBrace)?;
7530 Ok(node)
7531 }
7532
7533 fn parse_session_definition(&mut self) -> Result<SessionDefinition, ParseError> {
7541 let tok = self.consume(TokenType::Session)?;
7542 let name = self.consume(TokenType::Identifier)?.value;
7543 let mut node = SessionDefinition {
7544 name,
7545 roles: Vec::new(),
7546 loc: Loc {
7547 line: tok.line,
7548 column: tok.column,
7549 },
7550 leading_trivia: Vec::new(),
7551 trailing_trivia: Vec::new(),
7552 };
7553 self.consume(TokenType::LBrace)?;
7554 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7555 let role_tok = self.consume_any_ident_or_kw()?;
7556 self.consume(TokenType::Colon)?;
7557 let steps = self.parse_session_steps()?;
7558 node.roles.push(SessionRole {
7559 name: role_tok.value,
7560 steps,
7561 loc: Loc {
7562 line: role_tok.line,
7563 column: role_tok.column,
7564 },
7565 });
7566 }
7567 self.consume(TokenType::RBrace)?;
7568 Ok(node)
7569 }
7570
7571 fn parse_observable(&mut self) -> Result<ObservableDefinition, ParseError> {
7584 let tok = self.consume(TokenType::Observable)?;
7585 let name = self.consume(TokenType::Identifier)?.value;
7586 let mut node = ObservableDefinition {
7587 name,
7588 qubits: None,
7589 terms: Vec::new(),
7590 loc: Loc {
7591 line: tok.line,
7592 column: tok.column,
7593 },
7594 leading_trivia: Vec::new(),
7595 trailing_trivia: Vec::new(),
7596 };
7597 self.consume(TokenType::LBrace)?;
7598 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7599 let key_tok = self.consume_any_ident_or_kw()?;
7600 self.consume(TokenType::Colon)?;
7601 match key_tok.value.as_str() {
7602 "qubits" => node.qubits = Some(self.consume_number()? as i64),
7603 "term" => {
7604 let term_loc = Loc {
7605 line: key_tok.line,
7606 column: key_tok.column,
7607 };
7608 let mut negative = false;
7610 if self.check(TokenType::Minus) {
7611 self.advance();
7612 negative = true;
7613 } else if self.check(TokenType::Plus) {
7614 self.advance();
7615 }
7616 let mag = self.consume_number()?;
7617 let coefficient = if negative { -mag } else { mag };
7618 self.consume(TokenType::Star)?;
7620 let pauli = self.consume(TokenType::StringLit)?.value;
7621 node.terms.push(PauliTerm {
7622 coefficient,
7623 pauli,
7624 loc: term_loc,
7625 });
7626 }
7627 _ => self.skip_value(),
7628 }
7629 }
7630 self.consume(TokenType::RBrace)?;
7631 Ok(node)
7632 }
7633
7634 fn parse_witness(&mut self) -> Result<WitnessDefinition, ParseError> {
7642 let tok = self.consume(TokenType::Witness)?;
7643 let name = self.consume(TokenType::Identifier)?.value;
7644 let mut node = WitnessDefinition {
7645 name,
7646 claim: String::new(),
7647 baseline: String::new(),
7648 metric: String::new(),
7649 threshold: 0.0,
7650 data: String::new(),
7651 loc: Loc {
7652 line: tok.line,
7653 column: tok.column,
7654 },
7655 leading_trivia: Vec::new(),
7656 trailing_trivia: Vec::new(),
7657 };
7658 self.consume(TokenType::LBrace)?;
7659 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7660 let key_tok = self.consume_any_ident_or_kw()?;
7661 self.consume(TokenType::Colon)?;
7662 match key_tok.value.as_str() {
7663 "claim" => node.claim = self.consume_any_ident_or_kw()?.value,
7664 "against" => node.baseline = self.consume_any_ident_or_kw()?.value,
7667 "metric" => node.metric = self.consume_any_ident_or_kw()?.value,
7668 "threshold" => node.threshold = self.consume_number()?,
7669 "data" => node.data = self.consume_any_ident_or_kw()?.value,
7670 _ => self.skip_value(),
7671 }
7672 }
7673 self.consume(TokenType::RBrace)?;
7674 Ok(node)
7675 }
7676
7677 fn parse_socket(&mut self) -> Result<SocketDefinition, ParseError> {
7682 let tok = self.consume(TokenType::Socket)?;
7683 let name = self.consume(TokenType::Identifier)?.value;
7684 let mut node = SocketDefinition {
7685 name,
7686 loc: Loc { line: tok.line, column: tok.column },
7687 ..Default::default()
7688 };
7689 self.consume(TokenType::LBrace)?;
7690 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7691 let key = self.consume_any_ident_or_kw()?.value;
7692 self.consume(TokenType::Colon)?;
7693 match key.as_str() {
7694 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
7695 "backpressure" => {
7696 let kind = self.consume_any_ident_or_kw()?.value;
7698 if kind != "credit" {
7699 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
7700 }
7701 self.consume(TokenType::LParen)?;
7702 let n = self
7703 .consume(TokenType::Integer)?
7704 .value
7705 .parse::<i64>()
7706 .map_err(|_| self.error("backpressure credit must be an integer"))?;
7707 self.consume(TokenType::RParen)?;
7708 node.backpressure_credit = Some(n);
7709 }
7710 "reconnect" => {
7711 let mode = self.consume_any_ident_or_kw()?.value;
7712 node.reconnect = mode == "cognitive_state";
7713 }
7714 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
7715 other => return Err(self.error(&format!("unknown socket field `{other}`"))),
7716 }
7717 if self.check(TokenType::Comma) {
7719 self.consume(TokenType::Comma)?;
7720 }
7721 }
7722 self.consume(TokenType::RBrace)?;
7723 Ok(node)
7724 }
7725
7726 fn parse_upstream(&mut self) -> Result<UpstreamDefinition, ParseError> {
7734 let tok = self.consume(TokenType::Upstream)?;
7735 let name = self.consume(TokenType::Identifier)?.value;
7736 let mut node = UpstreamDefinition {
7737 name,
7738 loc: Loc { line: tok.line, column: tok.column },
7739 ..Default::default()
7740 };
7741 if self.check(TokenType::From) {
7743 self.advance();
7744 let base = self.consume(TokenType::Identifier)?.value;
7745 self.consume(TokenType::At)?;
7746 let version = self.consume_any_ident_or_kw()?.value;
7747 node.preset = Some(format!("{base}@{version}"));
7748 }
7749 self.consume(TokenType::LBrace)?;
7750 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7751 let key = self.consume_any_ident_or_kw()?.value;
7752 self.consume(TokenType::Colon)?;
7753 match key.as_str() {
7754 "transport" => node.transport = self.consume_any_ident_or_kw()?.value,
7755 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
7756 "role" => node.role = self.consume_any_ident_or_kw()?.value,
7757 "resolve" => node.resolve = self.parse_dotted_identifier()?,
7758 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
7762 "secret" => node.secret = self.parse_dotted_identifier()?,
7763 "auth" => {
7764 node.auth_kind = self.consume_any_ident_or_kw()?.value;
7767 if self.check(TokenType::LParen) {
7768 self.consume(TokenType::LParen)?;
7769 node.auth_name = Some(self.consume(TokenType::StringLit)?.value);
7770 if self.check(TokenType::Comma) {
7771 self.consume(TokenType::Comma)?;
7772 node.auth_prefix = Some(self.consume(TokenType::StringLit)?.value);
7773 }
7774 self.consume(TokenType::RParen)?;
7775 }
7776 }
7777 "map" => node.map = self.parse_upstream_map()?,
7778 "reconnect" => node.reconnect = Some(self.parse_upstream_reconnect()?),
7779 "overflow" => node.overflow = Some(self.consume_any_ident_or_kw()?.value),
7780 "backpressure" => {
7781 let kind = self.consume_any_ident_or_kw()?.value;
7783 if kind != "credit" {
7784 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
7785 }
7786 self.consume(TokenType::LParen)?;
7787 let n = self
7788 .consume(TokenType::Integer)?
7789 .value
7790 .parse::<i64>()
7791 .map_err(|_| self.error("backpressure credit must be an integer"))?;
7792 self.consume(TokenType::RParen)?;
7793 node.backpressure_credit = Some(n);
7794 }
7795 other => return Err(self.error(&format!("unknown upstream field `{other}`"))),
7796 }
7797 if self.check(TokenType::Comma) {
7799 self.consume(TokenType::Comma)?;
7800 }
7801 }
7802 self.consume(TokenType::RBrace)?;
7803 Ok(node)
7804 }
7805
7806 fn parse_cors(&mut self) -> Result<CorsDefinition, ParseError> {
7815 let tok = self.consume(TokenType::Cors)?;
7816 let name = self.consume(TokenType::Identifier)?.value;
7817 let mut node = CorsDefinition {
7818 name,
7819 loc: Loc { line: tok.line, column: tok.column },
7820 ..Default::default()
7821 };
7822 self.consume(TokenType::LBrace)?;
7823 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7824 let key = self.consume_any_ident_or_kw()?.value;
7825 self.consume(TokenType::Colon)?;
7826 match key.as_str() {
7827 "allow_origins" => node.allow_origins = self.parse_bracketed_strings()?,
7828 "allow_methods" => node.allow_methods = self.parse_bracketed_identifiers()?,
7829 "allow_headers" => node.allow_headers = self.parse_bracketed_strings()?,
7830 "allow_credentials" => {
7831 node.allow_credentials = self.consume_any_ident_or_kw()?.value == "true"
7832 }
7833 "max_age" => node.max_age = Some(self.consume(TokenType::Duration)?.value),
7834 "expose_headers" => node.expose_headers = self.parse_bracketed_strings()?,
7835 other => return Err(self.error(&format!("unknown cors field `{other}`"))),
7836 }
7837 if self.check(TokenType::Comma) {
7839 self.consume(TokenType::Comma)?;
7840 }
7841 }
7842 self.consume(TokenType::RBrace)?;
7843 Ok(node)
7844 }
7845
7846 fn parse_credential(&mut self) -> Result<CredentialDefinition, ParseError> {
7854 let tok = self.consume(TokenType::Credential)?;
7855 let name = self.consume(TokenType::Identifier)?.value;
7856 let mut node = CredentialDefinition {
7857 name,
7858 loc: Loc { line: tok.line, column: tok.column },
7859 ..Default::default()
7860 };
7861 self.consume(TokenType::LBrace)?;
7862 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7863 let key = self.consume_any_ident_or_kw()?.value;
7864 self.consume(TokenType::Colon)?;
7865 match key.as_str() {
7866 "ttl" => node.ttl = self.consume(TokenType::Duration)?.value,
7867 "grants" => {
7868 let bracket_tok = self.current().clone();
7869 let items = self.parse_bracketed_dot_identifiers()?;
7870 for slug in &items {
7871 if !is_valid_capability_slug(slug) {
7872 return Err(ParseError {
7873 message: format!(
7874 "Invalid capability slug '{slug}' in credential '{}' \
7875 `grants:`. Capability slugs must match \
7876 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
7877 lowercase identifiers starting with a letter. Examples: \
7878 `chat.invoke`, `flow.execute`.",
7879 node.name
7880 ),
7881 line: bracket_tok.line,
7882 column: bracket_tok.column,
7883 ..Default::default()
7884 });
7885 }
7886 }
7887 node.grants = items;
7888 }
7889 other => return Err(self.error(&format!("unknown credential field `{other}`"))),
7890 }
7891 if self.check(TokenType::Comma) {
7893 self.consume(TokenType::Comma)?;
7894 }
7895 }
7896 self.consume(TokenType::RBrace)?;
7897 Ok(node)
7898 }
7899
7900 fn parse_cache(&mut self) -> Result<CacheDefinition, ParseError> {
7907 let tok = self.consume(TokenType::Cache)?;
7908 let name = self.consume(TokenType::Identifier)?.value;
7909 let mut node = CacheDefinition {
7910 name,
7911 loc: Loc { line: tok.line, column: tok.column },
7912 ..Default::default()
7913 };
7914 self.consume(TokenType::LBrace)?;
7915 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7916 let key = self.consume_any_ident_or_kw()?.value;
7917 self.consume(TokenType::Colon)?;
7918 match key.as_str() {
7919 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
7920 "ttl" => node.ttl = Some(self.consume(TokenType::Duration)?.value),
7921 "key" => node.key_params = self.parse_bracketed_identifiers()?,
7922 "default" => {
7923 node.default_policy = self.consume_any_ident_or_kw()?.value == "true"
7924 }
7925 "apply_to_effects" => {
7926 node.apply_to_effects = self.parse_bracketed_identifiers()?
7927 }
7928 "invalidate_on" => node.invalidate_on = self.parse_bracketed_identifiers()?,
7929 other => return Err(self.error(&format!("unknown cache field `{other}`"))),
7930 }
7931 if self.check(TokenType::Comma) {
7932 self.consume(TokenType::Comma)?;
7933 }
7934 }
7935 self.consume(TokenType::RBrace)?;
7936 Ok(node)
7937 }
7938
7939 fn parse_document(&mut self) -> Result<crate::ast::DocumentDefinition, ParseError> {
7947 let tok = self.consume(TokenType::Document)?;
7948 let name = self.consume(TokenType::Identifier)?.value;
7949 let mut node = crate::ast::DocumentDefinition {
7950 name,
7951 loc: Loc {
7952 line: tok.line,
7953 column: tok.column,
7954 },
7955 ..Default::default()
7956 };
7957 self.consume(TokenType::LBrace)?;
7958 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7959 let field = self.current().clone();
7960 let field_name = field.value.clone();
7961 self.advance();
7962 if self.check(TokenType::Colon) {
7963 self.advance();
7964 match field_name.as_str() {
7965 "target" => node.target = self.consume_any_ident_or_kw()?.value,
7966 "template" => node.template = self.parse_dotted_identifier()?,
7967 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
7968 "effects" => node.effects = Some(self.parse_effect_row()?),
7969 other => {
7970 return Err(self.error(&format!(
7971 "unknown document field `{other}` in document `{}` — expected \
7972 `target:` / `template:` / `provenance:` / `effects:`, or a body \
7973 block (`section {{ … }}` / `slide {{ … }}` / `sheet {{ … }}`)",
7974 node.name
7975 )))
7976 }
7977 }
7978 } else if self.check(TokenType::LBrace) {
7979 node.blocks
7980 .push(self.parse_doc_block_body(field_name, field.line, field.column)?);
7981 } else {
7982 return Err(self.error(&format!(
7983 "unexpected `{field_name}` in document `{}` body — expected a `field:` or a \
7984 body block `{field_name} {{ … }}`",
7985 node.name
7986 )));
7987 }
7988 if self.check(TokenType::Comma) {
7989 self.advance();
7990 }
7991 }
7992 self.consume(TokenType::RBrace)?;
7993 Ok(node)
7994 }
7995
7996 fn parse_doc_block_body(
8002 &mut self,
8003 kind: String,
8004 line: u32,
8005 column: u32,
8006 ) -> Result<crate::ast::DocBlock, ParseError> {
8007 let mut block = crate::ast::DocBlock {
8008 kind,
8009 loc: Loc { line, column },
8010 ..Default::default()
8011 };
8012 self.consume(TokenType::LBrace)?;
8013 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8014 let name_tok = self.current().clone();
8015 let name = self.consume_any_ident_or_kw()?.value;
8016 if self.check(TokenType::Colon) {
8017 self.advance();
8018 let value = self.parse_doc_scalar()?;
8019 block.fields.push((name, value));
8020 } else if self.check(TokenType::LBrace) {
8021 let child = self.parse_doc_block_body(name, name_tok.line, name_tok.column)?;
8022 block.children.push(child);
8023 } else {
8024 return Err(self.error(&format!(
8025 "in document block `{}`: `{name}` must be a `field:` value or open a nested \
8026 block `{name} {{ … }}`",
8027 block.kind
8028 )));
8029 }
8030 if self.check(TokenType::Comma) {
8031 self.advance();
8032 }
8033 }
8034 self.consume(TokenType::RBrace)?;
8035 Ok(block)
8036 }
8037
8038 fn parse_doc_scalar(&mut self) -> Result<crate::ast::DocScalar, ParseError> {
8043 let tok = self.current().clone();
8044 match tok.ttype {
8045 TokenType::StringLit => {
8046 self.advance();
8047 Ok(crate::ast::DocScalar::Text(tok.value))
8048 }
8049 TokenType::Integer => {
8050 self.advance();
8051 Ok(crate::ast::DocScalar::Int(tok.value.parse::<i64>().unwrap_or(0)))
8052 }
8053 TokenType::Bool => {
8054 self.advance();
8055 Ok(crate::ast::DocScalar::Bool(tok.value == "true"))
8056 }
8057 TokenType::LBracket => {
8058 let items = self.parse_bracketed_strings()?;
8059 Ok(crate::ast::DocScalar::List(items))
8060 }
8061 _ => {
8062 let name = self.consume_any_ident_or_kw()?.value;
8063 Ok(crate::ast::DocScalar::Ref(name))
8064 }
8065 }
8066 }
8067
8068 fn parse_notify(&mut self) -> Result<crate::ast::NotifyDefinition, ParseError> {
8093 let tok = self.consume(TokenType::Notify)?;
8094 let name = self.consume(TokenType::Identifier)?.value;
8095 let mut node = crate::ast::NotifyDefinition {
8096 name,
8097 loc: Loc {
8098 line: tok.line,
8099 column: tok.column,
8100 },
8101 ..Default::default()
8102 };
8103 self.consume(TokenType::LBrace)?;
8104 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8105 let field = self.current().clone();
8106 let field_name = field.value.clone();
8107 self.advance();
8108 if self.check(TokenType::Colon) {
8109 self.advance();
8110 match field_name.as_str() {
8111 "channel" => node.channel = self.consume_any_ident_or_kw()?.value,
8112 "to" => {
8113 if self.current().value == "secret" && self.peek_is_lparen() {
8117 self.advance(); self.consume(TokenType::LParen)?;
8119 node.to_secret = self.parse_dotted_identifier()?;
8120 self.consume(TokenType::RParen)?;
8121 node.to_is_secret = true;
8122 } else if self.check(TokenType::StringLit) {
8123 node.to_secret = self.consume(TokenType::StringLit)?.value.clone();
8124 node.to_is_secret = false;
8125 } else {
8126 node.to_secret = self.consume_any_ident_or_kw()?.value.clone();
8127 node.to_is_secret = false;
8128 }
8129 }
8130 "template" => {
8131 node.template = self.consume(TokenType::StringLit)?.value.clone()
8132 }
8133 "window" => {
8134 if self.check(TokenType::Integer) {
8137 let n = self.consume(TokenType::Integer)?.value.clone();
8138 let unit = self.consume_any_ident_or_kw()?.value.clone();
8139 node.window = format!("{n}{unit}");
8140 } else {
8141 node.window = self.consume_any_ident_or_kw()?.value.clone();
8142 }
8143 }
8144 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
8145 "effects" => node.effects = Some(self.parse_effect_row()?),
8146 other => {
8147 return Err(self.error(&format!(
8148 "unknown notify field `{other}` in notify `{}` — expected \
8149 `channel:` / `to:` / `template:` / `window:` / `provenance:` / \
8150 `effects:`",
8151 node.name
8152 )))
8153 }
8154 }
8155 }
8156 }
8157 self.consume(TokenType::RBrace)?;
8158 Ok(node)
8159 }
8160
8161 fn peek_is_identifier(&self) -> bool {
8165 self.tokens
8166 .get(self.pos + 1)
8167 .map(|t| t.ttype == TokenType::Identifier)
8168 .unwrap_or(false)
8169 }
8170
8171 fn peek_is_lparen(&self) -> bool {
8172 self.tokens
8173 .get(self.pos + 1)
8174 .map(|t| t.ttype == TokenType::LParen)
8175 .unwrap_or(false)
8176 }
8177
8178 fn parse_deliver(&mut self) -> Result<crate::ast::DeliverDefinition, ParseError> {
8180 let tok = self.consume(TokenType::Deliver)?;
8181 let name = self.consume(TokenType::Identifier)?.value;
8182 let mut node = crate::ast::DeliverDefinition {
8183 name,
8184 loc: Loc {
8185 line: tok.line,
8186 column: tok.column,
8187 },
8188 ..Default::default()
8189 };
8190 self.consume(TokenType::LBrace)?;
8191 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8192 let field = self.current().clone();
8193 let field_name = field.value.clone();
8194 self.advance();
8195 if self.check(TokenType::Colon) {
8196 self.advance();
8197 match field_name.as_str() {
8198 "target" => node.target = self.consume_any_ident_or_kw()?.value,
8199 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
8200 "secret" => node.secret = self.consume_any_ident_or_kw()?.value,
8201 "effects" => node.effects = Some(self.parse_effect_row()?),
8202 other => {
8203 return Err(self.error(&format!(
8204 "unknown deliver field `{other}` in deliver `{}` — expected \
8205 `target:` / `provenance:` / `secret:` / `effects:`, or an operation \
8206 block (`upsert_contact {{ … }}` / `create_deal {{ … }}` / \
8207 `add_note {{ … }}`)",
8208 node.name
8209 )))
8210 }
8211 }
8212 } else if self.check(TokenType::LBrace) {
8213 node.ops
8214 .push(self.parse_deliver_op(field_name, field.line, field.column)?);
8215 } else {
8216 return Err(self.error(&format!(
8217 "unexpected `{field_name}` in deliver `{}` body — expected a `field:` or an \
8218 operation block `{field_name} {{ … }}`",
8219 node.name
8220 )));
8221 }
8222 if self.check(TokenType::Comma) {
8223 self.advance();
8224 }
8225 }
8226 self.consume(TokenType::RBrace)?;
8227 Ok(node)
8228 }
8229
8230 fn parse_deliver_op(
8234 &mut self,
8235 kind: String,
8236 line: u32,
8237 column: u32,
8238 ) -> Result<crate::ast::DeliverOp, ParseError> {
8239 let mut op = crate::ast::DeliverOp {
8240 kind,
8241 loc: Loc { line, column },
8242 ..Default::default()
8243 };
8244 self.consume(TokenType::LBrace)?;
8245 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8246 let name = self.consume_any_ident_or_kw()?.value;
8247 self.consume(TokenType::Colon).map_err(|_| {
8248 self.error(&format!(
8249 "in deliver operation `{}`: `{name}` must be a `field: value` pair — an \
8250 operation binds scalar fields, it takes no nested blocks",
8251 op.kind
8252 ))
8253 })?;
8254 let value = self.parse_doc_scalar()?;
8255 op.fields.push((name, value));
8256 if self.check(TokenType::Comma) {
8257 self.advance();
8258 }
8259 }
8260 self.consume(TokenType::RBrace)?;
8261 Ok(op)
8262 }
8263
8264 fn parse_savant(&mut self) -> Result<SavantDefinition, ParseError> {
8270 let tok = self.consume(TokenType::Savant)?;
8271 let name = self.consume(TokenType::Identifier)?.value;
8272 let mut node = SavantDefinition {
8273 name,
8274 loc: Loc {
8275 line: tok.line,
8276 column: tok.column,
8277 },
8278 ..Default::default()
8279 };
8280 self.consume(TokenType::LBrace)?;
8281 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8282 let field = self.current().clone();
8283 let field_name = field.value.clone();
8284 self.advance();
8285 if self.check(TokenType::Colon) {
8286 self.advance();
8287 match field_name.as_str() {
8288 "domain" => node.domain = self.consume(TokenType::StringLit)?.value,
8289 other => {
8290 return Err(self.error(&format!(
8291 "unknown savant field `{other}` in savant `{}` — expected \
8292 `domain:` or a `cognition` / `memory` / `budget` / `mandate` block",
8293 node.name
8294 )))
8295 }
8296 }
8297 } else if field_name == "cognition" {
8298 node.cognition = Some(self.parse_savant_cognition(field.line, field.column)?);
8299 } else if field_name == "memory" {
8300 node.memory = Some(self.parse_savant_memory(field.line, field.column)?);
8301 } else if field_name == "budget" {
8302 node.budget = Some(self.parse_savant_budget(field.line, field.column)?);
8303 } else if field_name == "mandate" {
8304 node.mandates
8305 .push(self.parse_savant_mandate(field.line, field.column)?);
8306 } else {
8307 return Err(self.error(&format!(
8308 "unexpected `{field_name}` in savant `{}` body — expected `domain:` or a \
8309 `cognition` / `memory` / `budget` / `mandate` block",
8310 node.name
8311 )));
8312 }
8313 if self.check(TokenType::Comma) {
8314 self.advance();
8315 }
8316 }
8317 self.consume(TokenType::RBrace)?;
8318 Ok(node)
8319 }
8320
8321 fn parse_savant_cognition(
8324 &mut self,
8325 line: u32,
8326 column: u32,
8327 ) -> Result<SavantCognition, ParseError> {
8328 self.consume(TokenType::LBrace)?;
8329 let mut node = SavantCognition {
8330 loc: Loc { line, column },
8331 ..Default::default()
8332 };
8333 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8334 let key = self.consume_any_ident_or_kw()?.value;
8335 self.consume(TokenType::Colon)?;
8336 match key.as_str() {
8337 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
8338 "entropic_threshold" => node.entropic_threshold = self.parse_optional_float(),
8339 "divergence" => node.divergence = self.consume_any_ident_or_kw()?.value,
8340 other => {
8341 return Err(self.error(&format!(
8342 "unknown savant `cognition` field `{other}` — expected \
8343 `depth` / `entropic_threshold` / `divergence`"
8344 )))
8345 }
8346 }
8347 if self.check(TokenType::Comma) {
8348 self.advance();
8349 }
8350 }
8351 self.consume(TokenType::RBrace)?;
8352 Ok(node)
8353 }
8354
8355 fn parse_savant_memory(
8358 &mut self,
8359 line: u32,
8360 column: u32,
8361 ) -> Result<SavantMemory, ParseError> {
8362 self.consume(TokenType::LBrace)?;
8363 let mut node = SavantMemory {
8364 loc: Loc { line, column },
8365 ..Default::default()
8366 };
8367 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8368 let key = self.consume_any_ident_or_kw()?.value;
8369 self.consume(TokenType::Colon)?;
8370 match key.as_str() {
8371 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
8372 "corpus_graph" => {
8373 node.corpus_graph = self.consume_any_ident_or_kw()?.value == "true"
8374 }
8375 "isolation_level" => node.isolation_level = self.consume_any_ident_or_kw()?.value,
8376 other => {
8377 return Err(self.error(&format!(
8378 "unknown savant `memory` field `{other}` — expected \
8379 `backend` / `corpus_graph` / `isolation_level`"
8380 )))
8381 }
8382 }
8383 if self.check(TokenType::Comma) {
8384 self.advance();
8385 }
8386 }
8387 self.consume(TokenType::RBrace)?;
8388 Ok(node)
8389 }
8390
8391 fn parse_savant_budget(
8394 &mut self,
8395 line: u32,
8396 column: u32,
8397 ) -> Result<SavantBudget, ParseError> {
8398 self.consume(TokenType::LBrace)?;
8399 let mut node = SavantBudget {
8400 loc: Loc { line, column },
8401 ..Default::default()
8402 };
8403 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8404 let key = self.consume_any_ident_or_kw()?.value;
8405 self.consume(TokenType::Colon)?;
8406 match key.as_str() {
8407 "max_iterations" => node.max_iterations = self.parse_optional_int(),
8408 "max_tool_synth" => node.max_tool_synth = self.parse_optional_int(),
8409 other => {
8410 return Err(self.error(&format!(
8411 "unknown savant `budget` field `{other}` — expected \
8412 `max_iterations` / `max_tool_synth`"
8413 )))
8414 }
8415 }
8416 if self.check(TokenType::Comma) {
8417 self.advance();
8418 }
8419 }
8420 self.consume(TokenType::RBrace)?;
8421 Ok(node)
8422 }
8423
8424 fn parse_savant_mandate(
8427 &mut self,
8428 line: u32,
8429 column: u32,
8430 ) -> Result<SavantMandate, ParseError> {
8431 let name = self.consume(TokenType::Identifier)?.value;
8432 let mut node = SavantMandate {
8433 name,
8434 loc: Loc { line, column },
8435 ..Default::default()
8436 };
8437 self.consume(TokenType::LBrace)?;
8438 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8439 let key = self.consume_any_ident_or_kw()?.value;
8440 self.consume(TokenType::Colon)?;
8441 match key.as_str() {
8442 "objective" => node.objective = self.consume(TokenType::StringLit)?.value,
8443 "output" => node.output_type = self.consume_any_ident_or_kw()?.value,
8444 other => {
8445 return Err(self.error(&format!(
8446 "unknown savant `mandate` field `{other}` — expected `objective` / `output`"
8447 )))
8448 }
8449 }
8450 if self.check(TokenType::Comma) {
8451 self.advance();
8452 }
8453 }
8454 self.consume(TokenType::RBrace)?;
8455 Ok(node)
8456 }
8457
8458 fn parse_synth(&mut self) -> Result<SynthDefinition, ParseError> {
8463 let tok = self.consume(TokenType::Synth)?;
8464 let name = self.consume(TokenType::Identifier)?.value;
8465 let mut node = SynthDefinition {
8466 name,
8467 loc: Loc {
8468 line: tok.line,
8469 column: tok.column,
8470 },
8471 ..Default::default()
8472 };
8473 self.consume(TokenType::LBrace)?;
8474 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8475 let key = self.consume_any_ident_or_kw()?.value;
8476 self.consume(TokenType::Colon)?;
8477 match key.as_str() {
8478 "target" => node.target = self.consume(TokenType::StringLit)?.value,
8479 "risk" => node.risk = self.consume_any_ident_or_kw()?.value,
8480 "language" => node.language = self.consume_any_ident_or_kw()?.value,
8481 "sandbox" => node.sandbox = self.consume_any_ident_or_kw()?.value,
8482 "review" => node.review = self.consume_any_ident_or_kw()?.value,
8483 "max_lines" => node.max_lines = self.parse_optional_int(),
8484 other => {
8485 return Err(self.error(&format!(
8486 "unknown synth field `{other}` in synth `{}` — expected `target` / `risk` \
8487 / `language` / `sandbox` / `review` / `max_lines`",
8488 node.name
8489 )))
8490 }
8491 }
8492 if self.check(TokenType::Comma) {
8493 self.consume(TokenType::Comma)?;
8494 }
8495 }
8496 self.consume(TokenType::RBrace)?;
8497 Ok(node)
8498 }
8499
8500 fn parse_voice(&mut self) -> Result<VoiceDefinition, ParseError> {
8505 let tok = self.consume(TokenType::Voice)?;
8506 let name = self.consume(TokenType::Identifier)?.value;
8507 let mut node = VoiceDefinition {
8508 name,
8509 loc: Loc { line: tok.line, column: tok.column },
8510 ..Default::default()
8511 };
8512 self.consume(TokenType::LBrace)?;
8513 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8514 let key = self.consume_any_ident_or_kw()?.value;
8515 self.consume(TokenType::Colon)?;
8516 match key.as_str() {
8517 "stt" => node.stt = Some(self.parse_upstream_ref()?),
8519 "tts" => node.tts = Some(self.parse_upstream_ref()?),
8520 "realtime" => node.realtime = Some(self.parse_upstream_ref()?),
8521 "carrier" => node.carrier = self.consume_any_ident_or_kw()?.value,
8522 "interruptible" => {
8523 let v = self.consume_any_ident_or_kw()?.value;
8524 node.interruptible = v == "true";
8525 }
8526 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
8527 "persona" => node.persona = Some(self.consume(TokenType::Identifier)?.value),
8528 "context" => node.context = Some(self.consume(TokenType::Identifier)?.value),
8529 other => return Err(self.error(&format!("unknown voice field `{other}`"))),
8530 }
8531 if self.check(TokenType::Comma) {
8532 self.consume(TokenType::Comma)?;
8533 }
8534 }
8535 self.consume(TokenType::RBrace)?;
8536 Ok(node)
8537 }
8538
8539 fn parse_upstream_ref(&mut self) -> Result<String, ParseError> {
8542 let base = self.consume(TokenType::Identifier)?.value;
8543 if self.check(TokenType::At) {
8544 self.advance();
8545 let version = self.consume_any_ident_or_kw()?.value;
8546 Ok(format!("{base}@{version}"))
8547 } else {
8548 Ok(base)
8549 }
8550 }
8551
8552 fn parse_upstream_map(&mut self) -> Result<Vec<UpstreamMapRule>, ParseError> {
8558 self.consume(TokenType::LBracket)?;
8559 let mut rules = Vec::new();
8560 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8561 let dir_tok = self.current().clone();
8562 let direction = match dir_tok.ttype {
8563 TokenType::Send => "send",
8564 TokenType::Receive => "receive",
8565 _ => {
8566 return Err(self.error(&format!(
8567 "upstream map rule must start with `send` or `receive`, got `{}`",
8568 dir_tok.value
8569 )))
8570 }
8571 };
8572 self.advance();
8573 let message = self.consume(TokenType::Identifier)?.value;
8574 self.consume(TokenType::As)?;
8575 let framing = self.consume_any_ident_or_kw()?.value;
8576 let mut rule = UpstreamMapRule {
8577 direction: direction.to_string(),
8578 message,
8579 framing,
8580 loc: Loc { line: dir_tok.line, column: dir_tok.column },
8581 ..Default::default()
8582 };
8583 if self.current().value == "tag" {
8585 self.advance();
8586 rule.tag = Some(self.consume(TokenType::StringLit)?.value);
8587 } else if self.current().value == "when" {
8588 self.advance();
8593 rule.when_field = Some(self.consume(TokenType::StringLit)?.value);
8594 if self.check(TokenType::Assign) {
8595 self.advance();
8596 rule.when_value = Some(self.consume(TokenType::StringLit)?.value);
8597 }
8598 }
8599 rules.push(rule);
8600 if self.check(TokenType::Comma) {
8601 self.advance();
8602 }
8603 }
8604 self.consume(TokenType::RBracket)?;
8605 Ok(rules)
8606 }
8607
8608 fn parse_upstream_reconnect(&mut self) -> Result<UpstreamReconnect, ParseError> {
8612 self.consume(TokenType::LBrace)?;
8613 let mut backoff_ms: Option<i64> = None;
8614 let mut max_attempts: Option<i64> = None;
8615 let mut on_exhausted: Option<String> = None;
8616 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8617 let key = self.consume_any_ident_or_kw()?.value;
8618 self.consume(TokenType::Colon)?;
8619 match key.as_str() {
8620 "backoff_ms" => {
8621 backoff_ms = Some(
8622 self.consume(TokenType::Integer)?
8623 .value
8624 .parse::<i64>()
8625 .map_err(|_| self.error("backoff_ms must be an integer"))?,
8626 )
8627 }
8628 "max_attempts" => {
8629 max_attempts = Some(
8630 self.consume(TokenType::Integer)?
8631 .value
8632 .parse::<i64>()
8633 .map_err(|_| self.error("max_attempts must be an integer"))?,
8634 )
8635 }
8636 "on_exhausted" => on_exhausted = Some(self.consume_any_ident_or_kw()?.value),
8637 other => return Err(self.error(&format!("unknown reconnect field `{other}`"))),
8638 }
8639 if self.check(TokenType::Comma) {
8640 self.consume(TokenType::Comma)?;
8641 }
8642 }
8643 self.consume(TokenType::RBrace)?;
8644 match (backoff_ms, max_attempts, on_exhausted) {
8645 (Some(b), Some(m), Some(o)) => Ok(UpstreamReconnect { backoff_ms: b, max_attempts: m, on_exhausted: o }),
8646 _ => Err(self.error(
8647 "reconnect requires all of `backoff_ms:`, `max_attempts:`, `on_exhausted:` — a reconnection policy with a hole is not a policy",
8648 )),
8649 }
8650 }
8651
8652 fn parse_session_steps(&mut self) -> Result<Vec<SessionStep>, ParseError> {
8654 self.consume(TokenType::LBracket)?;
8655 let mut steps = Vec::new();
8656 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8657 steps.push(self.parse_session_step()?);
8658 if self.check(TokenType::Comma) {
8659 self.advance();
8660 }
8661 }
8662 self.consume(TokenType::RBracket)?;
8663 Ok(steps)
8664 }
8665
8666 fn parse_session_step_block(&mut self) -> Result<Vec<SessionStep>, ParseError> {
8670 self.consume(TokenType::LBrace)?;
8671 let mut steps = Vec::new();
8672 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8673 steps.push(self.parse_session_step()?);
8674 if self.check(TokenType::Comma) {
8675 self.advance();
8676 }
8677 }
8678 self.consume(TokenType::RBrace)?;
8679 Ok(steps)
8680 }
8681
8682 fn parse_session_step(&mut self) -> Result<SessionStep, ParseError> {
8683 let tok = self.current().clone();
8684 let loc = Loc { line: tok.line, column: tok.column };
8685 match tok.ttype {
8686 TokenType::Send => {
8687 self.advance();
8688 let msg = self.consume_any_ident_or_kw()?;
8689 Ok(SessionStep { op: "send".into(), message_type: msg.value, loc, ..Default::default() })
8690 }
8691 TokenType::Receive => {
8692 self.advance();
8693 let msg = self.consume_any_ident_or_kw()?;
8694 Ok(SessionStep { op: "receive".into(), message_type: msg.value, loc, ..Default::default() })
8695 }
8696 TokenType::Loop => {
8697 self.advance();
8698 Ok(SessionStep { op: "loop".into(), loc, ..Default::default() })
8699 }
8700 TokenType::End => {
8701 self.advance();
8702 Ok(SessionStep { op: "end".into(), loc, ..Default::default() })
8703 }
8704 TokenType::Identifier if tok.value == "select" || tok.value == "branch" => {
8707 self.parse_session_choice(&tok.value, loc)
8708 }
8709 TokenType::Identifier if tok.value == "interrupt" => {
8712 self.parse_session_interrupt(loc)
8713 }
8714 TokenType::Identifier if tok.value == "resume" => {
8718 self.advance();
8719 Ok(SessionStep { op: "resume".into(), loc, ..Default::default() })
8720 }
8721 _ => Err(ParseError {
8722 message: format!(
8723 "Invalid session step '{}' — expected send | receive | loop | end | select | branch | interrupt | resume",
8724 tok.value
8725 ),
8726 line: tok.line,
8727 column: tok.column,
8728 ..Default::default()
8729 }),
8730 }
8731 }
8732
8733 fn consume_contextual(&mut self, kw: &str) -> Result<(), ParseError> {
8738 let t = self.current().clone();
8739 if t.value != kw {
8740 return Err(ParseError {
8741 message: format!("expected `{kw}` in interrupt step, got `{}`", t.value),
8742 line: t.line,
8743 column: t.column,
8744 ..Default::default()
8745 });
8746 }
8747 self.advance();
8748 Ok(())
8749 }
8750
8751 fn parse_session_interrupt(&mut self, loc: Loc) -> Result<SessionStep, ParseError> {
8759 self.advance(); let body = self.parse_session_step_block()?;
8763 self.consume_contextual("on")?;
8765 let signal = self.consume_any_ident_or_kw()?;
8766 self.consume_contextual("as")?;
8768 let binder = self.consume_any_ident_or_kw()?;
8769 self.consume_contextual("resumable")?;
8771 let handler = self.parse_session_step_block()?;
8772 Ok(SessionStep {
8773 op: "interrupt".into(),
8774 message_type: signal.value,
8775 branches: vec![
8776 SessionBranch { label: "body".into(), steps: body, loc: loc.clone() },
8777 SessionBranch { label: "handler".into(), steps: handler, loc: loc.clone() },
8778 ],
8779 binder: binder.value,
8780 resumable: true,
8781 loc,
8782 })
8783 }
8784
8785 fn parse_session_choice(&mut self, op: &str, loc: Loc) -> Result<SessionStep, ParseError> {
8788 self.advance(); self.consume(TokenType::LBrace)?;
8790 let mut branches = Vec::new();
8791 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8792 let label_tok = self.consume_any_ident_or_kw()?;
8793 self.consume(TokenType::Colon)?;
8794 let steps = self.parse_session_steps()?;
8795 branches.push(SessionBranch {
8796 label: label_tok.value,
8797 steps,
8798 loc: Loc { line: label_tok.line, column: label_tok.column },
8799 });
8800 if self.check(TokenType::Comma) {
8801 self.advance();
8802 }
8803 }
8804 self.consume(TokenType::RBrace)?;
8805 Ok(SessionStep { op: op.to_string(), branches, loc, ..Default::default() })
8806 }
8807
8808 fn parse_topology(&mut self) -> Result<TopologyDefinition, ParseError> {
8810 let tok = self.consume(TokenType::Topology)?;
8811 let name = self.consume(TokenType::Identifier)?.value;
8812 let mut node = TopologyDefinition {
8813 name,
8814 nodes: Vec::new(),
8815 edges: Vec::new(),
8816 loc: Loc {
8817 line: tok.line,
8818 column: tok.column,
8819 },
8820 leading_trivia: Vec::new(),
8821 trailing_trivia: Vec::new(),
8822 };
8823 self.consume(TokenType::LBrace)?;
8824 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8825 let field_name = self.current().value.clone();
8826 self.advance();
8827 if !self.check(TokenType::Colon) {
8828 if self.check(TokenType::LBrace) {
8829 self.skip_braced_block()?;
8830 }
8831 continue;
8832 }
8833 self.advance();
8834 match field_name.as_str() {
8835 "nodes" => node.nodes = self.parse_bracketed_identifiers()?,
8836 "edges" => node.edges = self.parse_topology_edges()?,
8837 _ => self.skip_value(),
8838 }
8839 }
8840 self.consume(TokenType::RBrace)?;
8841 Ok(node)
8842 }
8843
8844 fn parse_topology_edges(&mut self) -> Result<Vec<TopologyEdge>, ParseError> {
8845 self.consume(TokenType::LBracket)?;
8846 let mut edges = Vec::new();
8847 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
8848 edges.push(self.parse_topology_edge()?);
8849 if self.check(TokenType::Comma) {
8850 self.advance();
8851 }
8852 }
8853 self.consume(TokenType::RBracket)?;
8854 Ok(edges)
8855 }
8856
8857 fn parse_topology_edge(&mut self) -> Result<TopologyEdge, ParseError> {
8858 let src_tok = self.consume_any_ident_or_kw()?;
8859 self.consume(TokenType::Arrow)?;
8860 let tgt_tok = self.consume_any_ident_or_kw()?;
8861 self.consume(TokenType::Colon)?;
8862 let sess_tok = self.consume_any_ident_or_kw()?;
8863 Ok(TopologyEdge {
8864 source: src_tok.value,
8865 target: tgt_tok.value,
8866 session_ref: sess_tok.value,
8867 loc: Loc {
8868 line: src_tok.line,
8869 column: src_tok.column,
8870 },
8871 })
8872 }
8873
8874 fn parse_immune(&mut self) -> Result<ImmuneDefinition, ParseError> {
8878 let tok = self.consume(TokenType::Immune)?;
8879 let name = self.consume(TokenType::Identifier)?.value;
8880 let mut node = ImmuneDefinition {
8881 name,
8882 watch: Vec::new(),
8883 sensitivity: None,
8884 baseline: "learned".to_string(),
8885 window: 100,
8886 scope: String::new(),
8887 tau: String::new(),
8888 decay: "exponential".to_string(),
8889 loc: Loc {
8890 line: tok.line,
8891 column: tok.column,
8892 },
8893 leading_trivia: Vec::new(),
8894 trailing_trivia: Vec::new(),
8895 };
8896 self.consume(TokenType::LBrace)?;
8897 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8898 let field_name = self.current().value.clone();
8899 self.advance();
8900 if !self.check(TokenType::Colon) {
8901 if self.check(TokenType::LBrace) {
8902 self.skip_braced_block()?;
8903 }
8904 continue;
8905 }
8906 self.advance();
8907 match field_name.as_str() {
8908 "watch" => node.watch = self.parse_bracketed_identifiers()?,
8909 "sensitivity" => node.sensitivity = self.parse_optional_float(),
8910 "baseline" => node.baseline = self.consume_any_ident_or_kw()?.value,
8911 "window" => {
8912 if let Some(v) = self.parse_optional_int() {
8913 node.window = v;
8914 }
8915 }
8916 "scope" => {
8917 let s_tok = self.consume_any_ident_or_kw()?;
8918 let s = s_tok.value;
8919 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
8920 return Err(ParseError {
8921 message: format!(
8922 "Invalid scope '{s}' in immune '{}' — \
8923 expected tenant | flow | global",
8924 node.name
8925 ),
8926 line: s_tok.line,
8927 column: s_tok.column,
8928 ..Default::default()
8929 });
8930 }
8931 node.scope = s;
8932 }
8933 "tau" => {
8934 let t = self.current().clone();
8935 match t.ttype {
8936 TokenType::Duration | TokenType::StringLit => {
8937 self.advance();
8938 node.tau = t.value;
8939 }
8940 _ => node.tau = self.consume_any_ident_or_kw()?.value,
8941 }
8942 }
8943 "decay" => {
8944 let d_tok = self.consume_any_ident_or_kw()?;
8945 let d = d_tok.value;
8946 if !matches!(d.as_str(), "exponential" | "linear" | "none") {
8947 return Err(ParseError {
8948 message: format!(
8949 "Invalid decay '{d}' in immune '{}' — \
8950 expected exponential | linear | none",
8951 node.name
8952 ),
8953 line: d_tok.line,
8954 column: d_tok.column,
8955 ..Default::default()
8956 });
8957 }
8958 node.decay = d;
8959 }
8960 _ => self.skip_value(),
8961 }
8962 }
8963 self.consume(TokenType::RBrace)?;
8964 Ok(node)
8965 }
8966
8967 fn parse_reflex(&mut self) -> Result<ReflexDefinition, ParseError> {
8969 let tok = self.consume(TokenType::Reflex)?;
8970 let name = self.consume(TokenType::Identifier)?.value;
8971 let mut node = ReflexDefinition {
8972 name,
8973 trigger: String::new(),
8974 on_level: "doubt".to_string(),
8975 action: String::new(),
8976 scope: String::new(),
8977 sla: String::new(),
8978 loc: Loc {
8979 line: tok.line,
8980 column: tok.column,
8981 },
8982 leading_trivia: Vec::new(),
8983 trailing_trivia: Vec::new(),
8984 };
8985 self.consume(TokenType::LBrace)?;
8986 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8987 let field_name = self.current().value.clone();
8988 self.advance();
8989 if !self.check(TokenType::Colon) {
8990 if self.check(TokenType::LBrace) {
8991 self.skip_braced_block()?;
8992 }
8993 continue;
8994 }
8995 self.advance();
8996 match field_name.as_str() {
8997 "trigger" => node.trigger = self.consume_any_ident_or_kw()?.value,
8998 "on_level" => {
8999 let l_tok = self.consume_any_ident_or_kw()?;
9000 let l = l_tok.value;
9001 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
9002 return Err(ParseError {
9003 message: format!(
9004 "Invalid on_level '{l}' in reflex '{}' — \
9005 expected know | believe | speculate | doubt",
9006 node.name
9007 ),
9008 line: l_tok.line,
9009 column: l_tok.column,
9010 ..Default::default()
9011 });
9012 }
9013 node.on_level = l;
9014 }
9015 "action" => {
9016 let a_tok = self.consume_any_ident_or_kw()?;
9017 let a = a_tok.value;
9018 if !matches!(
9019 a.as_str(),
9020 "drop"
9021 | "revoke"
9022 | "emit"
9023 | "redact"
9024 | "quarantine"
9025 | "terminate"
9026 | "alert"
9027 ) {
9028 return Err(ParseError {
9029 message: format!(
9030 "Invalid action '{a}' in reflex '{}' — \
9031 expected drop | revoke | emit | redact | \
9032 quarantine | terminate | alert",
9033 node.name
9034 ),
9035 line: a_tok.line,
9036 column: a_tok.column,
9037 ..Default::default()
9038 });
9039 }
9040 node.action = a;
9041 }
9042 "scope" => {
9043 let s_tok = self.consume_any_ident_or_kw()?;
9044 let s = s_tok.value;
9045 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
9046 return Err(ParseError {
9047 message: format!(
9048 "Invalid scope '{s}' in reflex '{}' — \
9049 expected tenant | flow | global",
9050 node.name
9051 ),
9052 line: s_tok.line,
9053 column: s_tok.column,
9054 ..Default::default()
9055 });
9056 }
9057 node.scope = s;
9058 }
9059 "sla" => {
9060 let t = self.current().clone();
9061 match t.ttype {
9062 TokenType::Duration | TokenType::StringLit => {
9063 self.advance();
9064 node.sla = t.value;
9065 }
9066 _ => node.sla = self.consume_any_ident_or_kw()?.value,
9067 }
9068 }
9069 _ => self.skip_value(),
9070 }
9071 }
9072 self.consume(TokenType::RBrace)?;
9073 Ok(node)
9074 }
9075
9076 fn parse_heal(&mut self) -> Result<HealDefinition, ParseError> {
9078 let tok = self.consume(TokenType::Heal)?;
9079 let name = self.consume(TokenType::Identifier)?.value;
9080 let mut node = HealDefinition {
9081 name,
9082 source: String::new(),
9083 on_level: "doubt".to_string(),
9084 mode: "human_in_loop".to_string(),
9085 scope: String::new(),
9086 review_sla: String::new(),
9087 shield_ref: String::new(),
9088 max_patches: 3,
9089 loc: Loc {
9090 line: tok.line,
9091 column: tok.column,
9092 },
9093 leading_trivia: Vec::new(),
9094 trailing_trivia: Vec::new(),
9095 };
9096 self.consume(TokenType::LBrace)?;
9097 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9098 let field_name = self.current().value.clone();
9099 self.advance();
9100 if !self.check(TokenType::Colon) {
9101 if self.check(TokenType::LBrace) {
9102 self.skip_braced_block()?;
9103 }
9104 continue;
9105 }
9106 self.advance();
9107 match field_name.as_str() {
9108 "source" => node.source = self.consume_any_ident_or_kw()?.value,
9109 "on_level" => {
9110 let l_tok = self.consume_any_ident_or_kw()?;
9111 let l = l_tok.value;
9112 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
9113 return Err(ParseError {
9114 message: format!(
9115 "Invalid on_level '{l}' in heal '{}' — \
9116 expected know | believe | speculate | doubt",
9117 node.name
9118 ),
9119 line: l_tok.line,
9120 column: l_tok.column,
9121 ..Default::default()
9122 });
9123 }
9124 node.on_level = l;
9125 }
9126 "mode" => {
9127 let m_tok = self.consume_any_ident_or_kw()?;
9128 let m = m_tok.value;
9129 if !matches!(m.as_str(), "audit_only" | "human_in_loop" | "adversarial") {
9130 return Err(ParseError {
9131 message: format!(
9132 "Invalid mode '{m}' in heal '{}' — \
9133 expected audit_only | human_in_loop | adversarial",
9134 node.name
9135 ),
9136 line: m_tok.line,
9137 column: m_tok.column,
9138 ..Default::default()
9139 });
9140 }
9141 node.mode = m;
9142 }
9143 "scope" => {
9144 let s_tok = self.consume_any_ident_or_kw()?;
9145 let s = s_tok.value;
9146 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
9147 return Err(ParseError {
9148 message: format!(
9149 "Invalid scope '{s}' in heal '{}' — \
9150 expected tenant | flow | global",
9151 node.name
9152 ),
9153 line: s_tok.line,
9154 column: s_tok.column,
9155 ..Default::default()
9156 });
9157 }
9158 node.scope = s;
9159 }
9160 "review_sla" => {
9161 let t = self.current().clone();
9162 match t.ttype {
9163 TokenType::Duration | TokenType::StringLit => {
9164 self.advance();
9165 node.review_sla = t.value;
9166 }
9167 _ => node.review_sla = self.consume_any_ident_or_kw()?.value,
9168 }
9169 }
9170 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
9171 "max_patches" => {
9172 if let Some(v) = self.parse_optional_int() {
9173 node.max_patches = v;
9174 }
9175 }
9176 _ => self.skip_value(),
9177 }
9178 }
9179 self.consume(TokenType::RBrace)?;
9180 Ok(node)
9181 }
9182
9183 fn parse_component(&mut self) -> Result<ComponentDefinition, ParseError> {
9187 let tok = self.consume(TokenType::Component)?;
9188 let name = self.consume(TokenType::Identifier)?.value;
9189 let mut node = ComponentDefinition {
9190 name,
9191 renders: String::new(),
9192 via_shield: String::new(),
9193 on_interact: String::new(),
9194 render_hint: "custom".to_string(),
9195 loc: Loc {
9196 line: tok.line,
9197 column: tok.column,
9198 },
9199 leading_trivia: Vec::new(),
9200 trailing_trivia: Vec::new(),
9201 };
9202 self.consume(TokenType::LBrace)?;
9203 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9204 let field_name = self.current().value.clone();
9205 self.advance();
9206 if !self.check(TokenType::Colon) {
9207 if self.check(TokenType::LBrace) {
9208 self.skip_braced_block()?;
9209 }
9210 continue;
9211 }
9212 self.advance();
9213 match field_name.as_str() {
9214 "renders" => node.renders = self.consume_any_ident_or_kw()?.value,
9215 "via_shield" => node.via_shield = self.consume_any_ident_or_kw()?.value,
9216 "on_interact" => node.on_interact = self.consume_any_ident_or_kw()?.value,
9217 "render_hint" => {
9218 let h_tok = self.consume_any_ident_or_kw()?;
9219 let h = h_tok.value;
9220 if !matches!(h.as_str(), "card" | "list" | "form" | "chart" | "custom") {
9221 return Err(ParseError {
9222 message: format!(
9223 "Invalid render_hint '{h}' in component '{}' — \
9224 expected card | list | form | chart | custom",
9225 node.name
9226 ),
9227 line: h_tok.line,
9228 column: h_tok.column,
9229 ..Default::default()
9230 });
9231 }
9232 node.render_hint = h;
9233 }
9234 _ => self.skip_value(),
9235 }
9236 }
9237 self.consume(TokenType::RBrace)?;
9238 Ok(node)
9239 }
9240
9241 fn parse_view(&mut self) -> Result<ViewDefinition, ParseError> {
9243 let tok = self.consume(TokenType::View)?;
9244 let name = self.consume(TokenType::Identifier)?.value;
9245 let mut node = ViewDefinition {
9246 name,
9247 title: String::new(),
9248 components: Vec::new(),
9249 route: String::new(),
9250 loc: Loc {
9251 line: tok.line,
9252 column: tok.column,
9253 },
9254 leading_trivia: Vec::new(),
9255 trailing_trivia: Vec::new(),
9256 };
9257 self.consume(TokenType::LBrace)?;
9258 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9259 let field_name = self.current().value.clone();
9260 self.advance();
9261 if !self.check(TokenType::Colon) {
9262 if self.check(TokenType::LBrace) {
9263 self.skip_braced_block()?;
9264 }
9265 continue;
9266 }
9267 self.advance();
9268 match field_name.as_str() {
9269 "title" => node.title = self.consume(TokenType::StringLit)?.value,
9270 "components" => node.components = self.parse_bracketed_identifiers()?,
9271 "route" => node.route = self.consume(TokenType::StringLit)?.value,
9272 _ => self.skip_value(),
9273 }
9274 }
9275 self.consume(TokenType::RBrace)?;
9276 Ok(node)
9277 }
9278
9279 fn parse_axonendpoint(&mut self) -> Result<AxonEndpointDefinition, ParseError> {
9280 let tok = self.consume(TokenType::AxonEndpoint)?;
9281 let name = self.consume(TokenType::Identifier)?.value;
9282 let mut node = AxonEndpointDefinition {
9283 name,
9284 method: String::new(),
9285 path: String::new(),
9286 body_type: String::new(),
9287 execute_flow: String::new(),
9288 output_type: String::new(),
9289 shield_ref: String::new(),
9290 cors_ref: String::new(),
9293 retries: None,
9294 timeout: String::new(),
9295 compliance: Vec::new(),
9296 transport: "json".to_string(),
9298 keepalive: String::new(),
9299 transport_explicit: false,
9305 implicit_transport: String::new(),
9306 requires_capabilities: Vec::new(),
9308 public: false,
9311 replay_explicit: false,
9315 replay: false,
9316 transport_dialect: String::new(),
9321 has_algebraic_stream_effect: false,
9327 backend: String::new(),
9332 path_params: Vec::new(),
9337 query_params: Vec::new(),
9342 loc: Loc {
9343 line: tok.line,
9344 column: tok.column,
9345 },
9346 leading_trivia: Vec::new(),
9347 trailing_trivia: Vec::new(),
9348 };
9349 self.consume(TokenType::LBrace)?;
9350 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9351 let field_name = self.current().value.clone();
9352 self.advance();
9353 if self.check(TokenType::Colon) {
9354 self.advance();
9355 match field_name.as_str() {
9356 "method" => {
9357 let value_tok = self.consume_any_ident_or_kw()?;
9364 let value_upper = value_tok.value.to_uppercase();
9365 if !axonendpoint_is_valid_method(&value_upper) {
9366 let hint = crate::smart_suggest::suggest_for(
9367 &value_upper,
9368 AXONENDPOINT_METHOD_VALUES,
9369 );
9370 let base = format!(
9371 "Invalid method '{}' in axonendpoint '{}'.",
9372 value_tok.value, node.name
9373 );
9374 let message = if hint.is_empty() {
9375 format!(
9376 "{base} expected GET | POST | PUT | DELETE | PATCH, found {}",
9377 value_tok.value
9378 )
9379 } else {
9380 format!(
9381 "{base} {hint} (expected GET | POST | PUT | DELETE | PATCH, found {})",
9382 value_tok.value
9383 )
9384 };
9385 return Err(ParseError {
9386 message,
9387 line: value_tok.line,
9388 column: value_tok.column,
9389 ..Default::default()
9390 });
9391 }
9392 node.method = value_upper;
9393 }
9394 "path" => {
9395 node.path = self.consume(TokenType::StringLit)?.value.clone();
9396 match extract_path_param_names(&node.path) {
9404 Ok(names) => node.path_params = names,
9405 Err(dup) => {
9406 let cur = self.current().clone();
9407 return Err(ParseError {
9408 message: format!(
9409 "axonendpoint '{}' declares path '{}' \
9410 containing duplicate placeholder '{{{}}}'. \
9411 Each `{{name}}` in a `path:` must be \
9412 unique — the runtime cannot bind two \
9413 path segments to the same name (Fase 37.y D1).",
9414 node.name, node.path, dup,
9415 ),
9416 line: cur.line,
9417 column: cur.column,
9418 ..Default::default()
9419 });
9420 }
9421 }
9422 },
9423 "body" => node.body_type = self.consume_any_ident_or_kw()?.value.clone(),
9424 "query" => {
9425 let lbrace_tok = self.consume(TokenType::LBrace)?;
9444 let block_line = lbrace_tok.line;
9445 if !node.query_params.is_empty() {
9446 return Err(ParseError {
9447 message: format!(
9448 "axonendpoint '{}' declares `query: {{ … }}` \
9449 more than once. The query-parameter block \
9450 is unique per endpoint; combine all params \
9451 into a single block (Fase 37.y D2).",
9452 node.name,
9453 ),
9454 line: lbrace_tok.line,
9455 column: lbrace_tok.column,
9456 ..Default::default()
9457 });
9458 }
9459 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9460 let name_tok = self.consume(TokenType::Identifier)?;
9461 let field_name = name_tok.value.clone();
9462 if node
9464 .query_params
9465 .iter()
9466 .any(|f| f.name == field_name)
9467 {
9468 return Err(ParseError {
9469 message: format!(
9470 "axonendpoint '{}' declares duplicate \
9471 query param '{}' inside `query: {{ … }}`. \
9472 Each name must appear at most once \
9473 (Fase 37.y D2).",
9474 node.name, field_name,
9475 ),
9476 line: name_tok.line,
9477 column: name_tok.column,
9478 ..Default::default()
9479 });
9480 }
9481 self.consume(TokenType::Colon)?;
9482 let type_expr = self.parse_type_expr()?;
9483 if !type_expr.generic_param.is_empty() {
9495 let canonical_hint = if type_expr.name == "Optional" {
9496 format!(
9497 " Use `{}?` (the `?` suffix) for an \
9498 optional query param instead of \
9499 `Optional<{}>`.",
9500 type_expr.generic_param,
9501 type_expr.generic_param,
9502 )
9503 } else if type_expr.name == "List" {
9504 " Multi-value query params (e.g. `?tag=a&tag=b`) \
9505 are honest-deferred from v1.38.5; bind a \
9506 single-value `Text` query param and parse \
9507 the value inside the flow."
9508 .to_string()
9509 } else {
9510 String::new()
9511 };
9512 return Err(ParseError {
9513 message: format!(
9514 "axonendpoint '{}' query param '{}' uses \
9515 a generic type `{}<{}>`. Query params \
9516 take a primitive type from the closed \
9517 catalog ({}); the `?` suffix marks \
9518 optional.{} (Fase 37.y D2).",
9519 node.name,
9520 field_name,
9521 type_expr.name,
9522 type_expr.generic_param,
9523 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | "),
9524 canonical_hint,
9525 ),
9526 line: type_expr.loc.line,
9527 column: type_expr.loc.column,
9528 ..Default::default()
9529 });
9530 }
9531 if !axonendpoint_is_valid_query_param_type(&type_expr.name) {
9535 let hint = crate::smart_suggest::suggest_for(
9542 &type_expr.name,
9543 AXONENDPOINT_QUERY_PARAM_TYPES,
9544 );
9545 let hint_text = if hint.is_empty() {
9546 format!(
9547 " Expected one of: {}.",
9548 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
9549 )
9550 } else {
9551 format!(
9552 " {} Expected one of: {}.",
9553 hint,
9554 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
9555 )
9556 };
9557 return Err(ParseError {
9558 message: format!(
9559 "axonendpoint '{}' query param '{}' has \
9560 unsupported type '{}'.{} (Fase 37.y D2).",
9561 node.name, field_name, type_expr.name,
9562 hint_text,
9563 ),
9564 line: type_expr.loc.line,
9565 column: type_expr.loc.column,
9566 ..Default::default()
9567 });
9568 }
9569 node.query_params.push(TypeField {
9570 name: field_name,
9571 type_expr,
9572 loc: Loc {
9573 line: name_tok.line,
9574 column: name_tok.column,
9575 },
9576 });
9577 if self.check(TokenType::Comma) {
9583 self.advance();
9584 }
9585 let _ = block_line; }
9587 self.consume(TokenType::RBrace)?;
9588 },
9589 "execute" => node.execute_flow = self.consume_any_ident_or_kw()?.value.clone(),
9590 "output" => {
9591 node.output_type = self.parse_output_type_string()?;
9609 }
9610 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
9611 "cors" => node.cors_ref = self.consume_any_ident_or_kw()?.value.clone(),
9613 "retries" => node.retries = self.parse_optional_int(),
9614 "timeout" => {
9615 let t = self.current().clone();
9616 self.advance();
9617 node.timeout = t.value.clone();
9618 }
9619 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
9620 "replay" => {
9621 let value_tok = self.consume(TokenType::Bool)?;
9628 node.replay = value_tok.value.eq_ignore_ascii_case("true");
9629 node.replay_explicit = true;
9630 }
9631 "public" => {
9637 let value_tok = self.consume(TokenType::Bool)?;
9638 node.public = value_tok.value.eq_ignore_ascii_case("true");
9639 }
9640 "requires" => {
9641 let bracket_tok = self.current().clone();
9648 let items = self.parse_bracketed_dot_identifiers()?;
9649 for slug in &items {
9650 if !is_valid_capability_slug(slug) {
9651 return Err(ParseError {
9652 message: format!(
9653 "Invalid capability slug '{slug}' in axonendpoint '{}' \
9654 `requires:`. Capability slugs must match \
9655 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
9656 lowercase identifiers starting with a letter. Examples: \
9657 `admin`, `legal.read`, `hipaa.phi.read`.",
9658 node.name
9659 ),
9660 line: bracket_tok.line,
9661 column: bracket_tok.column,
9662 ..Default::default()
9663 });
9664 }
9665 }
9666 node.requires_capabilities = items;
9667 }
9668 "transport" => {
9672 let value_tok = self.consume_any_ident_or_kw()?;
9673 let value = &value_tok.value;
9674 if !axonendpoint_is_valid_transport(value) {
9675 let hint = crate::smart_suggest::suggest_for(
9676 value,
9677 AXONENDPOINT_TRANSPORT_VALUES,
9678 );
9679 let base = format!(
9680 "Invalid transport '{}' in axonendpoint '{}'.",
9681 value, node.name
9682 );
9683 let message = if hint.is_empty() {
9684 format!("{base} expected json | sse | ndjson, found {value}")
9685 } else {
9686 format!(
9687 "{base} {hint} (expected json | sse | ndjson, found {value})"
9688 )
9689 };
9690 return Err(ParseError {
9691 message,
9692 line: value_tok.line,
9693 column: value_tok.column,
9694 ..Default::default()
9695 });
9696 }
9697 node.transport = value.clone();
9698 node.transport_explicit = true;
9703 if self.check(TokenType::LParen) {
9710 if value != "sse" {
9711 let tok = self.current().clone();
9712 return Err(ParseError {
9713 message: format!(
9714 "Dialect parametrization \
9715 `transport: {value}(<dialect>)` is \
9716 only valid for `sse`; got \
9717 `{value}` in axonendpoint '{}'.",
9718 node.name
9719 ),
9720 line: tok.line,
9721 column: tok.column,
9722 ..Default::default()
9723 });
9724 }
9725 self.advance(); let dialect_tok = self.consume_any_ident_or_kw()?;
9727 let dialect = dialect_tok.value.clone();
9728 if !AXONENDPOINT_TRANSPORT_DIALECTS
9729 .iter()
9730 .any(|&d| d == dialect)
9731 {
9732 let hint = crate::smart_suggest::suggest_for(
9733 &dialect,
9734 AXONENDPOINT_TRANSPORT_DIALECTS,
9735 );
9736 let base = format!(
9737 "Invalid SSE dialect '{dialect}' in axonendpoint '{}'.",
9738 node.name
9739 );
9740 let message = if hint.is_empty() {
9741 format!(
9742 "{base} expected axon | openai | kimi | glm | anthropic, found {dialect}"
9743 )
9744 } else {
9745 format!(
9746 "{base} {hint} (expected axon | openai | kimi | glm | anthropic, found {dialect})"
9747 )
9748 };
9749 return Err(ParseError {
9750 message,
9751 line: dialect_tok.line,
9752 column: dialect_tok.column,
9753 ..Default::default()
9754 });
9755 }
9756 let rparen_tok = self.current().clone();
9758 if !self.check(TokenType::RParen) {
9759 return Err(ParseError {
9760 message: format!(
9761 "Expected `)` after dialect name \
9762 in axonendpoint '{}' \
9763 (transport: sse(<dialect>) grammar).",
9764 node.name
9765 ),
9766 line: rparen_tok.line,
9767 column: rparen_tok.column,
9768 ..Default::default()
9769 });
9770 }
9771 self.advance(); node.transport_dialect = dialect;
9773 }
9774 }
9775 "keepalive" => {
9776 let value_tok = self.current().clone();
9780 self.advance();
9781 let value = &value_tok.value;
9782 if !axonendpoint_is_valid_keepalive(value) {
9783 let hint = crate::smart_suggest::suggest_for(
9784 value,
9785 AXONENDPOINT_KEEPALIVE_VALUES,
9786 );
9787 let base = format!(
9788 "Invalid keepalive '{}' in axonendpoint '{}'.",
9789 value, node.name
9790 );
9791 let message = if hint.is_empty() {
9792 format!("{base} expected 5s | 15s | 30s | 60s, found {value}")
9793 } else {
9794 format!(
9795 "{base} {hint} (expected 5s | 15s | 30s | 60s, found {value})"
9796 )
9797 };
9798 return Err(ParseError {
9799 message,
9800 line: value_tok.line,
9801 column: value_tok.column,
9802 ..Default::default()
9803 });
9804 }
9805 node.keepalive = value.clone();
9806 }
9807 "backend" => {
9808 let value_tok = self.consume_any_ident_or_kw()?;
9816 let value = &value_tok.value;
9817 if !axonendpoint_is_valid_backend(value) {
9818 let hint = crate::smart_suggest::suggest_for(
9819 value,
9820 AXONENDPOINT_BACKEND_VALUES,
9821 );
9822 let expected = AXONENDPOINT_BACKEND_VALUES.join(" | ");
9823 let base = format!(
9824 "Invalid backend '{}' in axonendpoint '{}'.",
9825 value, node.name
9826 );
9827 let message = if hint.is_empty() {
9828 format!("{base} expected {expected}, found {value}")
9829 } else {
9830 format!(
9831 "{base} {hint} (expected {expected}, found {value})"
9832 )
9833 };
9834 return Err(ParseError {
9835 message,
9836 line: value_tok.line,
9837 column: value_tok.column,
9838 ..Default::default()
9839 });
9840 }
9841 node.backend = value.clone();
9842 }
9843 _ => self.skip_value(),
9844 }
9845 } else if self.check(TokenType::LBrace) {
9846 self.skip_braced_block()?;
9847 }
9848 }
9849 self.consume(TokenType::RBrace)?;
9850 Ok(node)
9851 }
9852
9853 fn parse_optional_int(&mut self) -> Option<i64> {
9856 let tok = self.current().clone();
9857 match tok.ttype {
9858 TokenType::Integer => {
9859 self.advance();
9860 tok.value.parse::<i64>().ok()
9861 }
9862 _ => {
9863 self.advance();
9864 None
9865 }
9866 }
9867 }
9868
9869 fn parse_optional_float(&mut self) -> Option<f64> {
9870 let tok = self.current().clone();
9871 match tok.ttype {
9872 TokenType::Float | TokenType::Integer => {
9873 self.advance();
9874 tok.value.parse::<f64>().ok()
9875 }
9876 _ => {
9877 self.advance();
9878 None
9879 }
9880 }
9881 }
9882
9883 fn parse_lambda_data(&mut self) -> Result<LambdaDataDefinition, ParseError> {
9886 let tok = self.consume(TokenType::Lambda)?;
9887 let name = self.consume(TokenType::Identifier)?;
9888 self.consume(TokenType::LBrace)?;
9889
9890 let mut node = LambdaDataDefinition {
9891 name: name.value.clone(),
9892 ontology: String::new(),
9893 certainty: 1.0,
9894 temporal_frame_start: String::new(),
9895 temporal_frame_end: String::new(),
9896 provenance: String::new(),
9897 derivation: String::new(),
9898 loc: Loc {
9899 line: tok.line,
9900 column: tok.column,
9901 },
9902 leading_trivia: Vec::new(),
9903 trailing_trivia: Vec::new(),
9904 };
9905
9906 while !self.check(TokenType::RBrace) {
9907 let field = self.current().clone();
9908 match field.ttype {
9909 TokenType::Ontology => {
9910 self.advance();
9911 self.consume(TokenType::Colon)?;
9912 node.ontology = self.consume(TokenType::StringLit)?.value.clone();
9913 }
9914 TokenType::Certainty => {
9915 self.advance();
9916 self.consume(TokenType::Colon)?;
9917 let val = self.current().clone();
9918 match val.ttype {
9919 TokenType::Float => {
9920 self.advance();
9921 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
9922 }
9923 TokenType::Integer => {
9924 self.advance();
9925 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
9926 }
9927 _ => {
9928 return Err(ParseError {
9929 message: format!(
9930 "Expected number for certainty, got '{}'",
9931 val.value
9932 ),
9933 line: val.line,
9934 column: val.column,
9935 ..Default::default()
9936 });
9937 }
9938 }
9939 }
9940 TokenType::TemporalFrame => {
9941 self.advance();
9942 self.consume(TokenType::Colon)?;
9943 node.temporal_frame_start = self.consume(TokenType::StringLit)?.value.clone();
9944 if self.check(TokenType::StringLit) {
9946 node.temporal_frame_end = self.consume(TokenType::StringLit)?.value.clone();
9947 }
9948 }
9949 TokenType::Provenance => {
9950 self.advance();
9951 self.consume(TokenType::Colon)?;
9952 node.provenance = self.consume(TokenType::StringLit)?.value.clone();
9953 }
9954 TokenType::Derivation => {
9955 self.advance();
9956 self.consume(TokenType::Colon)?;
9957 let d = self.current().clone();
9958 self.advance();
9959 node.derivation = d.value.clone();
9960 }
9961 _ => {
9962 self.advance();
9964 if self.check(TokenType::Colon) {
9965 self.advance();
9966 self.skip_value();
9967 }
9968 }
9969 }
9970 }
9971
9972 self.consume(TokenType::RBrace)?;
9973 Ok(node)
9974 }
9975
9976 fn parse_lambda_data_apply(&mut self) -> Result<LambdaDataApplyNode, ParseError> {
9977 let tok = self.consume(TokenType::Lambda)?;
9978 let lambda_name = self.consume(TokenType::Identifier)?;
9979
9980 let on_tok = self.current().clone();
9982 self.advance();
9983 if on_tok.value != "on" {
9984 return Err(ParseError {
9985 message: format!(
9986 "Expected 'on' after lambda data name in flow step, got '{}'",
9987 on_tok.value
9988 ),
9989 line: on_tok.line,
9990 column: on_tok.column,
9991 ..Default::default()
9992 });
9993 }
9994
9995 let target = self.current().clone();
9996 self.advance();
9997
9998 let mut output_type = String::new();
9999 if self.check(TokenType::Arrow) {
10000 self.advance();
10001 output_type = self.consume(TokenType::Identifier)?.value.clone();
10002 }
10003
10004 Ok(LambdaDataApplyNode {
10005 lambda_data_name: lambda_name.value.clone(),
10006 target: target.value.clone(),
10007 output_type,
10008 loc: Loc {
10009 line: tok.line,
10010 column: tok.column,
10011 },
10012 })
10013 }
10014
10015 fn parse_generic_declaration(&mut self) -> Result<Declaration, ParseError> {
10018 let kw_tok = self.current().clone();
10019 self.advance(); let name = if self.current().ttype == TokenType::Identifier {
10023 let n = self.current().value.clone();
10024 self.advance();
10025 n
10026 } else if !self.check(TokenType::LBrace)
10027 && !self.check(TokenType::LParen)
10028 && !self.check(TokenType::Eof)
10029 && self
10030 .current()
10031 .value
10032 .chars()
10033 .all(|c| c.is_alphanumeric() || c == '_')
10034 {
10035 let n = self.current().value.clone();
10036 self.advance();
10037 n
10038 } else {
10039 String::new()
10040 };
10041
10042 if self.check(TokenType::LParen) {
10044 self.advance();
10045 let mut depth = 1u32;
10046 while depth > 0 && !self.check(TokenType::Eof) {
10047 if self.check(TokenType::LParen) {
10048 depth += 1;
10049 } else if self.check(TokenType::RParen) {
10050 depth -= 1;
10051 }
10052 self.advance();
10053 }
10054 }
10055
10056 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
10058 if self.check(TokenType::Eof) {
10059 break;
10060 }
10061 self.advance();
10062 }
10063
10064 if self.check(TokenType::LBrace) {
10066 self.skip_braced_block()?;
10067 }
10068
10069 Ok(Declaration::Generic(GenericDeclaration {
10070 keyword: kw_tok.value,
10071 name,
10072 loc: Loc {
10073 line: kw_tok.line,
10074 column: kw_tok.column,
10075 },
10076 leading_trivia: Vec::new(),
10077 trailing_trivia: Vec::new(),
10078 }))
10079 }
10080
10081 fn parse_channel(&mut self) -> Result<ChannelDefinition, ParseError> {
10089 let tok = self.consume(TokenType::Channel)?;
10090 let name = self.consume(TokenType::Identifier)?.value;
10091 let mut node = ChannelDefinition {
10092 name: name.clone(),
10093 message: String::new(),
10094 qos: "at_least_once".to_string(),
10095 lifetime: "affine".to_string(),
10096 persistence: "ephemeral".to_string(),
10097 shield_ref: String::new(),
10098 loc: Loc {
10099 line: tok.line,
10100 column: tok.column,
10101 },
10102 leading_trivia: Vec::new(),
10103 trailing_trivia: Vec::new(),
10104 };
10105 self.consume(TokenType::LBrace)?;
10106 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10107 let field_tok = self.current().clone();
10108 let field_name = field_tok.value.clone();
10109 self.advance();
10110 if !self.check(TokenType::Colon) {
10111 if self.check(TokenType::LBrace) {
10112 self.skip_braced_block()?;
10113 }
10114 continue;
10115 }
10116 self.advance();
10117 match field_name.as_str() {
10118 "message" => node.message = self.parse_channel_message_type()?,
10119 "qos" => {
10120 let q_tok = self.consume_any_ident_or_kw()?;
10121 if !matches!(
10122 q_tok.value.as_str(),
10123 "at_most_once" | "at_least_once" | "exactly_once" | "broadcast" | "queue"
10124 ) {
10125 return Err(ParseError {
10126 message: format!(
10127 "Invalid qos '{}' in channel '{}' — \
10128 expected at_most_once | at_least_once | \
10129 exactly_once | broadcast | queue",
10130 q_tok.value, name
10131 ),
10132 line: q_tok.line,
10133 column: q_tok.column,
10134 ..Default::default()
10135 });
10136 }
10137 node.qos = q_tok.value;
10138 }
10139 "lifetime" => {
10140 let lt_tok = self.consume_any_ident_or_kw()?;
10141 if !matches!(lt_tok.value.as_str(), "linear" | "affine" | "persistent") {
10142 return Err(ParseError {
10143 message: format!(
10144 "Invalid lifetime '{}' in channel '{}' — \
10145 expected linear | affine | persistent",
10146 lt_tok.value, name
10147 ),
10148 line: lt_tok.line,
10149 column: lt_tok.column,
10150 ..Default::default()
10151 });
10152 }
10153 node.lifetime = lt_tok.value;
10154 }
10155 "persistence" => {
10156 let p_tok = self.consume_any_ident_or_kw()?;
10157 if !matches!(p_tok.value.as_str(), "ephemeral" | "persistent_axonstore") {
10158 return Err(ParseError {
10159 message: format!(
10160 "Invalid persistence '{}' in channel '{}' — \
10161 expected ephemeral | persistent_axonstore",
10162 p_tok.value, name
10163 ),
10164 line: p_tok.line,
10165 column: p_tok.column,
10166 ..Default::default()
10167 });
10168 }
10169 node.persistence = p_tok.value;
10170 }
10171 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
10172 _ => self.skip_value(),
10173 }
10174 }
10175 self.consume(TokenType::RBrace)?;
10176 Ok(node)
10177 }
10178
10179 fn parse_channel_message_type(&mut self) -> Result<String, ParseError> {
10182 let head = self.consume(TokenType::Identifier)?;
10183 let mut spelling = head.value;
10184 if self.check(TokenType::Lt) {
10185 self.advance();
10186 let inner = self.parse_channel_message_type()?;
10187 self.consume(TokenType::Gt)?;
10188 spelling = format!("{}<{}>", spelling, inner);
10189 }
10190 Ok(spelling)
10191 }
10192
10193 fn parse_emit_step(&mut self) -> Result<FlowStep, ParseError> {
10199 let tok = self.consume(TokenType::Emit)?;
10200 let channel = self.consume(TokenType::Identifier)?.value;
10201 self.consume(TokenType::LParen)?;
10202 let value = self.parse_emit_value_ref()?;
10203 self.consume(TokenType::RParen)?;
10204 Ok(FlowStep::Emit(EmitStatement {
10205 channel_ref: channel,
10206 value_ref: value,
10207 loc: Loc {
10208 line: tok.line,
10209 column: tok.column,
10210 },
10211 }))
10212 }
10213
10214 fn parse_mint_step(&mut self) -> Result<FlowStep, ParseError> {
10220 let tok = self.consume(TokenType::Mint)?;
10221 let credential_ref = self.consume(TokenType::Identifier)?.value;
10222 self.consume(TokenType::As)?;
10223 let binding = self.consume(TokenType::Identifier)?.value;
10224 Ok(FlowStep::Mint(MintStep {
10225 credential_ref,
10226 binding,
10227 loc: Loc {
10228 line: tok.line,
10229 column: tok.column,
10230 },
10231 }))
10232 }
10233
10234 fn parse_rotate_step(&mut self) -> Result<FlowStep, ParseError> {
10249 let tok = self.consume(TokenType::Rotate)?;
10250 let store_ref = self.consume(TokenType::Identifier)?.value;
10251 let mut where_expr = String::new();
10252 if self.check(TokenType::Where) {
10253 self.advance();
10254 where_expr = self.consume(TokenType::StringLit)?.value.clone();
10255 }
10256 let with_tok = self.current().clone();
10257 if with_tok.value != "with" {
10258 return Err(ParseError {
10259 message: format!(
10260 "Expected `with <Tool>` after `rotate {store_ref}{}`, found '{}'. \
10261 A rotation names the tool that performs the renewal exchange: \
10262 `rotate {store_ref} [where \"<filter>\"] with <Tool> as <binding>`.",
10263 if where_expr.is_empty() { "" } else { " where …" },
10264 with_tok.value
10265 ),
10266 line: with_tok.line,
10267 column: with_tok.column,
10268 ..Default::default()
10269 });
10270 }
10271 self.advance();
10272 let tool_ref = self.consume(TokenType::Identifier)?.value;
10273 self.consume(TokenType::As)?;
10274 let binding = self.consume(TokenType::Identifier)?.value;
10275 Ok(FlowStep::Rotate(RotateStep {
10276 store_ref,
10277 where_expr,
10278 tool_ref,
10279 binding,
10280 loc: Loc {
10281 line: tok.line,
10282 column: tok.column,
10283 },
10284 }))
10285 }
10286
10287 fn parse_emit_value_ref(&mut self) -> Result<String, ParseError> {
10303 let head = self.consume(TokenType::Identifier)?.value;
10304 let mut parts = vec![head];
10305 while self.check(TokenType::Dot) {
10306 self.advance(); let next_tok = self.current().clone();
10308 let valid = !next_tok.value.is_empty()
10309 && next_tok.value.as_bytes()[0].is_ascii_alphabetic()
10310 || next_tok.value.starts_with('_');
10311 if !valid {
10312 return Err(ParseError {
10313 message: format!(
10314 "Expected identifier or keyword after '.' in dotted \
10315 access, found {:?}",
10316 next_tok.value
10317 ),
10318 line: next_tok.line,
10319 column: next_tok.column,
10320 ..Default::default()
10321 });
10322 }
10323 self.advance();
10324 parts.push(next_tok.value);
10325 }
10326 Ok(parts.join("."))
10327 }
10328
10329 fn parse_publish_step(&mut self) -> Result<FlowStep, ParseError> {
10331 let tok = self.consume(TokenType::Publish)?;
10332 let channel = self.consume(TokenType::Identifier)?.value;
10333 self.consume(TokenType::Within)?;
10334 let shield = self.consume(TokenType::Identifier)?.value;
10335 Ok(FlowStep::Publish(PublishStatement {
10336 channel_ref: channel,
10337 shield_ref: shield,
10338 loc: Loc {
10339 line: tok.line,
10340 column: tok.column,
10341 },
10342 }))
10343 }
10344
10345 fn parse_discover_step(&mut self) -> Result<FlowStep, ParseError> {
10347 let tok = self.consume(TokenType::Discover)?;
10348 let cap = self.consume(TokenType::Identifier)?.value;
10349 self.consume(TokenType::As)?;
10350 let alias = self.consume(TokenType::Identifier)?.value;
10351 Ok(FlowStep::Discover(DiscoverStatement {
10352 capability_ref: cap,
10353 alias,
10354 loc: Loc {
10355 line: tok.line,
10356 column: tok.column,
10357 },
10358 }))
10359 }
10360}
10361
10362#[cfg(test)]
10365mod fase13_parser_tests {
10366 use super::*;
10367 use crate::lexer::Lexer;
10368
10369 fn parse(src: &str) -> Result<Program, ParseError> {
10370 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
10371 Parser::new(tokens).parse()
10372 }
10373
10374 #[test]
10375 fn channel_full_parses() {
10376 let src = r#"channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }"#;
10377 let prog = parse(src).expect("parse");
10378 match &prog.declarations[0] {
10379 Declaration::Channel(c) => {
10380 assert_eq!(c.name, "C");
10381 assert_eq!(c.message, "Order");
10382 assert_eq!(c.qos, "at_least_once");
10383 assert_eq!(c.lifetime, "affine");
10384 assert_eq!(c.persistence, "ephemeral");
10385 assert_eq!(c.shield_ref, "Gate");
10386 }
10387 _ => panic!("expected ChannelDefinition"),
10388 }
10389 }
10390
10391 #[test]
10392 fn channel_defaults_match_paper_d1() {
10393 let prog = parse("channel C { message: Order }").expect("parse");
10394 if let Declaration::Channel(c) = &prog.declarations[0] {
10395 assert_eq!(c.qos, "at_least_once"); assert_eq!(c.lifetime, "affine"); assert_eq!(c.persistence, "ephemeral");
10398 assert_eq!(c.shield_ref, "");
10399 } else {
10400 panic!("expected ChannelDefinition");
10401 }
10402 }
10403
10404 #[test]
10405 fn channel_second_order_message_type_parses() {
10406 let prog = parse("channel C { message: Channel<Order> }").expect("parse");
10407 if let Declaration::Channel(c) = &prog.declarations[0] {
10408 assert_eq!(c.message, "Channel<Order>");
10409 } else {
10410 panic!("expected ChannelDefinition");
10411 }
10412 }
10413
10414 #[test]
10415 fn channel_nested_channel_message_type_parses() {
10416 let prog = parse("channel C { message: Channel<Channel<Order>> }").expect("parse");
10417 if let Declaration::Channel(c) = &prog.declarations[0] {
10418 assert_eq!(c.message, "Channel<Channel<Order>>");
10419 } else {
10420 panic!("expected ChannelDefinition");
10421 }
10422 }
10423
10424 #[test]
10425 fn channel_invalid_qos_rejected() {
10426 let err = parse("channel C { message: T qos: bogus }").unwrap_err();
10427 assert!(err.message.contains("Invalid qos"), "got {}", err.message);
10428 }
10429
10430 #[test]
10431 fn channel_invalid_lifetime_rejected() {
10432 let err = parse("channel C { message: T lifetime: eternal }").unwrap_err();
10433 assert!(
10434 err.message.contains("Invalid lifetime"),
10435 "got {}",
10436 err.message
10437 );
10438 }
10439
10440 #[test]
10441 fn channel_invalid_persistence_rejected() {
10442 let err = parse("channel C { message: T persistence: forever }").unwrap_err();
10443 assert!(
10444 err.message.contains("Invalid persistence"),
10445 "got {}",
10446 err.message
10447 );
10448 }
10449
10450 #[test]
10451 fn emit_value_parses() {
10452 let src = "flow f() -> Out { emit C(payload) }";
10453 let prog = parse(src).expect("parse");
10454 if let Declaration::Flow(f) = &prog.declarations[0] {
10455 match &f.body[0] {
10456 FlowStep::Emit(e) => {
10457 assert_eq!(e.channel_ref, "C");
10458 assert_eq!(e.value_ref, "payload");
10459 }
10460 other => panic!("expected Emit, got {:?}", other),
10461 }
10462 } else {
10463 panic!("expected Flow");
10464 }
10465 }
10466
10467 #[test]
10468 fn publish_within_shield_parses() {
10469 let src = "flow f() -> Cap { publish C within Gate }";
10470 let prog = parse(src).expect("parse");
10471 if let Declaration::Flow(f) = &prog.declarations[0] {
10472 match &f.body[0] {
10473 FlowStep::Publish(p) => {
10474 assert_eq!(p.channel_ref, "C");
10475 assert_eq!(p.shield_ref, "Gate");
10476 }
10477 other => panic!("expected Publish, got {:?}", other),
10478 }
10479 } else {
10480 panic!("expected Flow");
10481 }
10482 }
10483
10484 #[test]
10485 fn discover_with_alias_parses() {
10486 let src = "flow f() -> Out { discover C as ch }";
10487 let prog = parse(src).expect("parse");
10488 if let Declaration::Flow(f) = &prog.declarations[0] {
10489 match &f.body[0] {
10490 FlowStep::Discover(d) => {
10491 assert_eq!(d.capability_ref, "C");
10492 assert_eq!(d.alias, "ch");
10493 }
10494 other => panic!("expected Discover, got {:?}", other),
10495 }
10496 } else {
10497 panic!("expected Flow");
10498 }
10499 }
10500
10501 #[test]
10502 fn listen_typed_ref_sets_flag_true() {
10503 let src = "daemon D() { goal: \"x\" listen C as ev { } }";
10504 let prog = parse(src).expect("parse");
10505 if let Declaration::Daemon(d) = &prog.declarations[0] {
10506 assert_eq!(d.listeners.len(), 1);
10507 assert_eq!(d.listeners[0].channel, "C");
10508 assert!(d.listeners[0].channel_is_ref, "typed ref ⇒ true");
10509 } else {
10510 panic!("expected Daemon");
10511 }
10512 }
10513
10514 #[test]
10515 fn listen_string_topic_legacy_flag_false() {
10516 let src = "daemon D() { goal: \"x\" listen \"orders\" as ev { } }";
10517 let prog = parse(src).expect("parse");
10518 if let Declaration::Daemon(d) = &prog.declarations[0] {
10519 assert_eq!(d.listeners.len(), 1);
10520 assert_eq!(d.listeners[0].channel, "orders");
10521 assert!(!d.listeners[0].channel_is_ref, "string topic ⇒ false");
10522 } else {
10523 panic!("expected Daemon");
10524 }
10525 }
10526
10527 fn extract_first_emit(prog: &Program) -> &EmitStatement {
10530 if let Declaration::Flow(f) = &prog.declarations[0] {
10531 if let FlowStep::Emit(e) = &f.body[0] {
10532 return e;
10533 }
10534 }
10535 panic!("expected emit statement at flow body[0]");
10536 }
10537
10538 #[test]
10539 fn emit_accepts_bare_identifier_value_ref() {
10540 let prog = parse("flow f() -> Out { emit Hello(payload) }").expect("parse");
10542 let emit = extract_first_emit(&prog);
10543 assert_eq!(emit.channel_ref, "Hello");
10544 assert_eq!(emit.value_ref, "payload");
10545 }
10546
10547 #[test]
10548 fn emit_accepts_two_segment_dotted_value_ref() {
10549 let prog = parse("flow f() -> Out { emit Hello(Build.output) }").expect("parse");
10551 let emit = extract_first_emit(&prog);
10552 assert_eq!(emit.value_ref, "Build.output");
10553 }
10554
10555 #[test]
10556 fn emit_accepts_three_segment_nested_dotted_value_ref() {
10557 let prog = parse("flow f() -> Out { emit Score(Analyze.result.score) }").expect("parse");
10558 let emit = extract_first_emit(&prog);
10559 assert_eq!(emit.value_ref, "Analyze.result.score");
10560 }
10561
10562 #[test]
10563 fn emit_dotted_with_trailing_dot_fails() {
10564 let result = parse("flow f() -> Out { emit Hello(Build.) }");
10566 assert!(result.is_err(), "expected parse error for trailing dot");
10567 }
10568}
10569
10570#[cfg(test)]
10573mod fase14a_declaration_trivia_tests {
10574 use super::*;
10575 use crate::lexer::Lexer;
10576 use crate::tokens::TriviaKind;
10577
10578 fn parse(src: &str) -> Program {
10579 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10580 Parser::new(toks).parse().expect("parse")
10581 }
10582
10583 #[test]
10584 fn no_comments_means_empty_trivia_per_decl() {
10585 let prog = parse("flow F() -> Out { }");
10586 assert_eq!(prog.declarations.len(), 1);
10587 assert_eq!(prog.declaration_trivia.len(), 1);
10588 assert!(prog.declaration_trivia[0].leading.is_empty());
10589 assert!(prog.declaration_trivia[0].trailing.is_empty());
10590 }
10591
10592 #[test]
10593 fn doc_line_comment_attaches_as_leading() {
10594 let prog = parse("/// Documents F\nflow F() -> Out { }");
10595 let triv = &prog.declaration_trivia[0];
10596 assert_eq!(triv.leading.len(), 1);
10597 assert_eq!(triv.leading[0].kind, TriviaKind::DocLine);
10598 assert!(triv.leading[0].is_doc());
10599 assert_eq!(triv.leading[0].text, "/// Documents F");
10600 }
10601
10602 #[test]
10603 fn regular_line_comment_attaches_as_leading() {
10604 let prog = parse("// header\nflow F() -> Out { }");
10605 let triv = &prog.declaration_trivia[0];
10606 assert_eq!(triv.leading.len(), 1);
10607 assert_eq!(triv.leading[0].kind, TriviaKind::Line);
10608 assert!(!triv.leading[0].is_doc());
10609 }
10610
10611 #[test]
10612 fn block_doc_comment_attaches_as_leading() {
10613 let prog = parse("/** Doc block */\nflow F() -> Out { }");
10614 let triv = &prog.declaration_trivia[0];
10615 assert_eq!(triv.leading[0].kind, TriviaKind::DocBlock);
10616 assert!(triv.leading[0].is_doc());
10617 }
10618
10619 #[test]
10620 fn multiple_comments_collected_in_source_order() {
10621 let src = "/// First\n/// Second\nflow F() -> Out { }";
10622 let prog = parse(src);
10623 let triv = &prog.declaration_trivia[0];
10624 assert_eq!(triv.leading.len(), 2);
10625 assert_eq!(triv.leading[0].text, "/// First");
10626 assert_eq!(triv.leading[1].text, "/// Second");
10627 }
10628
10629 #[test]
10630 fn three_decls_each_get_own_leading() {
10631 let src = "/// for A\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }\n/// for C\nflow C() -> Out { }";
10632 let prog = parse(src);
10633 assert_eq!(prog.declarations.len(), 3);
10634 assert_eq!(prog.declaration_trivia.len(), 3);
10635 for (idx, name) in ["A", "B", "C"].iter().enumerate() {
10636 let triv = &prog.declaration_trivia[idx];
10637 assert_eq!(triv.leading.len(), 1);
10638 assert_eq!(triv.leading[0].text, format!("/// for {name}"));
10639 }
10640 }
10641
10642 #[test]
10643 fn trailing_comment_attaches_to_last_token_of_decl() {
10644 let prog = parse("flow F() -> Out { } // tail");
10646 let triv = &prog.declaration_trivia[0];
10647 assert_eq!(triv.trailing.len(), 1);
10648 assert_eq!(triv.trailing[0].text, "// tail");
10649 }
10650
10651 #[test]
10652 fn mixed_doc_and_regular_preserve_order_between_decls() {
10653 let src = "/// doc for A\nflow A() -> Out { }\n\n// header line\n/// doc for B\nflow B() -> Out { }";
10654 let prog = parse(src);
10655 assert_eq!(prog.declarations.len(), 2);
10656 assert_eq!(prog.declaration_trivia[0].leading.len(), 1);
10658 assert_eq!(prog.declaration_trivia[1].leading.len(), 2);
10660 assert_eq!(prog.declaration_trivia[1].leading[0].text, "// header line");
10661 assert_eq!(prog.declaration_trivia[1].leading[1].text, "/// doc for B");
10662 }
10663
10664 #[test]
10665 fn parser_unaffected_by_comments_in_grammar_path() {
10666 let src =
10671 "// before flow\nflow /* between flow and name */ F() -> Out {\n // body comment\n}";
10672 let prog = parse(src);
10673 assert_eq!(prog.declarations.len(), 1);
10674 if let Declaration::Flow(f) = &prog.declarations[0] {
10675 assert_eq!(f.name, "F");
10676 } else {
10677 panic!("expected Flow declaration");
10678 }
10679 }
10680}
10681
10682#[cfg(test)]
10691mod fase14b_per_struct_trivia_tests {
10692 use super::*;
10693 use crate::lexer::Lexer;
10694 use crate::tokens::TriviaKind;
10695
10696 fn parse(src: &str) -> Program {
10697 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10698 Parser::new(toks).parse().expect("parse")
10699 }
10700
10701 #[test]
10702 fn flow_definition_carries_leading_trivia_directly() {
10703 let prog = parse("/// documents F\nflow F() -> Out { }");
10704 if let Declaration::Flow(f) = &prog.declarations[0] {
10705 assert_eq!(f.leading_trivia.len(), 1);
10706 assert_eq!(f.leading_trivia[0].kind, TriviaKind::DocLine);
10707 assert_eq!(f.leading_trivia[0].text, "/// documents F");
10708 assert!(f.trailing_trivia.is_empty());
10709 } else {
10710 panic!("expected Flow declaration");
10711 }
10712 }
10713
10714 #[test]
10715 fn flow_definition_carries_trailing_trivia_directly() {
10716 let prog = parse("flow F() -> Out { } // tail comment");
10717 if let Declaration::Flow(f) = &prog.declarations[0] {
10718 assert_eq!(f.trailing_trivia.len(), 1);
10719 assert_eq!(f.trailing_trivia[0].text, "// tail comment");
10720 } else {
10721 panic!("expected Flow declaration");
10722 }
10723 }
10724
10725 #[test]
10726 fn channel_definition_carries_trivia_directly() {
10727 let src = concat!(
10730 "/// inbound order events\n",
10731 "channel Orders {\n",
10732 " message: Order\n",
10733 " qos: at_least_once\n",
10734 " lifetime: affine\n",
10735 " persistence: ephemeral\n",
10736 " shield: Broker\n",
10737 "}",
10738 );
10739 let prog = parse(src);
10740 if let Declaration::Channel(ch) = &prog.declarations[0] {
10741 assert_eq!(ch.leading_trivia.len(), 1);
10742 assert!(ch.leading_trivia[0].is_doc());
10743 assert_eq!(ch.leading_trivia[0].text, "/// inbound order events");
10744 } else {
10745 panic!("expected Channel declaration");
10746 }
10747 }
10748
10749 #[test]
10750 fn per_struct_fields_match_side_channel() {
10751 let src = "/// for A\n// header for B\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }";
10754 let prog = parse(src);
10755 for (idx, decl) in prog.declarations.iter().enumerate() {
10756 let side = &prog.declaration_trivia[idx];
10757 let (per_lead, per_trail) = match decl {
10758 Declaration::Flow(f) => (&f.leading_trivia, &f.trailing_trivia),
10759 _ => panic!("unexpected variant"),
10760 };
10761 assert_eq!(per_lead.len(), side.leading.len());
10762 assert_eq!(per_trail.len(), side.trailing.len());
10763 for (a, b) in per_lead.iter().zip(side.leading.iter()) {
10764 assert_eq!(a.text, b.text);
10765 assert_eq!(a.kind, b.kind);
10766 }
10767 }
10768 }
10769
10770 #[test]
10771 fn comment_free_program_yields_empty_per_struct_fields() {
10772 let prog = parse("flow F() -> Out { }");
10773 if let Declaration::Flow(f) = &prog.declarations[0] {
10774 assert!(f.leading_trivia.is_empty());
10775 assert!(f.trailing_trivia.is_empty());
10776 } else {
10777 panic!("expected Flow declaration");
10778 }
10779 }
10780}
10781
10782#[cfg(test)]
10791mod fase14c_inner_doc_tests {
10792 use super::*;
10793 use crate::lexer::Lexer;
10794 use crate::tokens::TriviaKind;
10795
10796 fn parse(src: &str) -> Program {
10797 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
10798 Parser::new(toks).parse().expect("parse")
10799 }
10800
10801 #[test]
10802 fn inner_doc_line_reaches_leading_trivia() {
10803 let src = "//! file-level docs\nflow F() -> Out { }";
10804 let prog = parse(src);
10805 let triv = &prog.declaration_trivia[0];
10806 assert_eq!(triv.leading.len(), 1);
10807 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocLine);
10808 assert!(triv.leading[0].is_doc());
10809 assert!(triv.leading[0].is_inner_doc());
10810 assert_eq!(triv.leading[0].text, "//! file-level docs");
10811 assert_eq!(triv.leading[0].stripped_text(), " file-level docs");
10812 }
10813
10814 #[test]
10815 fn inner_doc_block_reaches_leading_trivia() {
10816 let src = "/*! module-level docs */\nflow F() -> Out { }";
10817 let prog = parse(src);
10818 let triv = &prog.declaration_trivia[0];
10819 assert_eq!(triv.leading.len(), 1);
10820 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocBlock);
10821 assert!(triv.leading[0].is_inner_doc());
10822 assert_eq!(triv.leading[0].stripped_text(), " module-level docs ");
10823 }
10824
10825 #[test]
10826 fn outer_and_inner_doc_can_coexist() {
10827 let src = "//! file docs\n/// docs F\nflow F() -> Out { }";
10831 let prog = parse(src);
10832 let triv = &prog.declaration_trivia[0];
10833 assert_eq!(triv.leading.len(), 2);
10834 assert!(triv.leading[0].is_inner_doc());
10835 assert!(triv.leading[1].is_doc());
10836 assert!(!triv.leading[1].is_inner_doc());
10837 }
10838
10839 #[test]
10840 fn inner_doc_reaches_per_struct_fields() {
10841 let src = "//! intro\nflow F() -> Out { }";
10843 let prog = parse(src);
10844 if let Declaration::Flow(f) = &prog.declarations[0] {
10845 assert_eq!(f.leading_trivia.len(), 1);
10846 assert!(f.leading_trivia[0].is_inner_doc());
10847 } else {
10848 panic!("expected Flow declaration");
10849 }
10850 }
10851}
10852
10853#[cfg(test)]
10871mod fase28_recovery_tests {
10872 use super::*;
10873 use crate::lexer::Lexer;
10874
10875 fn lex(src: &str) -> Vec<Token> {
10878 Lexer::new(src, "<test>").tokenize().expect("lex")
10879 }
10880
10881 fn recover(src: &str) -> ParseResult {
10884 Parser::new(lex(src)).parse_with_recovery()
10885 }
10886
10887 fn strict(src: &str) -> Result<Program, ParseError> {
10889 Parser::new(lex(src)).parse()
10890 }
10891
10892 #[test]
10895 fn strict_parse_unchanged_for_clean_source() {
10896 let src = "intent I {}";
10899 let prog = strict(src).expect("clean parse");
10900 assert_eq!(prog.declarations.len(), 1);
10901 }
10902
10903 #[test]
10904 fn strict_parse_still_raises_on_first_error() {
10905 let src = "flow F() { } not_a_keyword flow G() { }";
10910 let _ = strict(src).expect_err("must error fast in strict mode");
10911 }
10912
10913 #[test]
10914 fn recovery_clean_source_yields_no_errors() {
10915 let src = "flow F() { } flow G() { }";
10916 let pr = recover(src);
10917 assert!(pr.is_clean(), "errors: {:?}", pr.errors);
10918 assert_eq!(pr.program.declarations.len(), 2);
10919 }
10920
10921 #[test]
10924 fn single_unknown_top_level_token_recovers() {
10925 let src = "garbage_token flow F() { } flow G() { }";
10927 let pr = recover(src);
10928 assert_eq!(pr.errors.len(), 1, "errors: {:?}", pr.errors);
10929 assert_eq!(pr.program.declarations.len(), 2);
10930 }
10931
10932 #[test]
10933 fn error_in_first_decl_does_not_block_second() {
10934 let src = "flow F() { not_a_step nope } flow G() { }";
10937 let pr = recover(src);
10938 assert!(pr.has_errors(), "expected at least one error");
10939 let names: Vec<&str> = pr
10941 .program
10942 .declarations
10943 .iter()
10944 .filter_map(|d| match d {
10945 Declaration::Flow(f) => Some(f.name.as_str()),
10946 _ => None,
10947 })
10948 .collect();
10949 assert!(names.contains(&"G"), "G not found among {names:?}");
10950 }
10951
10952 #[test]
10953 fn malformed_declaration_then_clean_intent_recovers() {
10954 let src = "flow @ () { } intent I {}";
10955 let pr = recover(src);
10956 assert!(pr.has_errors());
10957 let kinds: Vec<&str> = pr
10958 .program
10959 .declarations
10960 .iter()
10961 .map(|d| match d {
10962 Declaration::Intent(_) => "intent",
10963 Declaration::Flow(_) => "flow",
10964 _ => "other",
10965 })
10966 .collect();
10967 assert!(kinds.contains(&"intent"), "kinds: {kinds:?}");
10968 }
10969
10970 #[test]
10971 fn recovery_does_not_double_count_a_single_error() {
10972 let src = "flow F() { not_a_step }";
10980 let pr = recover(src);
10981 let outer_ghosts = pr
10982 .errors
10983 .iter()
10984 .filter(|e| e.message.contains("at top level"))
10985 .count();
10986 assert_eq!(outer_ghosts, 0, "ghost errors: {:?}", pr.errors);
10987 }
10988
10989 #[test]
10992 fn three_independent_errors_yield_three_entries() {
10993 let src =
10994 "garbage1 flow F() { } garbage2 flow G() { } garbage3 flow H() { }";
10995 let pr = recover(src);
10996 assert_eq!(pr.errors.len(), 3, "errors: {:?}", pr.errors);
10997 assert_eq!(pr.program.declarations.len(), 3);
10998 }
10999
11000 #[test]
11001 fn all_errors_no_valid_declarations() {
11002 let src = "foo bar baz qux";
11003 let pr = recover(src);
11004 assert!(pr.has_errors());
11005 assert!(pr.program.declarations.is_empty());
11006 }
11007
11008 #[test]
11009 fn errors_recorded_in_source_order() {
11010 let src = "x flow A() { } y flow B() { } z flow C() { }";
11011 let pr = recover(src);
11012 assert_eq!(pr.errors.len(), 3);
11013 let lines: Vec<u32> = pr.errors.iter().map(|e| e.line).collect();
11014 assert!(
11017 lines.windows(2).all(|w| w[0] <= w[1]),
11018 "errors out of order: {lines:?}"
11019 );
11020 }
11021
11022 #[test]
11025 fn sync_to_flow_keyword() {
11026 let src = "garbage flow F() { }";
11027 let pr = recover(src);
11028 assert_eq!(pr.program.declarations.len(), 1);
11029 }
11030
11031 #[test]
11032 fn sync_to_intent_keyword() {
11033 let src = "garbage intent I {}";
11034 let pr = recover(src);
11035 assert_eq!(pr.program.declarations.len(), 1);
11036 }
11037
11038 #[test]
11039 fn sync_to_persona_keyword() {
11040 let src = "garbage persona P { name: \"P\" role: \"R\" }";
11041 let pr = recover(src);
11042 assert!(
11043 pr.program
11044 .declarations
11045 .iter()
11046 .any(|d| matches!(d, Declaration::Persona(_))),
11047 "persona not recovered: decls = {:?}",
11048 pr.program.declarations.len()
11049 );
11050 }
11051
11052 #[test]
11053 fn sync_to_run_keyword() {
11054 let src = "garbage run R { input: { user_message: \"hi\" } }";
11055 let pr = recover(src);
11056 assert!(pr.has_errors());
11058 }
11059
11060 #[test]
11063 fn parse_result_has_errors_and_is_clean_invert() {
11064 let pr_clean = recover("flow F() { }");
11065 assert!(pr_clean.is_clean());
11066 assert!(!pr_clean.has_errors());
11067
11068 let pr_err = recover("garbage");
11069 assert!(!pr_err.is_clean());
11070 assert!(pr_err.has_errors());
11071 }
11072
11073 #[test]
11074 fn parse_result_program_field_holds_partial_program() {
11075 let pr = recover("garbage flow F() { }");
11076 assert!(!pr.program.declarations.is_empty());
11077 }
11078
11079 #[test]
11080 fn parse_result_errors_carry_line_and_column() {
11081 let pr = recover("garbage");
11082 assert!(!pr.errors.is_empty());
11083 let e = &pr.errors[0];
11084 assert!(e.line >= 1);
11085 let _ = e.column;
11088 assert!(!e.message.is_empty());
11089 }
11090
11091 #[test]
11092 fn parse_result_debug_renders() {
11093 let pr = recover("flow F() { }");
11094 let s = format!("{pr:?}");
11095 assert!(s.contains("ParseResult"));
11096 }
11097
11098 #[test]
11101 fn empty_source_is_clean() {
11102 let pr = recover("");
11103 assert!(pr.is_clean());
11104 assert!(pr.program.declarations.is_empty());
11105 }
11106
11107 #[test]
11108 fn whitespace_only_source_is_clean() {
11109 let pr = recover(" \n\n\t \n");
11110 assert!(pr.is_clean());
11111 assert!(pr.program.declarations.is_empty());
11112 }
11113
11114 #[test]
11115 fn only_garbage_does_not_crash() {
11116 let pr = recover("foo bar baz { qux quux } corge { grault }");
11118 assert!(pr.has_errors());
11119 }
11120
11121 #[test]
11122 fn unbalanced_close_brace_does_not_crash() {
11123 let pr = recover("} flow F() { }");
11124 let names: Vec<&str> = pr
11126 .program
11127 .declarations
11128 .iter()
11129 .filter_map(|d| match d {
11130 Declaration::Flow(f) => Some(f.name.as_str()),
11131 _ => None,
11132 })
11133 .collect();
11134 assert!(names.contains(&"F"), "F not recovered: {names:?}");
11135 }
11136
11137 #[test]
11138 fn error_at_eof_does_not_loop() {
11139 let pr = recover("flow F() { ");
11141 let _ = pr.errors.len();
11143 }
11144
11145 #[test]
11146 fn nested_braces_inside_error_still_balance() {
11147 let src = "flow F() { not_a_step { inner } } flow G() { }";
11150 let pr = recover(src);
11151 let names: Vec<&str> = pr
11152 .program
11153 .declarations
11154 .iter()
11155 .filter_map(|d| match d {
11156 Declaration::Flow(f) => Some(f.name.as_str()),
11157 _ => None,
11158 })
11159 .collect();
11160 assert!(names.contains(&"G"), "G not recovered: {names:?}");
11161 }
11162
11163 #[derive(Clone, Copy)]
11172 struct Xorshift(u64);
11173 impl Xorshift {
11174 fn next(&mut self) -> u64 {
11175 let mut x = self.0;
11176 x ^= x << 13;
11177 x ^= x >> 7;
11178 x ^= x << 17;
11179 self.0 = x;
11180 x
11181 }
11182 fn pick<T: Copy>(&mut self, slice: &[T]) -> T {
11183 slice[(self.next() as usize) % slice.len()]
11184 }
11185 }
11186
11187 fn mutate(src: &str, rng: &mut Xorshift) -> String {
11188 let mut bytes: Vec<u8> = src.bytes().collect();
11189 if bytes.is_empty() {
11190 return src.to_string();
11191 }
11192 let op = rng.next() % 4;
11193 let pos = (rng.next() as usize) % bytes.len();
11194 let safe: &[u8] = b"abcdefghijklmnopqrstuvwxyz {}();:,_0123456789";
11198 match op {
11199 0 => {
11200 bytes.remove(pos);
11201 }
11202 1 => {
11203 let b = rng.pick(safe);
11204 bytes.insert(pos, b);
11205 }
11206 2 if pos + 1 < bytes.len() => {
11207 bytes.swap(pos, pos + 1);
11208 }
11209 _ => {
11210 let b = rng.pick(safe);
11211 bytes[pos] = b;
11212 }
11213 }
11214 bytes.retain(|b| b.is_ascii());
11217 String::from_utf8_lossy(&bytes).into_owned()
11218 }
11219
11220 #[test]
11221 fn fuzz_recovery_never_crashes() {
11222 let seed_bases = [
11223 "flow F() { }",
11224 "intent I { }",
11225 "persona P { name: \"P\" role: \"R\" }",
11226 "intent J { ask: \"a\" }",
11227 "type T = String",
11228 ];
11229 for (bucket, base) in (0..100u64).zip(seed_bases.iter().cycle()) {
11231 let mut rng = Xorshift(0x1234_5678_9abc_def0_u64.wrapping_add(bucket));
11232 let mut current = (*base).to_string();
11233 for _ in 0..10 {
11234 current = mutate(¤t, &mut rng);
11235 let toks = match Lexer::new(¤t, "<fuzz>").tokenize() {
11238 Ok(t) => t,
11239 Err(_) => continue,
11240 };
11241 let _pr = Parser::new(toks).parse_with_recovery();
11243 }
11244 }
11245 }
11246
11247 #[test]
11250 fn missing_colon_hint_preserved_under_recovery() {
11251 let src = "flow F() { run R { input { user_message: \"hi\" } } }";
11255 let pr = recover(src);
11256 if !pr.errors.is_empty() {
11260 let any_msg = pr.errors.iter().any(|e| !e.message.is_empty());
11261 assert!(any_msg);
11262 }
11263 }
11264
11265 #[test]
11268 fn recovered_declarations_appear_in_source_order() {
11269 let src = "flow A() { } garbage flow B() { } garbage flow C() { }";
11270 let pr = recover(src);
11271 let names: Vec<&str> = pr
11272 .program
11273 .declarations
11274 .iter()
11275 .filter_map(|d| match d {
11276 Declaration::Flow(f) => Some(f.name.as_str()),
11277 _ => None,
11278 })
11279 .collect();
11280 assert_eq!(names, vec!["A", "B", "C"]);
11281 }
11282}
11283
11284#[cfg(test)]
11292mod fase28_source_context_tests {
11293 use super::*;
11294 use crate::lexer::Lexer;
11295
11296 fn snippet(source: &str, line: u32, column: u32, filename: &str) -> String {
11297 SourceSnippet::new(
11298 source.to_string(),
11299 line,
11300 column,
11301 filename.to_string(),
11302 )
11303 .render()
11304 }
11305
11306 #[test]
11309 fn rustc_style_block_for_middle_line() {
11310 let src = "line one\nline two\nline three\nline four\nline five";
11311 let out = snippet(src, 3, 6, "x.axon");
11312 assert!(out.contains("--> x.axon:3:6"));
11313 assert!(out.contains("1 | line one"));
11314 assert!(out.contains("2 | line two"));
11315 assert!(out.contains("3 | line three"));
11316 assert!(out.contains("4 | line four"));
11317 assert!(out.contains("5 | line five"));
11318 assert!(out.contains("\n | ^"), "out:\n{out}");
11320 }
11321
11322 #[test]
11323 fn caret_column_one_renders_correctly() {
11324 let out = snippet("abc\n", 1, 1, "<source>");
11325 assert!(out.contains("\n | ^"));
11326 }
11327
11328 #[test]
11329 fn first_line_clamps_context_before_to_zero() {
11330 let src = "first\nsecond\nthird\nfourth\nfifth";
11331 let out = snippet(src, 1, 1, "<source>");
11332 assert!(out.contains("1 | first"));
11333 assert!(out.contains("2 | second"));
11334 assert!(out.contains("3 | third"));
11335 assert!(!out.contains("4 | fourth"));
11336 }
11337
11338 #[test]
11339 fn last_line_clamps_context_after_to_eof() {
11340 let src = "first\nsecond\nthird\nfourth\nfifth";
11341 let out = snippet(src, 5, 2, "<source>");
11342 assert!(out.contains("5 | fifth"));
11343 assert!(out.contains("3 | third"));
11344 assert!(out.contains("4 | fourth"));
11345 assert!(!out.contains("2 | second"));
11346 }
11347
11348 #[test]
11349 fn gutter_width_grows_with_line_count() {
11350 let src: String = (1..=12).map(|i| format!("line{i}")).collect::<Vec<_>>().join("\n");
11351 let out = snippet(&src, 12, 1, "<source>");
11352 assert!(out.contains("12 | line12"));
11353 assert!(out.contains("10 | line10"));
11354 }
11355
11356 #[test]
11359 fn empty_source_returns_empty() {
11360 assert_eq!(snippet("", 1, 1, "<source>"), "");
11361 }
11362
11363 #[test]
11364 fn zero_line_returns_empty() {
11365 assert_eq!(snippet("hi", 0, 1, "<source>"), "");
11366 }
11367
11368 #[test]
11369 fn out_of_range_line_returns_empty() {
11370 assert_eq!(snippet("hi", 99, 1, "<source>"), "");
11371 }
11372
11373 #[test]
11374 fn caret_clamps_past_eol() {
11375 let out = snippet("hello", 1, 50, "<source>");
11376 assert!(out.contains("\n | ^"), "out:\n{out}");
11377 }
11378
11379 #[test]
11380 fn unicode_codepoint_count_for_caret_clamp() {
11381 let out = snippet("héllo", 1, 99, "<source>");
11383 assert!(out.contains("\n | ^"), "out:\n{out}");
11384 }
11385
11386 #[test]
11387 fn trailing_newline_does_not_create_phantom_last_line() {
11388 let out = snippet("first\nsecond\n", 2, 1, "<source>");
11389 assert!(!out.contains("3 |"));
11390 assert!(out.contains("2 | second"));
11391 }
11392
11393 fn lex(src: &str) -> Vec<Token> {
11396 Lexer::new(src, "<test>").tokenize().expect("lex")
11397 }
11398
11399 #[test]
11400 fn strict_parse_attaches_snippet_when_source_given() {
11401 let src = "garbage_token\nflow F() { }";
11402 let err = Parser::new(lex(src))
11403 .with_source(src, "x.axon")
11404 .parse()
11405 .expect_err("must error");
11406 assert!(err.source_snippet.is_some());
11407 let display = format!("{err}");
11408 assert!(display.contains("--> x.axon:"), "display: {display}");
11409 }
11410
11411 #[test]
11412 fn strict_parse_no_snippet_when_no_source() {
11413 let src = "garbage_token";
11414 let err = Parser::new(lex(src)).parse().expect_err("must error");
11415 assert!(err.source_snippet.is_none());
11416 let display = format!("{err}");
11417 assert!(!display.contains("\n -->"));
11418 }
11419
11420 #[test]
11421 fn every_recovered_error_has_snippet() {
11422 let src = "garbage1\nflow F() { }\ngarbage2\nflow G() { }";
11423 let result = Parser::new(lex(src))
11424 .with_source(src, "multi.axon")
11425 .parse_with_recovery();
11426 assert!(!result.errors.is_empty());
11427 for err in &result.errors {
11428 assert!(err.source_snippet.is_some());
11429 let display = format!("{err}");
11430 assert!(
11431 display.contains("--> multi.axon:"),
11432 "display: {display}"
11433 );
11434 }
11435 }
11436
11437 #[test]
11438 fn recovery_no_snippet_when_no_source() {
11439 let src = "garbage1 garbage2";
11440 let result = Parser::new(lex(src)).parse_with_recovery();
11441 for err in &result.errors {
11442 assert!(err.source_snippet.is_none());
11443 }
11444 }
11445
11446 #[test]
11447 fn snippet_points_at_correct_line_for_each_error() {
11448 let src = "garbage_a\nflow F() { }\ngarbage_b\nflow G() { }";
11449 let result = Parser::new(lex(src))
11450 .with_source(src, "x")
11451 .parse_with_recovery();
11452 for err in &result.errors {
11453 let sn = err.source_snippet.as_ref().expect("snippet");
11454 assert_eq!(sn.line, err.line);
11455 }
11456 }
11457
11458 #[test]
11461 fn legacy_constructor_still_works() {
11462 let src = "flow F() { }";
11463 let prog = Parser::new(lex(src)).parse().expect("clean");
11464 assert_eq!(prog.declarations.len(), 1);
11465 }
11466
11467 #[test]
11468 fn attach_source_idempotent() {
11469 let err = ParseError {
11470 message: "bad".to_string(),
11471 line: 2,
11472 column: 3,
11473 ..Default::default()
11474 };
11475 let err2 = err.clone().attach_source("a\nb\nc\n", "f.axon");
11476 let first = format!("{err2}");
11477 let err3 = err.attach_source("a\nb\nc\n", "f.axon");
11478 let second = format!("{err3}");
11479 assert_eq!(first, second);
11480 }
11481
11482 #[test]
11483 fn attach_source_noop_when_line_zero() {
11484 let err = ParseError {
11485 message: "bad".to_string(),
11486 line: 0,
11487 column: 0,
11488 ..Default::default()
11489 };
11490 let err = err.attach_source("a\nb\nc\n", "f.axon");
11491 assert!(err.source_snippet.is_none());
11492 }
11493
11494 #[test]
11500 fn golden_simple_three_line_block() {
11501 let src = "alpha\nbeta\ngamma";
11502 let out = snippet(src, 2, 3, "g.axon");
11503 let expected = concat!(
11507 " --> g.axon:2:3\n",
11508 " |\n",
11509 "1 | alpha\n",
11510 "2 | beta\n",
11511 " | ^\n",
11512 "3 | gamma",
11513 );
11514 assert_eq!(out, expected);
11515 }
11516
11517 #[test]
11518 fn golden_first_line_caret() {
11519 let src = "abc\ndef\n";
11520 let out = snippet(src, 1, 1, "x");
11521 let expected = concat!(
11522 " --> x:1:1\n",
11523 " |\n",
11524 "1 | abc\n",
11525 " | ^\n",
11526 "2 | def",
11527 );
11528 assert_eq!(out, expected);
11529 }
11530
11531 #[test]
11532 fn golden_two_digit_gutter() {
11533 let src: String = (1..=11)
11534 .map(|i| format!("L{i}"))
11535 .collect::<Vec<_>>()
11536 .join("\n");
11537 let out = snippet(&src, 10, 2, "big");
11538 let expected = concat!(
11539 " --> big:10:2\n",
11540 " |\n",
11541 " 8 | L8\n",
11542 " 9 | L9\n",
11543 "10 | L10\n",
11544 " | ^\n",
11545 "11 | L11",
11546 );
11547 assert_eq!(out, expected);
11548 }
11549}
11550
11551#[cfg(test)]
11558mod fase28_smart_suggest_parser_tests {
11559 use super::*;
11560 use crate::lexer::Lexer;
11561
11562 fn lex(src: &str) -> Vec<Token> {
11563 Lexer::new(src, "<test>").tokenize().expect("lex")
11564 }
11565
11566 #[test]
11567 fn top_level_typo_suggests_flow() {
11568 let src = "flwo F() { }";
11569 let err = Parser::new(lex(src)).parse().expect_err("must error");
11570 assert!(
11571 err.message.contains("Did you mean `flow`?"),
11572 "msg: {}",
11573 err.message
11574 );
11575 }
11576
11577 #[test]
11578 fn top_level_unknown_far_no_suggestion() {
11579 let src = "qwerty F() { }";
11580 let err = Parser::new(lex(src)).parse().expect_err("must error");
11581 assert!(
11582 !err.message.contains("Did you mean"),
11583 "msg: {}",
11584 err.message
11585 );
11586 }
11587
11588 #[test]
11589 fn flow_body_typo_suggests_step() {
11590 let src = "flow F() { stepp S {} }";
11591 let err = Parser::new(lex(src)).parse().expect_err("must error");
11592 assert!(
11593 err.message.contains("Did you mean `step`"),
11594 "msg: {}",
11595 err.message
11596 );
11597 }
11598
11599 #[test]
11600 fn flow_body_typo_suggests_reason() {
11601 let src = "flow F() { reasn R {} }";
11602 let err = Parser::new(lex(src)).parse().expect_err("must error");
11603 assert!(
11604 err.message.contains("Did you mean `reason`?"),
11605 "msg: {}",
11606 err.message
11607 );
11608 }
11609
11610 #[test]
11611 fn recovery_mode_carries_hint() {
11612 let src = "flwo F() { }";
11613 let result = Parser::new(lex(src)).parse_with_recovery();
11614 assert!(
11615 result
11616 .errors
11617 .iter()
11618 .any(|e| e.message.contains("Did you mean `flow`?")),
11619 "errors: {:?}",
11620 result.errors
11621 );
11622 }
11623}
11624
11625#[cfg(test)]
11628mod fase35m_mutate_purge_where_tests {
11629 use super::*;
11630
11631 fn parse(src: &str) -> Program {
11632 let tokens = crate::lexer::Lexer::new(src, "<test>")
11633 .tokenize()
11634 .expect("lex");
11635 Parser::new(tokens).parse().expect("parse")
11636 }
11637
11638 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11639 for d in &prog.declarations {
11640 if let Declaration::Flow(f) = d {
11641 if f.name == flow {
11642 return f.body.first().expect("flow has at least one step");
11643 }
11644 }
11645 }
11646 panic!("flow `{flow}` not found");
11647 }
11648
11649 #[test]
11650 fn mutate_captures_its_where_clause() {
11651 let prog =
11654 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
11655 match first_step(&prog, "F") {
11656 FlowStep::Mutate(m) => {
11657 assert_eq!(m.store_name, "accounts");
11658 assert_eq!(m.where_expr, "id = 1");
11659 }
11660 other => panic!("expected Mutate, got {other:?}"),
11661 }
11662 }
11663
11664 #[test]
11665 fn purge_captures_its_where_clause() {
11666 let prog =
11667 parse("flow F() -> Unit { purge logs { where: \"ts < 100\" } }");
11668 match first_step(&prog, "F") {
11669 FlowStep::Purge(p) => {
11670 assert_eq!(p.store_name, "logs");
11671 assert_eq!(p.where_expr, "ts < 100");
11672 }
11673 other => panic!("expected Purge, got {other:?}"),
11674 }
11675 }
11676
11677 #[test]
11678 fn mutate_without_a_where_block_is_a_whole_store_op() {
11679 let prog = parse("flow F() -> Unit { mutate accounts }");
11682 match first_step(&prog, "F") {
11683 FlowStep::Mutate(m) => {
11684 assert_eq!(m.store_name, "accounts");
11685 assert_eq!(m.where_expr, "");
11686 }
11687 other => panic!("expected Mutate, got {other:?}"),
11688 }
11689 }
11690}
11691
11692#[cfg(test)]
11695mod fase35o_persist_fields_tests {
11696 use super::*;
11697
11698 fn parse(src: &str) -> Program {
11699 let tokens = crate::lexer::Lexer::new(src, "<test>")
11700 .tokenize()
11701 .expect("lex");
11702 Parser::new(tokens).parse().expect("parse")
11703 }
11704
11705 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11706 for d in &prog.declarations {
11707 if let Declaration::Flow(f) = d {
11708 if f.name == flow {
11709 return f.body.first().expect("flow has at least one step");
11710 }
11711 }
11712 }
11713 panic!("flow `{flow}` not found");
11714 }
11715
11716 #[test]
11717 fn persist_captures_its_field_block() {
11718 let prog = parse(
11722 "flow F() -> Unit { persist into chat_history { \
11723 session_id: \"${session_id}\" sender: \"user\" \
11724 content: \"${message}\" } }",
11725 );
11726 match first_step(&prog, "F") {
11727 FlowStep::Persist(p) => {
11728 assert_eq!(p.store_name, "chat_history");
11729 assert_eq!(
11730 p.fields,
11731 vec![
11732 ("session_id".to_string(), "${session_id}".to_string()),
11733 ("sender".to_string(), "user".to_string()),
11734 ("content".to_string(), "${message}".to_string()),
11735 ]
11736 );
11737 }
11738 other => panic!("expected Persist, got {other:?}"),
11739 }
11740 }
11741
11742 #[test]
11743 fn persist_without_a_block_keeps_the_user_bindings_fallback() {
11744 let prog = parse("flow F() -> Unit { persist events }");
11747 match first_step(&prog, "F") {
11748 FlowStep::Persist(p) => {
11749 assert_eq!(p.store_name, "events");
11750 assert!(p.fields.is_empty());
11751 }
11752 other => panic!("expected Persist, got {other:?}"),
11753 }
11754 }
11755
11756 #[test]
11757 fn persist_accepts_the_optional_into_connector() {
11758 let with =
11761 parse("flow F() -> Unit { persist into accounts { id: \"1\" } }");
11762 let without =
11763 parse("flow F() -> Unit { persist accounts { id: \"1\" } }");
11764 for prog in [&with, &without] {
11765 match first_step(prog, "F") {
11766 FlowStep::Persist(p) => assert_eq!(p.store_name, "accounts"),
11767 other => panic!("expected Persist, got {other:?}"),
11768 }
11769 }
11770 }
11771
11772 #[test]
11773 fn persist_into_without_a_block_resolves_the_store_name() {
11774 let prog = parse("flow F() -> Unit { persist into events }");
11777 match first_step(&prog, "F") {
11778 FlowStep::Persist(p) => {
11779 assert_eq!(p.store_name, "events");
11780 assert!(p.fields.is_empty());
11781 }
11782 other => panic!("expected Persist, got {other:?}"),
11783 }
11784 }
11785
11786 #[test]
11787 fn persist_fields_lower_into_the_ir() {
11788 let prog = parse(
11791 "flow F() -> Unit { persist into chat { content: \"${msg}\" } }",
11792 );
11793 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
11794 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
11795 match flow.steps.first().expect("one step") {
11796 crate::ir_nodes::IRFlowNode::Persist(p) => {
11797 assert_eq!(p.store_name, "chat");
11798 assert_eq!(
11799 p.fields,
11800 vec![("content".to_string(), "${msg}".to_string())]
11801 );
11802 }
11803 other => panic!("expected IRFlowNode::Persist, got {other:?}"),
11804 }
11805 }
11806}
11807
11808#[cfg(test)]
11811mod fase35p_mutate_fields_tests {
11812 use super::*;
11813
11814 fn parse(src: &str) -> Program {
11815 let tokens = crate::lexer::Lexer::new(src, "<test>")
11816 .tokenize()
11817 .expect("lex");
11818 Parser::new(tokens).parse().expect("parse")
11819 }
11820
11821 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
11822 for d in &prog.declarations {
11823 if let Declaration::Flow(f) = d {
11824 if f.name == flow {
11825 return f.body.first().expect("flow has at least one step");
11826 }
11827 }
11828 }
11829 panic!("flow `{flow}` not found");
11830 }
11831
11832 #[test]
11833 fn mutate_captures_its_set_field_block() {
11834 let prog = parse(
11838 "flow F() -> Unit { mutate accounts { where: \"id = ${id}\" \
11839 balance: \"${new_balance}\" status: \"active\" } }",
11840 );
11841 match first_step(&prog, "F") {
11842 FlowStep::Mutate(m) => {
11843 assert_eq!(m.store_name, "accounts");
11844 assert_eq!(m.where_expr, "id = ${id}");
11845 assert_eq!(
11846 m.fields,
11847 vec![
11848 ("balance".to_string(), "${new_balance}".to_string()),
11849 ("status".to_string(), "active".to_string()),
11850 ]
11851 );
11852 }
11853 other => panic!("expected Mutate, got {other:?}"),
11854 }
11855 }
11856
11857 #[test]
11858 fn mutate_where_only_block_has_no_set_fields() {
11859 let prog =
11862 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
11863 match first_step(&prog, "F") {
11864 FlowStep::Mutate(m) => {
11865 assert_eq!(m.where_expr, "id = 1");
11866 assert!(m.fields.is_empty());
11867 }
11868 other => panic!("expected Mutate, got {other:?}"),
11869 }
11870 }
11871
11872 #[test]
11873 fn mutate_with_no_block_is_a_whole_store_op() {
11874 let prog = parse("flow F() -> Unit { mutate accounts }");
11877 match first_step(&prog, "F") {
11878 FlowStep::Mutate(m) => {
11879 assert_eq!(m.store_name, "accounts");
11880 assert_eq!(m.where_expr, "");
11881 assert!(m.fields.is_empty());
11882 }
11883 other => panic!("expected Mutate, got {other:?}"),
11884 }
11885 }
11886
11887 #[test]
11888 fn mutate_fields_lower_into_the_ir() {
11889 let prog = parse(
11890 "flow F() -> Unit { mutate t { where: \"id = 1\" v: \"${x}\" } }",
11891 );
11892 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
11893 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
11894 match flow.steps.first().expect("one step") {
11895 crate::ir_nodes::IRFlowNode::Mutate(m) => {
11896 assert_eq!(m.where_expr, "id = 1");
11897 assert_eq!(
11898 m.fields,
11899 vec![("v".to_string(), "${x}".to_string())]
11900 );
11901 }
11902 other => panic!("expected IRFlowNode::Mutate, got {other:?}"),
11903 }
11904 }
11905}
11906