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::Effect(n) => {
81 n.leading_trivia = leading;
82 n.trailing_trivia = trailing;
83 }
84 Declaration::Tool(n) => {
85 n.leading_trivia = leading;
86 n.trailing_trivia = trailing;
87 }
88 Declaration::Type(n) => {
89 n.leading_trivia = leading;
90 n.trailing_trivia = trailing;
91 }
92 Declaration::Flow(n) => {
93 n.leading_trivia = leading;
94 n.trailing_trivia = trailing;
95 }
96 Declaration::Intent(n) => {
97 n.leading_trivia = leading;
98 n.trailing_trivia = trailing;
99 }
100 Declaration::Run(n) => {
101 n.leading_trivia = leading;
102 n.trailing_trivia = trailing;
103 }
104 Declaration::Epistemic(n) => {
105 n.leading_trivia = leading;
106 n.trailing_trivia = trailing;
107 }
108 Declaration::Let(n) => {
109 n.leading_trivia = leading;
110 n.trailing_trivia = trailing;
111 }
112 Declaration::LambdaData(n) => {
113 n.leading_trivia = leading;
114 n.trailing_trivia = trailing;
115 }
116 Declaration::Agent(n) => {
117 n.leading_trivia = leading;
118 n.trailing_trivia = trailing;
119 }
120 Declaration::Shield(n) => {
121 n.leading_trivia = leading;
122 n.trailing_trivia = trailing;
123 }
124 Declaration::Window(n) => {
125 n.leading_trivia = leading;
126 n.trailing_trivia = trailing;
127 }
128 Declaration::Pix(n) => {
129 n.leading_trivia = leading;
130 n.trailing_trivia = trailing;
131 }
132 Declaration::Ledger(n) => {
133 n.leading_trivia = leading;
134 n.trailing_trivia = trailing;
135 }
136 Declaration::Psyche(n) => {
137 n.leading_trivia = leading;
138 n.trailing_trivia = trailing;
139 }
140 Declaration::Corpus(n) => {
141 n.leading_trivia = leading;
142 n.trailing_trivia = trailing;
143 }
144 Declaration::Dataspace(n) => {
145 n.leading_trivia = leading;
146 n.trailing_trivia = trailing;
147 }
148 Declaration::Ots(n) => {
149 n.leading_trivia = leading;
150 n.trailing_trivia = trailing;
151 }
152 Declaration::Mandate(n) => {
153 n.leading_trivia = leading;
154 n.trailing_trivia = trailing;
155 }
156 Declaration::Compute(n) => {
157 n.leading_trivia = leading;
158 n.trailing_trivia = trailing;
159 }
160 Declaration::Daemon(n) => {
161 n.leading_trivia = leading;
162 n.trailing_trivia = trailing;
163 }
164 Declaration::Extension(n) => {
165 n.leading_trivia = leading;
166 n.trailing_trivia = trailing;
167 }
168 Declaration::AxonStore(n) => {
169 n.leading_trivia = leading;
170 n.trailing_trivia = trailing;
171 }
172 Declaration::AxonEndpoint(n) => {
173 n.leading_trivia = leading;
174 n.trailing_trivia = trailing;
175 }
176 Declaration::Resource(n) => {
177 n.leading_trivia = leading;
178 n.trailing_trivia = trailing;
179 }
180 Declaration::Fabric(n) => {
181 n.leading_trivia = leading;
182 n.trailing_trivia = trailing;
183 }
184 Declaration::Manifest(n) => {
185 n.leading_trivia = leading;
186 n.trailing_trivia = trailing;
187 }
188 Declaration::Observe(n) => {
189 n.leading_trivia = leading;
190 n.trailing_trivia = trailing;
191 }
192 Declaration::Reconcile(n) => {
193 n.leading_trivia = leading;
194 n.trailing_trivia = trailing;
195 }
196 Declaration::Lease(n) => {
197 n.leading_trivia = leading;
198 n.trailing_trivia = trailing;
199 }
200 Declaration::Ensemble(n) => {
201 n.leading_trivia = leading;
202 n.trailing_trivia = trailing;
203 }
204 Declaration::Session(n) => {
205 n.leading_trivia = leading;
206 n.trailing_trivia = trailing;
207 }
208 Declaration::Topology(n) => {
209 n.leading_trivia = leading;
210 n.trailing_trivia = trailing;
211 }
212 Declaration::Immune(n) => {
213 n.leading_trivia = leading;
214 n.trailing_trivia = trailing;
215 }
216 Declaration::Reflex(n) => {
217 n.leading_trivia = leading;
218 n.trailing_trivia = trailing;
219 }
220 Declaration::Heal(n) => {
221 n.leading_trivia = leading;
222 n.trailing_trivia = trailing;
223 }
224 Declaration::Component(n) => {
225 n.leading_trivia = leading;
226 n.trailing_trivia = trailing;
227 }
228 Declaration::View(n) => {
229 n.leading_trivia = leading;
230 n.trailing_trivia = trailing;
231 }
232 Declaration::Channel(n) => {
233 n.leading_trivia = leading;
234 n.trailing_trivia = trailing;
235 }
236 Declaration::Socket(n) => {
237 n.leading_trivia = leading;
238 n.trailing_trivia = trailing;
239 }
240 Declaration::Upstream(n) => {
241 n.leading_trivia = leading;
242 n.trailing_trivia = trailing;
243 }
244 Declaration::Voice(n) => {
245 n.leading_trivia = leading;
246 n.trailing_trivia = trailing;
247 }
248 Declaration::Cors(n) => {
249 n.leading_trivia = leading;
250 n.trailing_trivia = trailing;
251 }
252 Declaration::Credential(n) => {
253 n.leading_trivia = leading;
254 n.trailing_trivia = trailing;
255 }
256 Declaration::Cache(n) => {
257 n.leading_trivia = leading;
258 n.trailing_trivia = trailing;
259 }
260 Declaration::Savant(n) => {
261 n.leading_trivia = leading;
262 n.trailing_trivia = trailing;
263 }
264 Declaration::Synth(n) => {
265 n.leading_trivia = leading;
266 n.trailing_trivia = trailing;
267 }
268 Declaration::Scope(n) => {
269 n.leading_trivia = leading;
270 n.trailing_trivia = trailing;
271 }
272 Declaration::Observable(n) => {
273 n.leading_trivia = leading;
274 n.trailing_trivia = trailing;
275 }
276 Declaration::Witness(n) => {
277 n.leading_trivia = leading;
278 n.trailing_trivia = trailing;
279 }
280 Declaration::Document(n) => {
281 n.leading_trivia = leading;
282 n.trailing_trivia = trailing;
283 }
284 Declaration::Deliver(n) => {
285 n.leading_trivia = leading;
286 n.trailing_trivia = trailing;
287 }
288 Declaration::Notify(n) => {
289 n.leading_trivia = leading;
290 n.trailing_trivia = trailing;
291 }
292 Declaration::Generic(n) => {
293 n.leading_trivia = leading;
294 n.trailing_trivia = trailing;
295 }
296 }
297}
298
299pub const SOURCE_CONTEXT_LINES_BEFORE: usize = 2;
306pub const SOURCE_CONTEXT_LINES_AFTER: usize = 2;
307
308#[derive(Debug, Clone)]
319pub struct SourceSnippet {
320 pub source: String,
321 pub line: u32,
322 pub column: u32,
323 pub filename: String,
324 pub context_before: usize,
325 pub context_after: usize,
326}
327
328impl SourceSnippet {
329 pub fn new(source: String, line: u32, column: u32, filename: String) -> Self {
331 Self {
332 source,
333 line,
334 column,
335 filename,
336 context_before: SOURCE_CONTEXT_LINES_BEFORE,
337 context_after: SOURCE_CONTEXT_LINES_AFTER,
338 }
339 }
340
341 #[must_use]
348 pub fn render(&self) -> String {
349 if self.source.is_empty() || self.line < 1 {
350 return String::new();
351 }
352 let raw: Vec<&str> = self.source.split('\n').collect();
353 let lines: Vec<&str> = if raw.last() == Some(&"") {
356 raw[..raw.len() - 1].to_vec()
357 } else {
358 raw
359 };
360 if lines.is_empty() || self.line as usize > lines.len() {
361 return String::new();
362 }
363
364 let line_idx = self.line as usize;
365 let start = line_idx.saturating_sub(self.context_before).max(1);
366 let end = (line_idx + self.context_after).min(lines.len());
367
368 let gutter = end.to_string().len();
369 let empty_gutter = " ".repeat(gutter);
370
371 let mut out: Vec<String> = Vec::with_capacity(end - start + 4);
372 out.push(format!(
373 "{empty_gutter} --> {}:{}:{}",
374 self.filename, self.line, self.column
375 ));
376 out.push(format!("{empty_gutter} |"));
377 for n in start..=end {
378 let line_text = lines[n - 1];
379 out.push(format!("{n:>gutter$} | {line_text}", gutter = gutter));
380 if n == line_idx {
381 let line_len = line_text.chars().count();
382 let col = (self.column as usize).clamp(1, line_len + 1);
383 out.push(format!(
384 "{empty_gutter} | {pad}^",
385 pad = " ".repeat(col - 1)
386 ));
387 }
388 }
389 out.join("\n")
390 }
391}
392
393#[derive(Debug, Clone, Default)]
394pub struct ParseError {
395 pub message: String,
396 pub line: u32,
397 pub column: u32,
398 pub source_snippet: Option<SourceSnippet>,
405}
406
407impl ParseError {
408 #[must_use]
412 pub fn attach_source(mut self, source: &str, filename: &str) -> Self {
413 if self.line >= 1 {
414 self.source_snippet = Some(SourceSnippet::new(
415 source.to_string(),
416 self.line,
417 self.column,
418 filename.to_string(),
419 ));
420 }
421 self
422 }
423}
424
425impl std::fmt::Display for ParseError {
426 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427 write!(f, "[line {}:{}] {}", self.line, self.column, self.message)?;
428 if let Some(snippet) = &self.source_snippet {
429 let block = snippet.render();
430 if !block.is_empty() {
431 write!(f, "\n{block}")?;
432 }
433 }
434 Ok(())
435 }
436}
437
438impl std::error::Error for ParseError {}
439
440#[derive(Debug)]
458pub struct ParseResult {
459 pub program: Program,
460 pub errors: Vec<ParseError>,
461}
462
463impl ParseResult {
464 #[inline]
469 #[must_use]
470 pub fn has_errors(&self) -> bool {
471 !self.errors.is_empty()
472 }
473
474 #[inline]
477 #[must_use]
478 pub fn is_clean(&self) -> bool {
479 self.errors.is_empty()
480 }
481}
482
483#[inline]
497const fn is_top_level_decl_kw_for_recovery(tt: &TokenType) -> bool {
498 matches!(
499 tt,
500 TokenType::Import
501 | TokenType::Persona
502 | TokenType::Context
503 | TokenType::Anchor
504 | TokenType::Memory
505 | TokenType::Tool
506 | TokenType::Type
507 | TokenType::Flow
508 | TokenType::Intent
509 | TokenType::Run
510 | TokenType::Let
511 | TokenType::Know
512 | TokenType::Believe
513 | TokenType::Speculate
514 | TokenType::Doubt
515 | TokenType::Lambda
516 | TokenType::Agent
517 | TokenType::Shield
518 | TokenType::Pix
519 | TokenType::Ledger
520 | TokenType::Psyche
521 | TokenType::Corpus
522 | TokenType::Dataspace
523 | TokenType::Ots
524 | TokenType::Mandate
525 | TokenType::Compute
526 | TokenType::Daemon
527 | TokenType::Savant
529 | TokenType::Synth
530 | TokenType::Scope
532 | TokenType::AxonStore
533 | TokenType::AxonEndpoint
534 | TokenType::Resource
535 | TokenType::Fabric
536 | TokenType::Manifest
537 | TokenType::Observe
538 | TokenType::Reconcile
539 | TokenType::Lease
540 | TokenType::Ensemble
541 | TokenType::Session
542 | TokenType::Topology
543 | TokenType::Immune
544 | TokenType::Reflex
545 | TokenType::Heal
546 | TokenType::Component
547 | TokenType::View
548 | TokenType::Channel
549 | TokenType::Ingest
550 | TokenType::Persist
551 | TokenType::Retrieve
552 | TokenType::Mutate
553 | TokenType::Purge
554 | TokenType::Transact
555 | TokenType::Mcp
556 )
557}
558
559pub const AXONENDPOINT_TRANSPORT_VALUES: &[&str] = &["json", "sse", "ndjson"];
572
573pub const AXONENDPOINT_TRANSPORT_DIALECTS: &[&str] =
617 &["axon", "openai", "kimi", "glm", "anthropic"];
618
619pub const AXONENDPOINT_KEEPALIVE_VALUES: &[&str] = &["5s", "15s", "30s", "60s"];
621
622pub const AXONENDPOINT_METHOD_VALUES: &[&str] =
636 &["GET", "POST", "PUT", "DELETE", "PATCH", "QUERY"];
637
638pub const AXONENDPOINT_BACKEND_VALUES: &[&str] = &[
659 "anthropic",
660 "auto",
661 "gemini",
662 "glm",
663 "kimi",
664 "ollama",
665 "openai",
666 "openrouter",
667 "stub",
668];
669
670#[inline]
671fn axonendpoint_is_valid_transport(s: &str) -> bool {
672 AXONENDPOINT_TRANSPORT_VALUES.iter().any(|&v| v == s)
673}
674
675#[inline]
676fn axonendpoint_is_valid_method(s: &str) -> bool {
677 AXONENDPOINT_METHOD_VALUES.iter().any(|&v| v == s)
678}
679
680#[inline]
681fn axonendpoint_is_valid_backend(s: &str) -> bool {
682 AXONENDPOINT_BACKEND_VALUES.iter().any(|&v| v == s)
683}
684
685#[inline]
686fn axonendpoint_is_valid_keepalive(s: &str) -> bool {
687 AXONENDPOINT_KEEPALIVE_VALUES.iter().any(|&v| v == s)
688}
689
690pub const AXONENDPOINT_QUERY_PARAM_TYPES: &[&str] =
705 &["Text", "Int", "Float", "Bool", "Uuid"];
706
707#[inline]
710pub(crate) fn axonendpoint_is_valid_query_param_type(s: &str) -> bool {
711 AXONENDPOINT_QUERY_PARAM_TYPES.iter().any(|&v| v == s)
712}
713
714pub(crate) fn extract_path_param_names(path: &str) -> Result<Vec<String>, String> {
741 let mut out: Vec<String> = Vec::new();
742 let bytes = path.as_bytes();
743 let mut i = 0;
744 while i < bytes.len() {
745 if bytes[i] != b'{' {
746 i += 1;
747 continue;
748 }
749 let start = i + 1;
752 let mut end = start;
753 while end < bytes.len() && bytes[end] != b'}' {
754 end += 1;
755 }
756 if end == bytes.len() {
757 break;
760 }
761 let raw = &path[start..end];
762 let valid = !raw.is_empty()
764 && raw.bytes().enumerate().all(|(idx, b)| {
765 if idx == 0 {
766 b.is_ascii_alphabetic() || b == b'_'
767 } else {
768 b.is_ascii_alphanumeric() || b == b'_'
769 }
770 });
771 if valid {
772 let name = raw.to_string();
773 if out.iter().any(|existing| existing == &name) {
774 return Err(name);
775 }
776 out.push(name);
777 }
778 i = end + 1;
779 }
780 Ok(out)
781}
782
783pub fn is_valid_capability_slug(slug: &str) -> bool {
797 if slug.is_empty() {
798 return false;
799 }
800 for segment in slug.split('.') {
801 if !is_valid_slug_segment(segment) {
802 return false;
803 }
804 }
805 true
806}
807
808fn is_valid_slug_segment(seg: &str) -> bool {
809 let mut chars = seg.chars();
810 let first = match chars.next() {
811 Some(c) => c,
812 None => return false,
813 };
814 if !first.is_ascii_lowercase() {
815 return false;
816 }
817 chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
818}
819
820#[cfg(test)]
830mod query_param_catalog_tests {
831 use super::{axonendpoint_is_valid_query_param_type, AXONENDPOINT_QUERY_PARAM_TYPES};
832
833 #[test]
834 fn accepts_every_catalog_entry() {
835 for ty in AXONENDPOINT_QUERY_PARAM_TYPES {
836 assert!(
837 axonendpoint_is_valid_query_param_type(ty),
838 "catalog entry `{ty}` must validate"
839 );
840 }
841 }
842
843 #[test]
844 fn rejects_off_catalog_types() {
845 for off in &[
846 "Timestamp", "Date",
848 "DateTime",
849 "List<Text>", "Jsonb", "Bytea",
852 "text", "TEXT",
854 "Number", "", " ", ] {
858 assert!(
859 !axonendpoint_is_valid_query_param_type(off),
860 "off-catalog `{off}` must reject"
861 );
862 }
863 }
864
865 #[test]
866 fn catalog_size_matches_design() {
867 assert_eq!(AXONENDPOINT_QUERY_PARAM_TYPES.len(), 5);
871 }
872}
873
874#[cfg(test)]
875mod query_param_parser_tests {
876 use crate::lexer::Lexer;
877 use crate::parser::Parser;
878
879 fn parse_endpoint_source(src: &str) -> Result<crate::ast::AxonEndpointDefinition, String> {
880 let tokens = Lexer::new(src, "test.axon")
881 .tokenize()
882 .map_err(|e| format!("lex: {}", e.message))?;
883 let mut parser = Parser::new(tokens);
884 let program = parser.parse().map_err(|e| format!("parse: {}", e.message))?;
885 program
886 .declarations
887 .into_iter()
888 .find_map(|d| match d {
889 crate::ast::Declaration::AxonEndpoint(e) => Some(e),
890 _ => None,
891 })
892 .ok_or_else(|| "no axonendpoint in program".to_string())
893 }
894
895 #[test]
896 fn endpoint_with_no_query_block_keeps_empty_vec() {
897 let src = r#"
898 axonendpoint write_secret {
899 method: POST
900 path: "/api/users"
901 body: SecretWriteRequest
902 execute: WriteSecret
903 }
904 "#;
905 let ep = parse_endpoint_source(src).expect("parses");
906 assert!(
907 ep.query_params.is_empty(),
908 "D5 — no `query:` block ⇒ empty query_params"
909 );
910 }
911
912 #[test]
913 fn single_query_param_required() {
914 let src = r#"
915 axonendpoint list_users {
916 method: GET
917 path: "/api/users"
918 query: { status: Text }
919 execute: ListUsers
920 }
921 "#;
922 let ep = parse_endpoint_source(src).expect("parses");
923 assert_eq!(ep.query_params.len(), 1);
924 assert_eq!(ep.query_params[0].name, "status");
925 assert_eq!(ep.query_params[0].type_expr.name, "Text");
926 assert!(!ep.query_params[0].type_expr.optional);
927 }
928
929 #[test]
930 fn optional_query_param_via_question_suffix() {
931 let src = r#"
932 axonendpoint list_users {
933 method: GET
934 path: "/api/users"
935 query: { limit: Int? }
936 execute: ListUsers
937 }
938 "#;
939 let ep = parse_endpoint_source(src).expect("parses");
940 assert_eq!(ep.query_params.len(), 1);
941 assert_eq!(ep.query_params[0].name, "limit");
942 assert_eq!(ep.query_params[0].type_expr.name, "Int");
943 assert!(
944 ep.query_params[0].type_expr.optional,
945 "`?` suffix sets optional"
946 );
947 }
948
949 #[test]
950 fn multiple_query_params_preserve_declaration_order() {
951 let src = r#"
952 axonendpoint search {
953 method: GET
954 path: "/api/search"
955 query: { q: Text, page: Int?, limit: Int?, exact: Bool? }
956 execute: Search
957 }
958 "#;
959 let ep = parse_endpoint_source(src).expect("parses");
960 let names: Vec<&str> = ep.query_params.iter().map(|f| f.name.as_str()).collect();
961 assert_eq!(names, vec!["q", "page", "limit", "exact"]);
962 let types: Vec<&str> = ep
963 .query_params
964 .iter()
965 .map(|f| f.type_expr.name.as_str())
966 .collect();
967 assert_eq!(types, vec!["Text", "Int", "Int", "Bool"]);
968 let optionals: Vec<bool> = ep
969 .query_params
970 .iter()
971 .map(|f| f.type_expr.optional)
972 .collect();
973 assert_eq!(optionals, vec![false, true, true, true]);
974 }
975
976 #[test]
977 fn duplicate_query_param_is_parse_error() {
978 let src = r#"
979 axonendpoint bad {
980 method: GET
981 path: "/api/x"
982 query: { name: Text, name: Int? }
983 execute: Bad
984 }
985 "#;
986 let err = parse_endpoint_source(src).expect_err("must fail");
987 assert!(
988 err.contains("duplicate query param 'name'"),
989 "error must name the duplicate. Got: {err}"
990 );
991 }
992
993 #[test]
994 fn off_catalog_type_with_smart_suggest_hint() {
995 let src = r#"
1000 axonendpoint bad {
1001 method: GET
1002 path: "/api/x"
1003 query: { value: Strng }
1004 execute: Bad
1005 }
1006 "#;
1007 let err = parse_endpoint_source(src).expect_err("must fail");
1008 assert!(
1009 err.contains("unsupported type 'Strng'"),
1010 "error must name the bad type. Got: {err}"
1011 );
1012 assert!(
1013 err.contains("Expected one of: Text | Int | Float | Bool | Uuid"),
1014 "error must list the closed catalog. Got: {err}"
1015 );
1016 }
1017
1018 #[test]
1019 fn close_typo_gets_did_you_mean_hint() {
1020 let src = r#"
1023 axonendpoint bad {
1024 method: GET
1025 path: "/api/x"
1026 query: { value: Txt }
1027 execute: Bad
1028 }
1029 "#;
1030 let err = parse_endpoint_source(src).expect_err("must fail");
1031 assert!(
1032 err.contains("Did you mean") && err.contains("`Text`"),
1033 "smart-suggest must hint `Text`. Got: {err}"
1034 );
1035 }
1036
1037 #[test]
1038 fn every_catalog_type_parses_cleanly() {
1039 for ty in &["Text", "Int", "Float", "Bool", "Uuid"] {
1041 let src = format!(
1042 r#"
1043 axonendpoint x {{
1044 method: GET
1045 path: "/api/x"
1046 query: {{ v: {ty} }}
1047 execute: X
1048 }}
1049 "#
1050 );
1051 let ep = parse_endpoint_source(&src)
1052 .unwrap_or_else(|e| panic!("`{ty}` should parse: {e}"));
1053 assert_eq!(ep.query_params[0].type_expr.name, *ty);
1054 }
1055 }
1056
1057 #[test]
1058 fn comma_optional_between_params() {
1059 let src = r#"
1063 axonendpoint x {
1064 method: GET
1065 path: "/api/x"
1066 query: { a: Text b: Int? }
1067 execute: X
1068 }
1069 "#;
1070 let ep = parse_endpoint_source(src).expect("parses without commas");
1071 assert_eq!(ep.query_params.len(), 2);
1072 }
1073
1074 #[test]
1077 fn double_query_block_is_parse_error() {
1078 let src = r#"
1082 axonendpoint x {
1083 method: GET
1084 path: "/api/x"
1085 query: { a: Text }
1086 query: { b: Int? }
1087 execute: X
1088 }
1089 "#;
1090 let err = parse_endpoint_source(src).expect_err("must fail");
1091 assert!(
1092 err.contains("declares `query: { … }` more than once"),
1093 "error must call out the duplicate block. Got: {err}"
1094 );
1095 assert!(
1096 err.contains("combine all params into a single block"),
1097 "error must hint the canonical fix. Got: {err}"
1098 );
1099 }
1100
1101 #[test]
1102 fn optional_generic_type_is_parse_error_with_canonical_hint() {
1103 let src = r#"
1107 axonendpoint x {
1108 method: GET
1109 path: "/api/x"
1110 query: { value: Optional<Text> }
1111 execute: X
1112 }
1113 "#;
1114 let err = parse_endpoint_source(src).expect_err("must fail");
1115 assert!(
1116 err.contains("generic type `Optional<Text>`"),
1117 "error must name the generic type literally. Got: {err}"
1118 );
1119 assert!(
1120 err.contains("Use `Text?` (the `?` suffix)"),
1121 "error must hint the canonical `Text?` syntax. Got: {err}"
1122 );
1123 }
1124
1125 #[test]
1126 fn list_generic_type_is_parse_error_with_deferral_hint() {
1127 let src = r#"
1132 axonendpoint x {
1133 method: GET
1134 path: "/api/x"
1135 query: { tags: List<Text> }
1136 execute: X
1137 }
1138 "#;
1139 let err = parse_endpoint_source(src).expect_err("must fail");
1140 assert!(
1141 err.contains("generic type `List<Text>`"),
1142 "error must name the generic type. Got: {err}"
1143 );
1144 assert!(
1145 err.contains("Multi-value query params")
1146 && err.contains("honest-deferred"),
1147 "error must mention the multi-value deferral. Got: {err}"
1148 );
1149 }
1150
1151 #[test]
1152 fn other_generic_types_caught_generically() {
1153 let src = r#"
1157 axonendpoint x {
1158 method: GET
1159 path: "/api/x"
1160 query: { value: Stream<Int> }
1161 execute: X
1162 }
1163 "#;
1164 let err = parse_endpoint_source(src).expect_err("must fail");
1165 assert!(
1166 err.contains("generic type `Stream<Int>`"),
1167 "error must name the generic type. Got: {err}"
1168 );
1169 assert!(
1170 err.contains("Text | Int | Float | Bool | Uuid"),
1171 "error must list the closed catalog. Got: {err}"
1172 );
1173 }
1174
1175 #[test]
1176 fn uuid_optional_parses_cleanly() {
1177 let src = r#"
1180 axonendpoint find {
1181 method: GET
1182 path: "/api/x"
1183 query: { after: Uuid? }
1184 execute: Find
1185 }
1186 "#;
1187 let ep = parse_endpoint_source(src).expect("parses");
1188 assert_eq!(ep.query_params.len(), 1);
1189 assert_eq!(ep.query_params[0].name, "after");
1190 assert_eq!(ep.query_params[0].type_expr.name, "Uuid");
1191 assert!(ep.query_params[0].type_expr.optional);
1192 assert_eq!(ep.query_params[0].type_expr.generic_param, "");
1193 }
1194
1195 #[test]
1196 fn empty_query_block_yields_empty_vec() {
1197 let src = r#"
1201 axonendpoint x {
1202 method: GET
1203 path: "/api/x"
1204 query: { }
1205 execute: X
1206 }
1207 "#;
1208 let ep = parse_endpoint_source(src).expect("empty block parses");
1209 assert!(ep.query_params.is_empty());
1210 }
1211
1212 #[test]
1213 fn kivi_secret_write_path_plus_query() {
1214 let src = r#"
1218 axonendpoint write_secret {
1219 method: POST
1220 path: "/api/tenants/{tenant_id}/secrets/{secret_name}"
1221 query: { dry_run: Bool?, overwrite: Bool? }
1222 body: SecretWriteRequest
1223 execute: WriteSecret
1224 }
1225 "#;
1226 let ep = parse_endpoint_source(src).expect("parses");
1227 assert_eq!(ep.path_params, vec!["tenant_id", "secret_name"]);
1229 assert_eq!(ep.query_params.len(), 2);
1231 assert_eq!(ep.query_params[0].name, "dry_run");
1232 assert_eq!(ep.query_params[0].type_expr.name, "Bool");
1233 assert!(ep.query_params[0].type_expr.optional);
1234 assert_eq!(ep.query_params[1].name, "overwrite");
1235 assert_eq!(ep.body_type, "SecretWriteRequest");
1237 }
1238}
1239
1240#[cfg(test)]
1241mod path_param_extraction_tests {
1242 use super::extract_path_param_names;
1243
1244 #[test]
1245 fn empty_path_no_placeholders() {
1246 assert_eq!(extract_path_param_names("/api/users"), Ok(vec![]));
1247 assert_eq!(extract_path_param_names("/"), Ok(vec![]));
1248 assert_eq!(extract_path_param_names(""), Ok(vec![]));
1249 }
1250
1251 #[test]
1252 fn single_placeholder() {
1253 assert_eq!(
1254 extract_path_param_names("/api/users/{id}"),
1255 Ok(vec!["id".to_string()])
1256 );
1257 }
1258
1259 #[test]
1260 fn multiple_placeholders_in_declaration_order() {
1261 assert_eq!(
1262 extract_path_param_names(
1263 "/api/tenants/{tenant_id}/secrets/{secret_name}"
1264 ),
1265 Ok(vec![
1266 "tenant_id".to_string(),
1267 "secret_name".to_string(),
1268 ])
1269 );
1270 }
1271
1272 #[test]
1273 fn kivi_chat_history_path_pattern() {
1274 let names = extract_path_param_names(
1278 "/api/tenants/{tenant_id}/secrets/{secret_name}",
1279 );
1280 assert_eq!(
1281 names,
1282 Ok(vec![
1283 "tenant_id".to_string(),
1284 "secret_name".to_string(),
1285 ])
1286 );
1287 }
1288
1289 #[test]
1290 fn duplicate_placeholder_returns_err() {
1291 assert_eq!(
1292 extract_path_param_names("/api/users/{id}/posts/{id}"),
1293 Err("id".to_string())
1294 );
1295 }
1296
1297 #[test]
1298 fn underscore_and_numeric_in_name() {
1299 assert_eq!(
1300 extract_path_param_names("/api/{user_id}/items/{item_2}"),
1301 Ok(vec!["user_id".to_string(), "item_2".to_string()])
1302 );
1303 }
1304
1305 #[test]
1306 fn leading_underscore_accepted() {
1307 assert_eq!(
1313 extract_path_param_names("/api/{_internal}"),
1314 Ok(vec!["_internal".to_string()])
1315 );
1316 }
1317
1318 #[test]
1319 fn malformed_placeholder_silently_ignored() {
1320 assert_eq!(
1324 extract_path_param_names("/api/{not valid}"),
1325 Ok(vec![])
1326 );
1327 assert_eq!(extract_path_param_names("/api/{}"), Ok(vec![]));
1329 assert_eq!(
1331 extract_path_param_names("/api/{tenant id}/users/{id}"),
1332 Ok(vec!["id".to_string()])
1333 );
1334 }
1335
1336 #[test]
1337 fn unterminated_brace_returns_clean() {
1338 assert_eq!(extract_path_param_names("/api/{id"), Ok(vec![]));
1341 }
1342
1343 #[test]
1344 fn placeholders_at_path_boundaries() {
1345 assert_eq!(
1348 extract_path_param_names("{prefix}/api/users/{id}"),
1349 Ok(vec!["prefix".to_string(), "id".to_string()])
1350 );
1351 assert_eq!(
1352 extract_path_param_names("/api/{id}"),
1353 Ok(vec!["id".to_string()])
1354 );
1355 }
1356
1357 #[test]
1358 fn deduplication_detects_non_adjacent_duplicates() {
1359 assert_eq!(
1361 extract_path_param_names(
1362 "/api/orgs/{org_id}/teams/{team_id}/repos/{org_id}"
1363 ),
1364 Err("org_id".to_string())
1365 );
1366 }
1367
1368 #[test]
1369 fn never_panics_on_arbitrary_input() {
1370 for input in &[
1372 "{",
1373 "}",
1374 "{}",
1375 "{{}}",
1376 "{{{",
1377 "/api/{}/{id}",
1378 "////",
1379 "\u{1F4A1}", "\u{0000}", ] {
1382 let _ = extract_path_param_names(input); }
1384 }
1385}
1386
1387#[cfg(test)]
1388mod capability_slug_tests {
1389 use super::is_valid_capability_slug;
1390
1391 #[test]
1392 fn accepts_canonical_examples() {
1393 assert!(is_valid_capability_slug("admin"));
1394 assert!(is_valid_capability_slug("legal.read"));
1395 assert!(is_valid_capability_slug("hipaa.phi.read"));
1396 assert!(is_valid_capability_slug("bank.officer.senior"));
1397 assert!(is_valid_capability_slug("a"));
1398 assert!(is_valid_capability_slug("a_b"));
1399 assert!(is_valid_capability_slug("a1"));
1400 assert!(is_valid_capability_slug("a.b1_c"));
1401 }
1402
1403 #[test]
1404 fn rejects_empty_string() {
1405 assert!(!is_valid_capability_slug(""));
1406 }
1407
1408 #[test]
1409 fn rejects_uppercase() {
1410 assert!(!is_valid_capability_slug("Admin"));
1411 assert!(!is_valid_capability_slug("admin.READ"));
1412 }
1413
1414 #[test]
1415 fn rejects_digit_first() {
1416 assert!(!is_valid_capability_slug("1admin"));
1417 assert!(!is_valid_capability_slug("admin.1read"));
1418 }
1419
1420 #[test]
1421 fn rejects_hyphen() {
1422 assert!(!is_valid_capability_slug("bank-officer"));
1423 }
1424
1425 #[test]
1426 fn rejects_empty_segments() {
1427 assert!(!is_valid_capability_slug("bank..a"));
1428 assert!(!is_valid_capability_slug(".admin"));
1429 assert!(!is_valid_capability_slug("admin."));
1430 }
1431
1432 #[test]
1433 fn rejects_special_chars() {
1434 assert!(!is_valid_capability_slug("admin@read"));
1435 assert!(!is_valid_capability_slug("admin/read"));
1436 assert!(!is_valid_capability_slug("admin read"));
1437 }
1438}
1439
1440pub struct Parser {
1443 tokens: Vec<Token>,
1444 pos: usize,
1445 hoisted: Vec<Declaration>,
1465 leading_trivia: Vec<Vec<Trivia>>,
1470 trailing_trivia: Vec<Vec<Trivia>>,
1474 last_let_value_kind: String,
1478 loop_depth: u32,
1483 source: Option<String>,
1489 filename: String,
1490}
1491
1492impl Parser {
1493 pub fn new(raw_tokens: Vec<Token>) -> Self {
1494 let mut effective: Vec<Token> = Vec::with_capacity(raw_tokens.len());
1504 let mut leading: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1505 let mut trailing: Vec<Vec<Trivia>> = Vec::with_capacity(raw_tokens.len());
1506
1507 let mut pending_leading: Vec<Trivia> = Vec::new();
1508 let mut last_effective_line: i64 = -1;
1509 for tok in raw_tokens {
1510 if is_comment_token(&tok.ttype) {
1511 let kind = token_to_trivia_kind(&tok.ttype)
1512 .expect("comment token must map to a trivia kind");
1513 let triv = Trivia {
1514 kind,
1515 text: tok.value,
1516 line: tok.line,
1517 column: tok.column,
1518 };
1519 if !effective.is_empty() && (tok.line as i64) == last_effective_line {
1520 trailing.last_mut().unwrap().push(triv);
1521 } else {
1522 pending_leading.push(triv);
1523 }
1524 } else {
1525 last_effective_line = tok.line as i64;
1526 effective.push(tok);
1527 leading.push(std::mem::take(&mut pending_leading));
1528 trailing.push(Vec::new());
1529 }
1530 }
1531
1532 Parser {
1533 hoisted: Vec::new(),
1534 tokens: effective,
1535 pos: 0,
1536 leading_trivia: leading,
1537 trailing_trivia: trailing,
1538 last_let_value_kind: "literal".to_string(),
1539 loop_depth: 0,
1540 source: None,
1541 filename: "<source>".to_string(),
1542 }
1543 }
1544
1545 #[must_use]
1557 pub fn with_source(mut self, source: &str, filename: &str) -> Self {
1558 self.source = Some(source.to_string());
1559 self.filename = filename.to_string();
1560 self
1561 }
1562
1563 pub fn parse(&mut self) -> Result<Program, ParseError> {
1566 let mut program = Program {
1567 declarations: Vec::new(),
1568 declaration_trivia: Vec::new(),
1569 loc: Loc { line: 1, column: 1 },
1570 };
1571 while !self.check(TokenType::Eof) {
1572 let start_pos = self.pos;
1578 let mut decl = match self.parse_declaration() {
1579 Ok(d) => d,
1580 Err(e) => return Err(self.attach_source_to_error(e)),
1581 };
1582 let end_pos = self.pos.saturating_sub(1);
1583 let leading = self
1584 .leading_trivia
1585 .get(start_pos)
1586 .cloned()
1587 .unwrap_or_default();
1588 let trailing = self
1589 .trailing_trivia
1590 .get(end_pos)
1591 .cloned()
1592 .unwrap_or_default();
1593 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1599 program.declarations.push(decl);
1600 program
1601 .declaration_trivia
1602 .push(DeclarationTrivia { leading, trailing });
1603 for hoisted in std::mem::take(&mut self.hoisted) {
1607 program.declarations.push(hoisted);
1608 program.declaration_trivia.push(DeclarationTrivia {
1609 leading: Vec::new(),
1610 trailing: Vec::new(),
1611 });
1612 }
1613 }
1614 crate::voice_desugar::expand(&mut program);
1621 crate::upstream_presets::expand(&mut program);
1622 Ok(program)
1623 }
1624
1625 pub fn parse_with_recovery(&mut self) -> ParseResult {
1658 let mut program = Program {
1659 declarations: Vec::new(),
1660 declaration_trivia: Vec::new(),
1661 loc: Loc { line: 1, column: 1 },
1662 };
1663 let mut errors: Vec<ParseError> = Vec::new();
1664
1665 while !self.check(TokenType::Eof) {
1666 let start_pos = self.pos;
1667 match self.parse_declaration() {
1668 Ok(mut decl) => {
1669 let end_pos = self.pos.saturating_sub(1);
1670 let leading = self
1671 .leading_trivia
1672 .get(start_pos)
1673 .cloned()
1674 .unwrap_or_default();
1675 let trailing = self
1676 .trailing_trivia
1677 .get(end_pos)
1678 .cloned()
1679 .unwrap_or_default();
1680 attach_trivia_to_decl(&mut decl, leading.clone(), trailing.clone());
1681 program.declarations.push(decl);
1682 program
1683 .declaration_trivia
1684 .push(DeclarationTrivia { leading, trailing });
1685 }
1686 Err(err) => {
1687 errors.push(self.attach_source_to_error(err));
1691 if self.pos == start_pos && !self.check(TokenType::Eof) {
1696 self.advance();
1697 }
1698 self.advance_to_sync_point();
1699 }
1700 }
1701 }
1702
1703 ParseResult { program, errors }
1704 }
1705
1706 fn attach_source_to_error(&self, err: ParseError) -> ParseError {
1711 match &self.source {
1712 Some(src) if err.line >= 1 => err.attach_source(src, &self.filename),
1713 _ => err,
1714 }
1715 }
1716
1717 fn advance_to_sync_point(&mut self) {
1722 let mut depth: i32 = 0;
1723 while !self.check(TokenType::Eof) {
1724 let tt = self.current().ttype.clone();
1725 if is_top_level_decl_kw_for_recovery(&tt) && depth <= 0 {
1729 return;
1730 }
1731 if matches!(tt, TokenType::LBrace) {
1732 depth += 1;
1733 } else if matches!(tt, TokenType::RBrace) {
1734 depth -= 1;
1735 }
1736 self.advance();
1737 }
1738 }
1739
1740 fn current(&self) -> &Token {
1743 if self.pos >= self.tokens.len() {
1744 self.tokens.last().unwrap() } else {
1746 &self.tokens[self.pos]
1747 }
1748 }
1749
1750 fn advance(&mut self) -> &Token {
1751 let idx = self.pos;
1752 if self.pos < self.tokens.len() {
1753 self.pos += 1;
1754 }
1755 &self.tokens[idx]
1756 }
1757
1758 fn check(&self, tt: TokenType) -> bool {
1759 self.current().ttype == tt
1760 }
1761
1762 fn consume(&mut self, expected: TokenType) -> Result<Token, ParseError> {
1763 let tok = self.current().clone();
1764 if tok.ttype != expected {
1765 return Err(ParseError {
1766 message: format!(
1767 "Expected {:?}, found {:?}('{}')",
1768 expected, tok.ttype, tok.value
1769 ),
1770 line: tok.line,
1771 column: tok.column,
1772 ..Default::default()
1773 });
1774 }
1775 self.pos += 1;
1776 Ok(tok)
1777 }
1778
1779 fn error(&self, message: &str) -> ParseError {
1781 let tok = self.current();
1782 ParseError { message: message.to_string(), line: tok.line, column: tok.column, ..Default::default() }
1783 }
1784
1785 fn consume_any_ident_or_kw(&mut self) -> Result<Token, ParseError> {
1787 let tok = self.current().clone();
1788 match tok.ttype {
1789 TokenType::Identifier
1790 | TokenType::Bool
1791 | TokenType::StringLit
1792 | TokenType::Integer
1793 | TokenType::Float => {
1794 self.pos += 1;
1795 Ok(tok)
1796 }
1797 _ => {
1798 if !tok.value.is_empty()
1800 && tok.value.chars().all(|c| c.is_alphanumeric() || c == '_')
1801 && tok.ttype != TokenType::Eof
1802 {
1803 self.pos += 1;
1804 Ok(tok)
1805 } else {
1806 Err(ParseError {
1807 message: format!(
1808 "Expected identifier or keyword value, found {:?}('{}')",
1809 tok.ttype, tok.value
1810 ),
1811 line: tok.line,
1812 column: tok.column,
1813 ..Default::default()
1814 })
1815 }
1816 }
1817 }
1818 }
1819
1820 fn consume_number(&mut self) -> Result<f64, ParseError> {
1821 let tok = self.current().clone();
1822 match tok.ttype {
1823 TokenType::Float | TokenType::Integer => {
1824 self.pos += 1;
1825 tok.value.parse::<f64>().map_err(|_| ParseError {
1826 message: format!("Invalid number '{}'", tok.value),
1827 line: tok.line,
1828 column: tok.column,
1829 ..Default::default()
1830 })
1831 }
1832 _ => Err(ParseError {
1833 message: format!("Expected number, found {:?}('{}')", tok.ttype, tok.value),
1834 line: tok.line,
1835 column: tok.column,
1836 ..Default::default()
1837 }),
1838 }
1839 }
1840
1841 fn parse_bool(&mut self) -> Result<bool, ParseError> {
1842 let tok = self.consume(TokenType::Bool)?;
1843 Ok(tok.value == "true")
1844 }
1845
1846 fn loc_of(&self, tok: &Token) -> Loc {
1847 Loc {
1848 line: tok.line,
1849 column: tok.column,
1850 }
1851 }
1852
1853 fn check_run_modifier(&self) -> bool {
1854 if self.current().value == "with" {
1861 return true;
1862 }
1863 matches!(
1864 self.current().ttype,
1865 TokenType::As
1866 | TokenType::Within
1867 | TokenType::ConstrainedBy
1868 | TokenType::OnFailure
1869 | TokenType::OutputTo
1870 | TokenType::Effort
1871 )
1872 }
1873
1874 fn parse_string_list(&mut self) -> Result<Vec<String>, ParseError> {
1877 self.consume(TokenType::LBracket)?;
1878 let mut items = Vec::new();
1879 items.push(self.consume(TokenType::StringLit)?.value);
1880 while self.check(TokenType::Comma) {
1881 self.advance();
1882 items.push(self.consume(TokenType::StringLit)?.value);
1883 }
1884 self.consume(TokenType::RBracket)?;
1885 Ok(items)
1886 }
1887
1888 fn parse_bracketed_strings(&mut self) -> Result<Vec<String>, ParseError> {
1897 self.consume(TokenType::LBracket)?;
1898 let mut items = Vec::new();
1899 if !self.check(TokenType::RBracket) {
1900 items.push(self.consume(TokenType::StringLit)?.value);
1901 while self.check(TokenType::Comma) {
1902 self.advance();
1903 if self.check(TokenType::RBracket) {
1904 break; }
1906 items.push(self.consume(TokenType::StringLit)?.value);
1907 }
1908 }
1909 self.consume(TokenType::RBracket)?;
1910 Ok(items)
1911 }
1912
1913 fn parse_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1914 let mut names = Vec::new();
1915 names.push(self.consume(TokenType::Identifier)?.value);
1916 while self.check(TokenType::Comma) {
1917 self.advance();
1918 names.push(self.consume(TokenType::Identifier)?.value);
1919 }
1920 Ok(names)
1921 }
1922
1923 fn parse_bracketed_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1924 self.consume(TokenType::LBracket)?;
1925 let items = self.parse_extended_identifier_list()?;
1926 self.consume(TokenType::RBracket)?;
1927 Ok(items)
1928 }
1929
1930 fn parse_extended_identifier_list(&mut self) -> Result<Vec<String>, ParseError> {
1931 let mut items = Vec::new();
1932 items.push(self.consume_any_ident_or_kw()?.value);
1933 while self.check(TokenType::Comma) {
1934 self.advance();
1935 items.push(self.consume_any_ident_or_kw()?.value);
1936 }
1937 Ok(items)
1938 }
1939
1940 fn parse_dotted_identifier(&mut self) -> Result<String, ParseError> {
1941 let mut parts = vec![self.consume_any_ident_or_kw()?.value];
1942 while self.check(TokenType::Dot) {
1943 self.advance();
1944 parts.push(self.consume_any_ident_or_kw()?.value);
1945 }
1946 Ok(parts.join("."))
1947 }
1948
1949 fn parse_subject(&mut self) -> Result<String, ParseError> {
1973 let t = self.current().clone();
1974 match t.ttype {
1975 TokenType::StringLit => {
1976 self.advance();
1977 Ok(format!("\"{}\"", t.value))
1978 }
1979 TokenType::Integer | TokenType::Float => {
1980 self.advance();
1981 Ok(t.value)
1982 }
1983 _ => self.parse_dotted_identifier(),
1984 }
1985 }
1986
1987 fn parse_expression_string(&mut self) -> Result<String, ParseError> {
1988 if self.check(TokenType::LBracket) {
1989 let items = self.parse_bracketed_dot_identifiers()?;
1990 return Ok(format!("[{}]", items.join(", ")));
1991 }
1992 self.parse_dotted_identifier()
1993 }
1994
1995 fn parse_bracketed_dot_identifiers(&mut self) -> Result<Vec<String>, ParseError> {
1996 self.consume(TokenType::LBracket)?;
1997 let mut items = vec![self.parse_dotted_identifier()?];
1998 while self.check(TokenType::Comma) {
1999 self.advance();
2000 items.push(self.parse_dotted_identifier()?);
2001 }
2002 self.consume(TokenType::RBracket)?;
2003 Ok(items)
2004 }
2005
2006 fn parse_argument_list(&mut self) -> Result<Vec<String>, ParseError> {
2007 let mut args = Vec::new();
2008 while !self.check(TokenType::RParen) {
2009 let tok = self.current().clone();
2010 match tok.ttype {
2011 TokenType::StringLit | TokenType::Integer | TokenType::Float => {
2012 self.advance();
2013 args.push(tok.value);
2014 }
2015 TokenType::Identifier => {
2016 self.advance();
2017 let mut val = tok.value;
2018 if self.check(TokenType::Dot) {
2019 self.advance();
2020 val.push('.');
2021 val.push_str(&self.consume_any_ident_or_kw()?.value);
2022 }
2023 args.push(val);
2024 }
2025 _ => {
2026 self.advance();
2027 let key = tok.value;
2028 if self.check(TokenType::Colon) {
2029 self.advance();
2030 let v = self.advance().value.clone();
2031 args.push(format!("{key}:{v}"));
2032 } else {
2033 args.push(key);
2034 }
2035 }
2036 }
2037 if self.check(TokenType::Comma) {
2038 self.advance();
2039 }
2040 }
2041 Ok(args)
2042 }
2043
2044 fn skip_value(&mut self) {
2046 match self.current().ttype {
2047 TokenType::LBracket => {
2048 self.advance();
2049 let mut depth = 1u32;
2050 while depth > 0 && !self.check(TokenType::Eof) {
2051 if self.check(TokenType::LBracket) {
2052 depth += 1;
2053 } else if self.check(TokenType::RBracket) {
2054 depth -= 1;
2055 }
2056 self.advance();
2057 }
2058 }
2059 TokenType::LBrace => {
2060 self.advance();
2061 let mut depth = 1u32;
2062 while depth > 0 && !self.check(TokenType::Eof) {
2063 if self.check(TokenType::LBrace) {
2064 depth += 1;
2065 } else if self.check(TokenType::RBrace) {
2066 depth -= 1;
2067 }
2068 self.advance();
2069 }
2070 }
2071 TokenType::Lt => {
2072 self.advance();
2074 let mut depth = 1u32;
2075 while depth > 0 && !self.check(TokenType::Eof) {
2076 if self.check(TokenType::Lt) {
2077 depth += 1;
2078 } else if self.check(TokenType::Gt) {
2079 depth -= 1;
2080 }
2081 self.advance();
2082 }
2083 }
2084 _ => {
2085 self.advance();
2086 while self.check(TokenType::Dot) {
2087 self.advance();
2088 self.advance();
2089 }
2090 }
2091 }
2092 }
2093
2094 fn skip_braced_block(&mut self) -> Result<(), ParseError> {
2096 self.consume(TokenType::LBrace)?;
2097 let mut depth = 1u32;
2098 while depth > 0 {
2099 if self.check(TokenType::Eof) {
2100 let tok = self.current();
2101 return Err(ParseError {
2102 message: "Unterminated block — expected '}'".to_string(),
2103 line: tok.line,
2104 column: tok.column,
2105 ..Default::default()
2106 });
2107 }
2108 if self.check(TokenType::LBrace) {
2109 depth += 1;
2110 } else if self.check(TokenType::RBrace) {
2111 depth -= 1;
2112 }
2113 self.advance();
2114 }
2115 Ok(())
2116 }
2117
2118 fn at_declaration_start(&self) -> bool {
2119 is_declaration_keyword(&self.current().ttype) || self.check(TokenType::Eof)
2120 }
2121
2122 fn parse_declaration(&mut self) -> Result<Declaration, ParseError> {
2125 let tok = self.current().clone();
2126
2127 if tok.ttype == TokenType::Budget && self.peek_is_identifier() {
2135 return self.parse_top_level_budget().map(Declaration::Budget);
2136 }
2137
2138 match tok.ttype {
2139 TokenType::Import => self.parse_import().map(Declaration::Import),
2140 TokenType::Persona => self.parse_persona().map(Declaration::Persona),
2141 TokenType::Context => self.parse_context().map(Declaration::Context),
2142 TokenType::Anchor => self.parse_anchor().map(Declaration::Anchor),
2143 TokenType::Memory => self.parse_memory().map(Declaration::Memory),
2144 TokenType::Tool => self.parse_tool().map(Declaration::Tool),
2145 TokenType::Type => self.parse_type_def().map(Declaration::Type),
2146 TokenType::Flow => self.parse_flow().map(Declaration::Flow),
2147 TokenType::Effect => self.parse_effect().map(Declaration::Effect),
2150 TokenType::Intent => self.parse_intent().map(Declaration::Intent),
2151 TokenType::Run => self.parse_run().map(Declaration::Run),
2152 TokenType::Let => self.parse_let().map(Declaration::Let),
2153 TokenType::Know | TokenType::Believe | TokenType::Speculate | TokenType::Doubt => {
2154 self.parse_epistemic_block().map(Declaration::Epistemic)
2155 }
2156 TokenType::Lambda => self.parse_lambda_data().map(Declaration::LambdaData),
2157
2158 TokenType::Agent => self.parse_agent().map(Declaration::Agent),
2160 TokenType::Shield => self.parse_shield().map(Declaration::Shield),
2161 TokenType::Window => self.parse_window().map(Declaration::Window),
2163 TokenType::Pix => self.parse_pix().map(Declaration::Pix),
2164 TokenType::Ledger => self.parse_ledger().map(Declaration::Ledger),
2165 TokenType::Psyche => self.parse_psyche().map(Declaration::Psyche),
2166 TokenType::Corpus => self.parse_corpus().map(Declaration::Corpus),
2167 TokenType::Dataspace => self.parse_dataspace().map(Declaration::Dataspace),
2168 TokenType::Ots => self.parse_ots().map(Declaration::Ots),
2169 TokenType::Mandate => self.parse_mandate().map(Declaration::Mandate),
2170 TokenType::Compute => self.parse_compute().map(Declaration::Compute),
2171 TokenType::Daemon => self.parse_daemon().map(Declaration::Daemon),
2172 TokenType::Extension => self.parse_extension().map(Declaration::Extension),
2173 TokenType::AxonStore => self.parse_axonstore().map(Declaration::AxonStore),
2174 TokenType::AxonEndpoint => self.parse_axonendpoint().map(Declaration::AxonEndpoint),
2175
2176 TokenType::Resource => self.parse_resource().map(Declaration::Resource),
2178 TokenType::Fabric => self.parse_fabric().map(Declaration::Fabric),
2179 TokenType::Manifest => self.parse_manifest().map(Declaration::Manifest),
2180 TokenType::Observe => self.parse_observe().map(Declaration::Observe),
2181
2182 TokenType::Reconcile => self.parse_reconcile().map(Declaration::Reconcile),
2184 TokenType::Lease => self.parse_lease().map(Declaration::Lease),
2185 TokenType::Ensemble => self.parse_ensemble().map(Declaration::Ensemble),
2186
2187 TokenType::Session => self.parse_session_definition().map(Declaration::Session),
2189 TokenType::Topology => self.parse_topology().map(Declaration::Topology),
2190
2191 TokenType::Socket => self.parse_socket().map(Declaration::Socket),
2193
2194 TokenType::Upstream => self.parse_upstream().map(Declaration::Upstream),
2196
2197 TokenType::Voice => self.parse_voice().map(Declaration::Voice),
2199
2200 TokenType::Cors => self.parse_cors().map(Declaration::Cors),
2202
2203 TokenType::Cache => self.parse_cache().map(Declaration::Cache),
2205 TokenType::Document => self.parse_document().map(Declaration::Document),
2206
2207 TokenType::Deliver => self.parse_deliver().map(Declaration::Deliver),
2209 TokenType::Notify => self.parse_notify().map(Declaration::Notify),
2210
2211 TokenType::Savant => self.parse_savant().map(Declaration::Savant),
2213
2214 TokenType::Synth => self.parse_synth().map(Declaration::Synth),
2216
2217 TokenType::Scope => self.parse_scope().map(Declaration::Scope),
2219
2220 TokenType::Credential => self.parse_credential().map(Declaration::Credential),
2222
2223 TokenType::Observable => self.parse_observable().map(Declaration::Observable),
2225
2226 TokenType::Witness => self.parse_witness().map(Declaration::Witness),
2228
2229 TokenType::Immune => self.parse_immune().map(Declaration::Immune),
2231 TokenType::Reflex => self.parse_reflex().map(Declaration::Reflex),
2232 TokenType::Heal => self.parse_heal().map(Declaration::Heal),
2233
2234 TokenType::Component => self.parse_component().map(Declaration::Component),
2236 TokenType::View => self.parse_view().map(Declaration::View),
2237
2238 TokenType::Channel => self.parse_channel().map(Declaration::Channel),
2240
2241 TokenType::Ingest
2244 | TokenType::Persist
2245 | TokenType::Retrieve
2246 | TokenType::Mutate
2247 | TokenType::Purge
2248 | TokenType::Transact => self.parse_generic_declaration(),
2249
2250 TokenType::Mcp => self.parse_generic_declaration(),
2252
2253 _ => {
2254 let hint = crate::smart_suggest::suggest_for(
2258 &tok.value,
2259 crate::smart_suggest::TOP_LEVEL_KEYWORD_NAMES,
2260 );
2261 let base = format!(
2262 "Unexpected token at top level: '{}' — expected declaration \
2263 (persona, context, anchor, flow, run, ...)",
2264 tok.value
2265 );
2266 let message = if hint.is_empty() {
2267 base
2268 } else {
2269 format!("{base}. {hint}")
2270 };
2271 Err(ParseError {
2272 message,
2273 line: tok.line,
2274 column: tok.column,
2275 ..Default::default()
2276 })
2277 }
2278 }
2279 }
2280
2281 fn parse_import(&mut self) -> Result<ImportNode, ParseError> {
2284 let tok = self.consume(TokenType::Import)?;
2285 let loc = self.loc_of(&tok);
2286
2287 let mut path_parts = Vec::new();
2288
2289 if self.check(TokenType::At) {
2291 self.advance();
2292 let first = self.consume(TokenType::Identifier)?;
2293 path_parts.push(format!("@{}", first.value));
2294 } else {
2295 let first = self.consume(TokenType::Identifier)?;
2296 path_parts.push(first.value);
2297 }
2298
2299 while self.check(TokenType::Dot) {
2300 self.advance();
2301 if self.check(TokenType::LBrace) {
2302 break;
2303 }
2304 let part = self.consume(TokenType::Identifier)?;
2305 path_parts.push(part.value);
2306 }
2307
2308 let mut names = Vec::new();
2309 if self.check(TokenType::LBrace) {
2310 self.advance();
2311 names = self.parse_identifier_list()?;
2312 self.consume(TokenType::RBrace)?;
2313 }
2314
2315 let mut allow_downgrade = false;
2324 if self.check(TokenType::At) {
2325 let at_tok = self.current().clone();
2326 self.advance();
2327 let ident = self.consume(TokenType::Identifier)?;
2328 if ident.value == "allow_downgrade" {
2329 allow_downgrade = true;
2330 } else {
2331 return Err(ParseError {
2332 message: format!(
2333 "unknown import annotation '@{}' — the only import annotation is \
2334 `@allow_downgrade` (the §115 epistemic-downgrade acknowledgment).",
2335 ident.value
2336 ),
2337 line: at_tok.line,
2338 column: at_tok.column,
2339 ..Default::default()
2340 });
2341 }
2342 }
2343
2344 let next_is_apx = self
2359 .tokens
2360 .get(self.pos + 1)
2361 .map(|t| t.value == "apx")
2362 .unwrap_or(false);
2363 if self.current().value == "with" && next_is_apx {
2364 let tok = self.current().clone();
2365 return Err(ParseError {
2366 message: "`import … with apx { … }` is RETRACTED (§111). The apx policy block was \
2367 parsed and silently DISCARDED — it never reached the IR, and no epistemic \
2368 dependency manager exists: no MEC/PCC verification, no EPR ranking, no \
2369 quarantine, no compliance gate. Declaring it verified nothing while \
2370 implying your supply chain was checked. Remove the `with apx { … }` \
2371 clause; the plain `import` resolves through the §115 Epistemic Module \
2372 System."
2373 .to_string(),
2374 line: tok.line,
2375 column: tok.column,
2376 ..Default::default()
2377 });
2378 }
2379
2380 Ok(ImportNode {
2381 module_path: path_parts,
2382 names,
2383 allow_downgrade,
2384 loc,
2385 leading_trivia: Vec::new(),
2386 trailing_trivia: Vec::new(),
2387 })
2388 }
2389
2390 fn parse_persona(&mut self) -> Result<PersonaDefinition, ParseError> {
2393 let tok = self.consume(TokenType::Persona)?;
2394 let loc = self.loc_of(&tok);
2395 let name = self.consume(TokenType::Identifier)?.value;
2396 self.consume(TokenType::LBrace)?;
2397
2398 let mut node = PersonaDefinition {
2399 name,
2400 domain: Vec::new(),
2401 tone: String::new(),
2402 confidence_threshold: None,
2403 cite_sources: None,
2404 refuse_if: Vec::new(),
2405 language: String::new(),
2406 description: String::new(),
2407 loc,
2408 leading_trivia: Vec::new(),
2409 trailing_trivia: Vec::new(),
2410 };
2411
2412 while !self.check(TokenType::RBrace) {
2413 let field_name = self.current().value.clone();
2414 self.advance();
2415 self.consume(TokenType::Colon)?;
2416
2417 match field_name.as_str() {
2418 "domain" => node.domain = self.parse_string_list()?,
2419 "tone" => node.tone = self.consume_any_ident_or_kw()?.value,
2420 "confidence_threshold" => node.confidence_threshold = Some(self.consume_number()?),
2421 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2422 "refuse_if" => node.refuse_if = self.parse_bracketed_identifiers()?,
2423 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2424 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2425 _ => self.skip_value(),
2426 }
2427 }
2428 self.consume(TokenType::RBrace)?;
2429 Ok(node)
2430 }
2431
2432 fn parse_context(&mut self) -> Result<ContextDefinition, ParseError> {
2435 let tok = self.consume(TokenType::Context)?;
2436 let loc = self.loc_of(&tok);
2437 let name = self.consume(TokenType::Identifier)?.value;
2438 self.consume(TokenType::LBrace)?;
2439
2440 let mut node = ContextDefinition {
2441 name,
2442 memory_scope: String::new(),
2443 language: String::new(),
2444 depth: String::new(),
2445 max_tokens: None,
2446 temperature: None,
2447 cite_sources: None,
2448 now_tz: None,
2449 loc,
2450 leading_trivia: Vec::new(),
2451 trailing_trivia: Vec::new(),
2452 };
2453
2454 while !self.check(TokenType::RBrace) {
2455 let field_name = self.current().value.clone();
2456 self.advance();
2457 self.consume(TokenType::Colon)?;
2458
2459 match field_name.as_str() {
2460 "memory" => node.memory_scope = self.consume_any_ident_or_kw()?.value,
2461 "language" => node.language = self.consume(TokenType::StringLit)?.value,
2462 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
2463 "now" => node.now_tz = Some(self.consume(TokenType::StringLit)?.value),
2465 "max_tokens" => {
2466 node.max_tokens = Some(
2467 self.consume(TokenType::Integer)?
2468 .value
2469 .parse::<i64>()
2470 .unwrap_or(0),
2471 )
2472 }
2473 "temperature" => node.temperature = Some(self.consume_number()?),
2474 "cite_sources" => node.cite_sources = Some(self.parse_bool()?),
2475 _ => self.skip_value(),
2476 }
2477 }
2478 self.consume(TokenType::RBrace)?;
2479 Ok(node)
2480 }
2481
2482 fn parse_anchor(&mut self) -> Result<AnchorConstraint, ParseError> {
2485 let tok = self.consume(TokenType::Anchor)?;
2486 let loc = self.loc_of(&tok);
2487 let name = self.consume(TokenType::Identifier)?.value;
2488 self.consume(TokenType::LBrace)?;
2489
2490 let mut node = AnchorConstraint {
2491 name,
2492 require: String::new(),
2493 reject: Vec::new(),
2494 enforce: String::new(),
2495 description: String::new(),
2496 confidence_floor: None,
2497 unknown_response: String::new(),
2498 on_violation: String::new(),
2499 on_violation_target: String::new(),
2500 loc,
2501 leading_trivia: Vec::new(),
2502 trailing_trivia: Vec::new(),
2503 };
2504
2505 while !self.check(TokenType::RBrace) {
2506 let field_name = self.current().value.clone();
2507 self.advance();
2508 self.consume(TokenType::Colon)?;
2509
2510 match field_name.as_str() {
2511 "require" => node.require = self.consume_any_ident_or_kw()?.value,
2512 "description" => node.description = self.consume(TokenType::StringLit)?.value,
2513 "reject" => node.reject = self.parse_bracketed_identifiers()?,
2514 "enforce" => node.enforce = self.consume_any_ident_or_kw()?.value,
2515 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
2516 "unknown_response" => {
2517 node.unknown_response = self.consume(TokenType::StringLit)?.value
2518 }
2519 "on_violation" => {
2520 let action = self.consume_any_ident_or_kw()?.value;
2522 node.on_violation = action.clone();
2523 if action == "raise" || action == "fallback" {
2524 node.on_violation_target = self.consume_any_ident_or_kw()?.value;
2525 }
2526 }
2527 _ => self.skip_value(),
2528 }
2529 }
2530 self.consume(TokenType::RBrace)?;
2531 Ok(node)
2532 }
2533
2534 fn parse_memory(&mut self) -> Result<MemoryDefinition, ParseError> {
2537 let tok = self.consume(TokenType::Memory)?;
2538 let loc = self.loc_of(&tok);
2539 let name = self.consume(TokenType::Identifier)?.value;
2540 self.consume(TokenType::LBrace)?;
2541
2542 let mut node = MemoryDefinition {
2543 name,
2544 store: String::new(),
2545 backend: String::new(),
2546 retrieval: String::new(),
2547 decay: String::new(),
2548 loc,
2549 leading_trivia: Vec::new(),
2550 trailing_trivia: Vec::new(),
2551 };
2552
2553 while !self.check(TokenType::RBrace) {
2554 let field_name = self.current().value.clone();
2555 self.advance();
2556 self.consume(TokenType::Colon)?;
2557
2558 match field_name.as_str() {
2559 "store" => node.store = self.consume_any_ident_or_kw()?.value,
2560 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
2561 "retrieval" => node.retrieval = self.consume_any_ident_or_kw()?.value,
2562 "decay" => {
2563 if self.check(TokenType::Duration) {
2564 node.decay = self.advance().value.clone();
2565 } else {
2566 node.decay = self.consume_any_ident_or_kw()?.value;
2567 }
2568 }
2569 _ => self.skip_value(),
2570 }
2571 }
2572 self.consume(TokenType::RBrace)?;
2573 Ok(node)
2574 }
2575
2576 fn parse_tool(&mut self) -> Result<ToolDefinition, ParseError> {
2579 let tok = self.consume(TokenType::Tool)?;
2580 let loc = self.loc_of(&tok);
2581 let name = self.consume(TokenType::Identifier)?.value;
2582 self.consume(TokenType::LBrace)?;
2583
2584 let mut node = ToolDefinition {
2585 name,
2586 provider: String::new(),
2587 max_results: None,
2588 filter_expr: String::new(),
2589 timeout: String::new(),
2590 runtime: String::new(),
2591 resource_ref: String::new(),
2592 sandbox: None,
2593 effects: None,
2594 parameters: Vec::new(),
2595 output_type: None,
2596 requires: Vec::new(),
2597 secret: String::new(),
2598 secret_partition: String::new(),
2599 target: None,
2600 risk: None,
2601 argv: Vec::new(),
2602 cache: String::new(),
2603 scrape: None,
2604 loc,
2605 leading_trivia: Vec::new(),
2606 trailing_trivia: Vec::new(),
2607 };
2608
2609 let mut unknown_fields: Vec<(String, u32, u32)> = Vec::new();
2616
2617 while !self.check(TokenType::RBrace) {
2618 let field_tok = self.current().clone();
2619 let field_name = field_tok.value.clone();
2620 self.advance();
2621 self.consume(TokenType::Colon)?;
2622
2623 match field_name.as_str() {
2624 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
2625 "max_results" => {
2626 node.max_results = Some(
2627 self.consume(TokenType::Integer)?
2628 .value
2629 .parse::<i64>()
2630 .unwrap_or(0),
2631 )
2632 }
2633 "filter" => node.filter_expr = self.parse_filter_expression()?,
2634 "timeout" => node.timeout = self.consume(TokenType::Duration)?.value,
2635 "runtime" => node.runtime = self.consume_any_ident_or_kw()?.value,
2636 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
2640 "sandbox" => node.sandbox = Some(self.parse_bool()?),
2641 "effects" => node.effects = Some(self.parse_effect_row()?),
2642 "parameters" => node.parameters = self.parse_tool_param_schema()?,
2644 "output_type" => node.output_type = Some(self.parse_output_type_string()?),
2645 "requires" => {
2651 let bracket_tok = self.current().clone();
2652 let items = self.parse_bracketed_dot_identifiers()?;
2653 for slug in &items {
2654 if !is_valid_capability_slug(slug) {
2655 return Err(ParseError {
2656 message: format!(
2657 "Invalid capability slug '{slug}' in tool '{}' \
2658 `requires:`. Scope slugs must match \
2659 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — the same \
2660 grammar as `credential.grants`. Examples: \
2661 `w_organization_social`, `video.publish`.",
2662 node.name
2663 ),
2664 line: bracket_tok.line,
2665 column: bracket_tok.column,
2666 ..Default::default()
2667 });
2668 }
2669 }
2670 node.requires = items;
2671 }
2672 "secret" => node.secret = self.parse_dotted_identifier()?,
2676 "secret_partition" => {
2682 node.secret_partition = self.consume_any_ident_or_kw()?.value
2683 }
2684 "target" => node.target = Some(self.consume_any_ident_or_kw()?.value),
2686 "risk" => node.risk = Some(self.consume_any_ident_or_kw()?.value),
2687 "argv" => node.argv = self.parse_bracketed_strings()?,
2691 "cache" => node.cache = self.consume_any_ident_or_kw()?.value,
2694 "scrape" => node.scrape = Some(self.parse_scrape_spec()?),
2697 _ => {
2698 unknown_fields.push((field_name, field_tok.line, field_tok.column));
2699 self.skip_value();
2700 }
2701 }
2702 }
2703 self.consume(TokenType::RBrace)?;
2704
2705 if node.target.is_some() || node.scrape.is_some() {
2713 if let Some((field_name, line, column)) = unknown_fields.into_iter().next() {
2714 let (surface, valid) = if node.target.is_some() {
2715 (
2716 "technician tool (§Fase 84 D84.13)",
2717 "provider, parameters, output_type, timeout, effects, target, risk, argv",
2718 )
2719 } else {
2720 (
2721 "web-acquisition tool (§Fase 98 D98.2)",
2722 "provider, parameters, output_type, timeout, effects, secret, \
2723 secret_partition, cache, scrape",
2724 )
2725 };
2726 return Err(ParseError {
2727 message: format!(
2728 "unknown field `{field_name}` in {surface} `{}` — this tool uses \
2729 strict field checking; valid fields: {valid}",
2730 node.name
2731 ),
2732 line,
2733 column,
2734 ..Default::default()
2735 });
2736 }
2737 }
2738 Ok(node)
2739 }
2740
2741 fn parse_scrape_spec(&mut self) -> Result<crate::ast::ScrapeSpec, ParseError> {
2746 let open = self.consume(TokenType::LBrace)?;
2747 let loc = self.loc_of(&open);
2748 let mut spec = crate::ast::ScrapeSpec {
2749 loc,
2750 ..Default::default()
2751 };
2752 while !self.check(TokenType::RBrace) {
2753 let field_tok = self.current().clone();
2754 let field_name = field_tok.value.clone();
2755 self.advance();
2756 self.consume(TokenType::Colon)?;
2757 match field_name.as_str() {
2758 "engine" => spec.engine = Some(self.consume_any_ident_or_kw()?.value),
2759 "impersonate" => spec.impersonate = Some(self.consume_any_ident_or_kw()?.value),
2760 "render_wait" => spec.render_wait = Some(self.consume(TokenType::Duration)?.value),
2761 "proxy" => spec.proxy = self.parse_dotted_identifier()?,
2762 "respect_robots" => spec.respect_robots = Some(self.parse_bool()?),
2763 "extract" => spec.extract = self.parse_bracketed_strings()?,
2764 "adaptive" => spec.adaptive = Some(self.parse_bool()?),
2765 "similarity_floor" => spec.similarity_floor = self.parse_optional_float(),
2766 "follow" => spec.follow = self.consume(TokenType::StringLit)?.value,
2767 "max_depth" => {
2768 spec.max_depth =
2769 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2770 }
2771 "max_pages" => {
2772 spec.max_pages =
2773 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2774 }
2775 "concurrency" => {
2776 spec.concurrency =
2777 Some(self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0))
2778 }
2779 "politeness" => spec.politeness = self.consume_any_ident_or_kw()?.value,
2780 "checkpoint" => spec.checkpoint = self.consume_any_ident_or_kw()?.value,
2781 other => {
2782 return Err(self.error(&format!(
2783 "unknown scrape field `{other}` — the `scrape: {{ … }}` block is a \
2784 closed catalog (§Fase 98 D98.2); valid fields: engine, impersonate, \
2785 render_wait, proxy, respect_robots, extract, adaptive, \
2786 similarity_floor, follow, max_depth, max_pages, concurrency, \
2787 politeness, checkpoint"
2788 )));
2789 }
2790 }
2791 }
2792 self.consume(TokenType::RBrace)?;
2793 Ok(spec)
2794 }
2795
2796 fn parse_tool_param_schema(&mut self) -> Result<Vec<Parameter>, ParseError> {
2802 self.consume(TokenType::LBrace)?;
2803 let mut params = Vec::new();
2804 while !self.check(TokenType::RBrace) {
2805 let name = self.consume_any_ident_or_kw()?;
2809 let ploc = self.loc_of(&name);
2810 self.consume(TokenType::Colon)?;
2811 let type_expr = self.parse_type_expr()?;
2812 params.push(Parameter {
2813 name: name.value,
2814 type_expr,
2815 loc: ploc,
2816 });
2817 if self.check(TokenType::Comma) {
2818 self.advance();
2819 } else {
2820 break;
2821 }
2822 }
2823 self.consume(TokenType::RBrace)?;
2824 Ok(params)
2825 }
2826
2827 fn parse_filter_expression(&mut self) -> Result<String, ParseError> {
2828 let name = self.consume_any_ident_or_kw()?.value;
2829 if self.check(TokenType::LParen) {
2830 self.advance();
2831 let mut parts = vec![name, "(".to_string()];
2832 while !self.check(TokenType::RParen) {
2833 parts.push(self.advance().value.clone());
2834 }
2835 self.consume(TokenType::RParen)?;
2836 parts.push(")".to_string());
2837 Ok(parts.join(""))
2838 } else {
2839 Ok(name)
2840 }
2841 }
2842
2843 fn parse_effect_row(&mut self) -> Result<EffectRow, ParseError> {
2844 let tok = self.consume(TokenType::Lt)?;
2845 let loc = self.loc_of(&tok);
2846 let mut effects = Vec::new();
2847 let mut epistemic_level = String::new();
2848
2849 while !self.check(TokenType::Gt) {
2850 let name = self.consume_any_ident_or_kw()?.value;
2851 if self.check(TokenType::Colon) {
2852 self.advance();
2853 let level = self.parse_qualifier_value()?;
2870 if name == "epistemic" {
2871 epistemic_level = level;
2872 } else {
2873 effects.push(format!("{name}:{level}"));
2874 }
2875 } else {
2876 effects.push(name);
2877 }
2878 if self.check(TokenType::Comma) {
2879 self.advance();
2880 }
2881 }
2882 self.consume(TokenType::Gt)?;
2883
2884 Ok(EffectRow {
2885 effects,
2886 epistemic_level,
2887 loc,
2888 })
2889 }
2890
2891 fn parse_qualifier_value(&mut self) -> Result<String, ParseError> {
2900 let mut buf = self.consume_dotted_slug_segment()?;
2901 loop {
2902 let sep = if self.check(TokenType::Dot) {
2903 '.'
2904 } else if self.check(TokenType::Colon) {
2905 ':'
2906 } else {
2907 break;
2908 };
2909 self.advance();
2910 let part = self.consume_dotted_slug_segment()?;
2911 buf.push(sep);
2912 buf.push_str(&part);
2913 }
2914 Ok(buf)
2915 }
2916
2917 fn consume_dotted_slug_segment(&mut self) -> Result<String, ParseError> {
2929 let first = self.consume_any_ident_or_kw()?;
2930 let mut buf = first.value.clone();
2931 let mut next_line = first.line;
2932 let mut next_col = first.column + first.value.chars().count() as u32;
2933 loop {
2934 let cur = self.current();
2935 let is_segment_token = matches!(cur.ttype, TokenType::Identifier | TokenType::Integer,);
2936 if !is_segment_token {
2937 break;
2938 }
2939 if cur.line != next_line || cur.column != next_col {
2940 break;
2941 }
2942 buf.push_str(&cur.value);
2943 next_col = cur.column + cur.value.chars().count() as u32;
2944 next_line = cur.line;
2945 self.pos += 1;
2946 }
2947 Ok(buf)
2948 }
2949
2950 fn parse_type_def(&mut self) -> Result<TypeDefinition, ParseError> {
2953 let tok = self.consume(TokenType::Type)?;
2954 let loc = self.loc_of(&tok);
2955 let name = self.consume(TokenType::Identifier)?.value;
2956
2957 let mut node = TypeDefinition {
2958 name,
2959 fields: Vec::new(),
2960 range_constraint: None,
2961 where_clause: None,
2962 compliance: Vec::new(),
2963 loc: loc.clone(),
2964 leading_trivia: Vec::new(),
2965 trailing_trivia: Vec::new(),
2966 };
2967
2968 if self.check(TokenType::LParen) {
2970 self.advance();
2971 let min_val = self.consume_number()?;
2972 self.consume(TokenType::DotDot)?;
2973 let max_val = self.consume_number()?;
2974 self.consume(TokenType::RParen)?;
2975 node.range_constraint = Some(RangeConstraint {
2976 min_value: min_val,
2977 max_value: max_val,
2978 loc: loc.clone(),
2979 });
2980 }
2981
2982 if self.check(TokenType::Where) {
2984 self.advance();
2985 let mut expr_parts = Vec::new();
2986 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
2987 if self.check(TokenType::Eof) {
2988 break;
2989 }
2990 expr_parts.push(self.advance().value.clone());
2991 }
2992 node.where_clause = Some(WhereClause {
2993 expression: expr_parts.join(" "),
2994 loc: loc.clone(),
2995 });
2996 }
2997
2998 if self.check(TokenType::Identifier) && self.current().value == "compliance" {
3001 self.advance();
3002 node.compliance = self.parse_bracketed_identifiers()?;
3003 }
3004
3005 if self.check(TokenType::LBrace) {
3007 self.advance();
3008 while !self.check(TokenType::RBrace) {
3009 let field_name = self.consume(TokenType::Identifier)?;
3010 let field_loc = self.loc_of(&field_name);
3011 self.consume(TokenType::Colon)?;
3012 let type_expr = self.parse_type_expr()?;
3013 node.fields.push(TypeField {
3014 name: field_name.value,
3015 type_expr,
3016 loc: field_loc,
3017 });
3018 if self.check(TokenType::Comma) {
3019 self.advance();
3020 }
3021 }
3022 self.consume(TokenType::RBrace)?;
3023 }
3024
3025 Ok(node)
3026 }
3027
3028 fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
3029 if self.check(TokenType::LBracket) {
3035 let open = self.current().clone();
3036 self.advance();
3037 let inner = self.parse_type_expr()?;
3038 self.consume(TokenType::RBracket)?;
3039 let mut optional = false;
3040 if self.check(TokenType::Question) {
3041 self.advance();
3042 optional = true;
3043 }
3044 return Ok(TypeExpr {
3045 name: "List".to_string(),
3046 generic_param: if inner.generic_param.is_empty() {
3047 inner.name
3048 } else {
3049 format!("{}<{}>", inner.name, inner.generic_param)
3050 },
3051 optional,
3052 loc: self.loc_of(&open),
3053 });
3054 }
3055 let name_tok = self.consume(TokenType::Identifier)?;
3056 let loc = self.loc_of(&name_tok);
3057 let mut generic_param = String::new();
3058 let mut optional = false;
3059
3060 if self.check(TokenType::Lt) {
3061 self.advance();
3062 let inner = self.parse_type_expr()?;
3072 generic_param = if inner.generic_param.is_empty() {
3073 inner.name
3074 } else {
3075 format!("{}<{}>", inner.name, inner.generic_param)
3076 };
3077 self.consume(TokenType::Gt)?;
3078 }
3079 if self.check(TokenType::LBracket) {
3083 self.advance();
3084 if matches!(self.current().ttype, TokenType::Integer | TokenType::Float) {
3085 generic_param = self.advance().value.clone();
3086 } else {
3087 let inner = self.parse_type_expr()?;
3088 generic_param = if inner.generic_param.is_empty() {
3089 inner.name
3090 } else {
3091 format!("{}[{}]", inner.name, inner.generic_param)
3092 };
3093 }
3094 self.consume(TokenType::RBracket)?;
3095 }
3096 if self.check(TokenType::Question) {
3097 self.advance();
3098 optional = true;
3099 }
3100
3101 Ok(TypeExpr {
3102 name: name_tok.value,
3103 generic_param,
3104 optional,
3105 loc,
3106 })
3107 }
3108
3109 fn parse_output_type_string(&mut self) -> Result<String, ParseError> {
3134 let expr = self.parse_type_expr()?;
3135 let mut s = expr.name;
3136 if !expr.generic_param.is_empty() {
3137 s.push('<');
3138 s.push_str(&expr.generic_param);
3139 s.push('>');
3140 }
3141 if expr.optional {
3142 s.push('?');
3143 }
3144 Ok(s)
3145 }
3146
3147 fn parse_flow(&mut self) -> Result<FlowDefinition, ParseError> {
3150 let tok = self.consume(TokenType::Flow)?;
3151 let loc = self.loc_of(&tok);
3152 let name = self.consume(TokenType::Identifier)?.value;
3153
3154 self.consume(TokenType::LParen)?;
3155 let mut parameters = Vec::new();
3156 if !self.check(TokenType::RParen) {
3157 parameters = self.parse_param_list()?;
3158 }
3159 self.consume(TokenType::RParen)?;
3160
3161 let mut return_type = None;
3162 if self.check(TokenType::Arrow) {
3163 self.advance();
3164 return_type = Some(self.parse_type_expr()?);
3165 }
3166
3167 self.consume(TokenType::LBrace)?;
3168 let mut body = Vec::new();
3169 while !self.check(TokenType::RBrace) {
3170 body.push(self.parse_flow_step()?);
3171 }
3172 self.consume(TokenType::RBrace)?;
3173
3174 Ok(FlowDefinition {
3175 name,
3176 parameters,
3177 return_type,
3178 body,
3179 loc,
3180 leading_trivia: Vec::new(),
3181 trailing_trivia: Vec::new(),
3182 })
3183 }
3184
3185 fn parse_effect(&mut self) -> Result<EffectDefinition, ParseError> {
3198 let tok = self.consume(TokenType::Effect)?;
3199 let loc = self.loc_of(&tok);
3200 let name = self.consume_any_ident_or_kw()?.value;
3201 self.consume(TokenType::LBrace)?;
3202
3203 let mut operations: Vec<EffectOperation> = Vec::new();
3204 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3205 let op_tok = self.current().clone();
3206 let op_name = self.consume_any_ident_or_kw()?.value;
3207
3208 self.consume(TokenType::LParen)?;
3209 let parameters = if self.check(TokenType::RParen) {
3210 Vec::new()
3211 } else {
3212 self.parse_param_list()?
3213 };
3214 self.consume(TokenType::RParen)?;
3215
3216 let mut return_type = String::new();
3219 if self.check(TokenType::Arrow) {
3220 self.advance();
3221 return_type = self.parse_type_expr()?.name;
3222 }
3223
3224 if let Some(prior) = operations.iter().find(|o| o.name == op_name) {
3229 return Err(ParseError {
3230 message: format!(
3231 "effect `{name}` declares operation `{op_name}` twice (first at \
3232 line {}); handler dispatch is by operation NAME, so a second \
3233 declaration would silently shadow the first",
3234 prior.loc.line
3235 ),
3236 line: op_tok.line,
3237 column: op_tok.column,
3238 ..Default::default()
3239 });
3240 }
3241
3242 operations.push(EffectOperation {
3243 name: op_name,
3244 parameters,
3245 return_type,
3246 loc: self.loc_of(&op_tok),
3247 });
3248 }
3249 self.consume(TokenType::RBrace)?;
3250
3251 Ok(EffectDefinition {
3252 name,
3253 operations,
3254 loc,
3255 leading_trivia: Vec::new(),
3256 trailing_trivia: Vec::new(),
3257 })
3258 }
3259
3260 fn parse_handle_block(&mut self) -> Result<HandleBlock, ParseError> {
3270 let tok = self.consume(TokenType::Handle)?;
3271 let loc = self.loc_of(&tok);
3272
3273 let mut effect_names = vec![self.consume_any_ident_or_kw()?.value];
3275 while self.check(TokenType::Comma) {
3276 self.advance();
3277 effect_names.push(self.consume_any_ident_or_kw()?.value);
3278 }
3279
3280 self.consume(TokenType::LBrace)?;
3281 let mut clauses: Vec<HandlerClause> = Vec::new();
3282 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3283 let clause_tok = self.current().clone();
3284 let operation_name = self.consume_any_ident_or_kw()?.value;
3285
3286 self.consume(TokenType::LParen)?;
3290 let mut parameter_names = Vec::new();
3291 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3292 parameter_names.push(self.consume_any_ident_or_kw()?.value);
3293 if self.check(TokenType::Comma) {
3294 self.advance();
3295 }
3296 }
3297 self.consume(TokenType::RParen)?;
3298 self.consume(TokenType::Arrow)?;
3299 self.consume(TokenType::LBrace)?;
3300
3301 let mut body = Vec::new();
3302 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3303 body.push(self.parse_flow_step()?);
3304 }
3305 self.consume(TokenType::RBrace)?;
3306
3307 if let Some(prior) = clauses.iter().find(|c| c.operation_name == operation_name) {
3308 return Err(ParseError {
3309 message: format!(
3310 "handler declares clause `{operation_name}` twice (first at line {}); \
3311 dispatch finds a clause by operation NAME and would always run the \
3312 first, leaving the second dead",
3313 prior.loc.line
3314 ),
3315 line: clause_tok.line,
3316 column: clause_tok.column,
3317 ..Default::default()
3318 });
3319 }
3320
3321 clauses.push(HandlerClause {
3322 operation_name,
3323 parameter_names,
3324 body,
3325 loc: self.loc_of(&clause_tok),
3326 });
3327 }
3328 self.consume(TokenType::RBrace)?;
3329
3330 let in_tok = self.current().clone();
3335 if !self.check(TokenType::In) {
3336 return Err(ParseError {
3337 message: format!(
3338 "`handle {}` must be followed by `in {{ … }}` — a handler scope is \
3339 DELIMITED (fase_23 D3). Without the `in` block the frame has no \
3340 extent, so no `perform` could ever reach these clauses (got '{}')",
3341 effect_names.join(", "),
3342 in_tok.value
3343 ),
3344 line: in_tok.line,
3345 column: in_tok.column,
3346 ..Default::default()
3347 });
3348 }
3349 self.advance();
3350 self.consume(TokenType::LBrace)?;
3351 let mut body = Vec::new();
3352 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
3353 body.push(self.parse_flow_step()?);
3354 }
3355 self.consume(TokenType::RBrace)?;
3356
3357 Ok(HandleBlock {
3358 effect_names,
3359 clauses,
3360 body,
3361 loc,
3362 })
3363 }
3364
3365 fn parse_effect_op_ref(
3374 &mut self,
3375 ) -> Result<(Option<String>, String, Vec<String>), ParseError> {
3376 let first = self.consume_any_ident_or_kw()?.value;
3377 let (effect_name, operation_name) = if self.check(TokenType::Dot) {
3378 self.advance();
3379 (Some(first), self.consume_any_ident_or_kw()?.value)
3380 } else {
3381 (None, first)
3382 };
3383
3384 self.consume(TokenType::LParen)?;
3385 let mut arguments = Vec::new();
3386 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
3387 arguments.push(self.parse_subject()?);
3390 if self.check(TokenType::Comma) {
3391 self.advance();
3392 }
3393 }
3394 self.consume(TokenType::RParen)?;
3395 Ok((effect_name, operation_name, arguments))
3396 }
3397
3398 fn parse_perform_step(&mut self) -> Result<PerformStep, ParseError> {
3400 let tok = self.consume(TokenType::Perform)?;
3401 let (effect_name, operation_name, arguments) = self.parse_effect_op_ref()?;
3402 Ok(PerformStep {
3403 effect_name,
3404 operation_name,
3405 arguments,
3406 loc: self.loc_of(&tok),
3407 })
3408 }
3409
3410 fn parse_forward_step(&mut self) -> Result<ForwardStep, ParseError> {
3412 let tok = self.consume(TokenType::Forward)?;
3413 let (effect_name, operation_name, arguments) = self.parse_effect_op_ref()?;
3414 Ok(ForwardStep {
3415 effect_name,
3416 operation_name,
3417 arguments,
3418 loc: self.loc_of(&tok),
3419 })
3420 }
3421
3422 fn parse_discharge_value(&mut self) -> Result<String, ParseError> {
3424 self.consume(TokenType::LParen)?;
3425 let value = if self.check(TokenType::RParen) {
3426 String::new()
3427 } else {
3428 self.parse_subject()?
3429 };
3430 self.consume(TokenType::RParen)?;
3431 Ok(value)
3432 }
3433
3434 fn parse_param_list(&mut self) -> Result<Vec<Parameter>, ParseError> {
3435 let mut params = Vec::new();
3436
3437 let name = self.consume(TokenType::Identifier)?;
3438 let ploc = self.loc_of(&name);
3439 self.consume(TokenType::Colon)?;
3440 let type_expr = self.parse_type_expr()?;
3441 params.push(Parameter {
3442 name: name.value,
3443 type_expr,
3444 loc: ploc,
3445 });
3446
3447 while self.check(TokenType::Comma) {
3448 self.advance();
3449 let name = self.consume(TokenType::Identifier)?;
3450 let ploc = self.loc_of(&name);
3451 self.consume(TokenType::Colon)?;
3452 let type_expr = self.parse_type_expr()?;
3453 params.push(Parameter {
3454 name: name.value,
3455 type_expr,
3456 loc: ploc,
3457 });
3458 }
3459 Ok(params)
3460 }
3461
3462 fn parse_flow_step(&mut self) -> Result<FlowStep, ParseError> {
3465 let tok = self.current().clone();
3466
3467 match tok.ttype {
3468 TokenType::Know | TokenType::Believe | TokenType::Speculate
3474 if self
3475 .tokens
3476 .get(self.pos + 1)
3477 .is_some_and(|t| t.ttype == TokenType::LBrace) =>
3478 {
3479 let block = self.parse_epistemic_block()?;
3480 self.hoisted.push(Declaration::Epistemic(block));
3481 self.parse_flow_step()
3482 }
3483 TokenType::Doubt
3484 if self
3485 .tokens
3486 .get(self.pos + 1)
3487 .is_some_and(|t| t.ttype == TokenType::LBrace) =>
3488 {
3489 let block = self.parse_epistemic_block()?;
3490 self.hoisted.push(Declaration::Epistemic(block));
3491 self.parse_flow_step()
3492 }
3493 TokenType::Step => self.parse_step().map(FlowStep::Step),
3494 TokenType::If => self.parse_if().map(FlowStep::If),
3495 TokenType::For => self.parse_for_in().map(FlowStep::ForIn),
3496 TokenType::Let => self.parse_let().map(FlowStep::Let),
3497 TokenType::Return => self.parse_return().map(FlowStep::Return),
3498 TokenType::Break => self.parse_break().map(FlowStep::Break),
3499 TokenType::Continue => self.parse_continue().map(FlowStep::Continue),
3500 TokenType::Lambda => self.parse_lambda_data_apply().map(FlowStep::LambdaDataApply),
3501
3502 TokenType::Probe => self.parse_flow_step_simple("probe").map(|l| FlowStep::Probe(ProbeStep { target: l.1, fields: Vec::new(), loc: l.0 })),
3504 TokenType::Reason => self.parse_reason_step().map(FlowStep::Reason),
3508 TokenType::Validate => self.parse_flow_step_simple("validate").map(|l| FlowStep::Validate(ValidateStep { target: l.1, rule: String::new(), loc: l.0 })),
3509 TokenType::Refine => self.parse_flow_step_simple("refine").map(|l| FlowStep::Refine(RefineStep { target: l.1, strategy: String::new(), loc: l.0 })),
3510 TokenType::Weave => self.parse_weave_step(),
3511 TokenType::Use => self.parse_use_step(),
3512 TokenType::Remember => self.parse_remember_step(),
3513 TokenType::Recall => self.parse_recall_step(),
3514 TokenType::Par => self.parse_par_block().map(FlowStep::Par),
3515 TokenType::Hibernate => self.parse_hibernate_step(),
3516 TokenType::Deliberate => self.parse_block_step("deliberate").map(|l| FlowStep::Deliberate(DeliberateBlock { loc: l })),
3517 TokenType::Consensus => self.parse_block_step("consensus").map(|l| FlowStep::Consensus(ConsensusBlock { loc: l })),
3518 TokenType::Forge => self.parse_forge_step().map(FlowStep::Forge),
3519 TokenType::Focus => self.parse_focus_step(),
3520 TokenType::Grad => self.parse_grad_step(),
3521 TokenType::Associate => self.parse_associate_step(),
3522 TokenType::Aggregate => self.parse_aggregate_step(),
3523 TokenType::Explore => self.parse_explore_step(),
3524 TokenType::Ingest => self.parse_ingest_step(),
3525 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 })),
3526 TokenType::Stream => self.parse_stream_block().map(FlowStep::Stream),
3532 TokenType::Handle => self.parse_handle_block().map(FlowStep::Handle),
3540 TokenType::Perform => self.parse_perform_step().map(FlowStep::Perform),
3541 TokenType::Resume => {
3542 let tok = self.consume(TokenType::Resume)?;
3543 let value_expr = self.parse_discharge_value()?;
3544 Ok(FlowStep::Resume(ResumeStep {
3545 value_expr,
3546 loc: self.loc_of(&tok),
3547 }))
3548 }
3549 TokenType::Abort => {
3550 let tok = self.consume(TokenType::Abort)?;
3551 let value_expr = self.parse_discharge_value()?;
3552 Ok(FlowStep::Abort(AbortStep {
3553 value_expr,
3554 loc: self.loc_of(&tok),
3555 }))
3556 }
3557 TokenType::Forward => self.parse_forward_step().map(FlowStep::Forward),
3558 TokenType::Navigate => self.parse_navigate_step(),
3559 TokenType::Drill => self.parse_drill_step(),
3560 TokenType::Trail => self.parse_flow_step_simple("trail").map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 })),
3561 TokenType::Corroborate => self.parse_corroborate_step(),
3562 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 })),
3563 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 })),
3564 TokenType::Compute => self.parse_compute_apply().map(FlowStep::ComputeApply),
3569 TokenType::Listen => self.parse_listen_step(),
3570 TokenType::Daemon => self.parse_flow_step_simple("daemon").map(|l| FlowStep::DaemonStep(DaemonStepNode { daemon_ref: l.1, loc: l.0 })),
3571 TokenType::Emit => self.parse_emit_step(),
3573 TokenType::Mint => self.parse_mint_step(),
3575 TokenType::Rotate => self.parse_rotate_step(),
3578 TokenType::Publish => self.parse_publish_step(),
3579 TokenType::Discover => self.parse_discover_step(),
3580 TokenType::Persist => self.parse_persist_step(),
3581 TokenType::Retrieve => self.parse_retrieve_step(),
3582 TokenType::Mutate => self.parse_mutate_step(),
3583 TokenType::Purge => self.parse_store_where_step().map(|(loc, store_name, where_expr)| FlowStep::Purge(PurgeStep { store_name, where_expr, loc })),
3584 TokenType::Transact => self.parse_block_step("transact").map(|l| FlowStep::Transact(TransactBlock { loc: l })),
3585 TokenType::Warden => self.parse_warden().map(FlowStep::Warden),
3587 TokenType::Quant => self.parse_quant().map(FlowStep::Quant),
3589 TokenType::Yield => self.parse_yield().map(FlowStep::Yield),
3591 TokenType::Run => self.parse_run().map(FlowStep::Run),
3595
3596 _ => {
3597 let hint = crate::smart_suggest::suggest_for(
3601 &tok.value,
3602 crate::smart_suggest::FLOW_BODY_KEYWORD_NAMES,
3603 );
3604 let base = format!(
3605 "Unexpected token in flow body: '{}' — expected step, if, for, let, return, ...",
3606 tok.value
3607 );
3608 let message = if hint.is_empty() {
3609 base
3610 } else {
3611 format!("{base}. {hint}")
3612 };
3613 Err(ParseError {
3614 message,
3615 line: tok.line,
3616 column: tok.column,
3617 ..Default::default()
3618 })
3619 }
3620 }
3621 }
3622
3623 fn parse_step(&mut self) -> Result<StepNode, ParseError> {
3626 let tok = self.consume(TokenType::Step)?;
3627 let loc = self.loc_of(&tok);
3628 let name = self.consume(TokenType::Identifier)?.value;
3629
3630 let mut persona_ref = String::new();
3631 if self.check(TokenType::Use) {
3632 self.advance();
3633 persona_ref = self.consume_any_ident_or_kw()?.value;
3634 }
3635
3636 self.consume(TokenType::LBrace)?;
3637
3638 let mut node = StepNode {
3639 name,
3640 persona_ref,
3641 given: String::new(),
3642 ask: String::new(),
3643 output_type: String::new(),
3644 confidence_floor: None,
3645 navigate_ref: String::new(),
3646 apply_ref: String::new(),
3647 requires_context: None,
3648 now_tz: None,
3649 guards: Vec::new(),
3650 pix_ops: Vec::new(),
3651 stream: None,
3652 performs: Vec::new(),
3653 loc,
3654 };
3655
3656 self.parse_step_body_into(&mut node)?;
3657 self.consume(TokenType::RBrace)?;
3658 Ok(node)
3659 }
3660
3661 fn parse_step_body_into(&mut self, node: &mut StepNode) -> Result<(), ParseError> {
3674 while !self.check(TokenType::RBrace) {
3675 let inner = self.current().clone();
3676
3677 match inner.ttype {
3678 TokenType::Given => {
3679 self.advance();
3680 self.consume(TokenType::Colon)?;
3681 node.given = self.parse_expression_string()?;
3682 }
3683 TokenType::Ask => {
3684 self.advance();
3685 self.consume(TokenType::Colon)?;
3686 node.ask = self.consume(TokenType::StringLit)?.value;
3687 }
3688 TokenType::Output => {
3689 self.advance();
3699 self.consume(TokenType::Colon)?;
3700 node.output_type = self.parse_output_type_string()?;
3701 }
3702 TokenType::Navigate
3709 if self
3710 .tokens
3711 .get(self.pos + 1)
3712 .is_some_and(|t| t.ttype != TokenType::Colon) =>
3713 {
3714 let op = self.parse_navigate_step()?;
3715 node.pix_ops.push(op);
3716 }
3717 TokenType::Drill => {
3718 let op = self.parse_drill_step()?;
3719 node.pix_ops.push(op);
3720 }
3721 TokenType::Trail => {
3722 let op = self
3723 .parse_flow_step_simple("trail")
3724 .map(|l| FlowStep::Trail(TrailStep { navigate_ref: l.1, loc: l.0 }))?;
3725 node.pix_ops.push(op);
3726 }
3727 TokenType::Validate => {
3732 let tok = self.current().clone();
3733 self.advance();
3734 let target = self.parse_subject()?;
3736 let mut rule = String::new();
3737 if self.current().value == "against" {
3738 self.advance();
3739 self.consume(TokenType::Colon)?;
3740 rule = self.consume_any_ident_or_kw()?.value.clone();
3741 }
3742 node.pix_ops.push(FlowStep::Validate(ValidateStep {
3743 target,
3744 rule,
3745 loc: Loc { line: tok.line, column: tok.column },
3746 }));
3747 }
3748 TokenType::Navigate => {
3749 self.advance();
3750 self.consume(TokenType::Colon)?;
3751 node.navigate_ref = self.parse_dotted_identifier()?;
3752 }
3753 TokenType::Identifier if inner.value == "confidence_floor" => {
3754 self.advance();
3755 self.consume(TokenType::Colon)?;
3756 node.confidence_floor = Some(self.consume_number()?);
3757 }
3758 TokenType::Identifier if inner.value == "apply" => {
3759 self.advance();
3760 self.consume(TokenType::Colon)?;
3761 node.apply_ref = self.consume_any_ident_or_kw()?.value;
3762 }
3763 TokenType::Identifier if inner.value == "requires_context" => {
3771 self.advance();
3772 self.consume(TokenType::Colon)?;
3773 let num = self.current().clone();
3774 let bad = |tok: &crate::tokens::Token| ParseError {
3775 message: format!(
3776 "`requires_context:` must be a positive integer token count \
3777 (got '{}')",
3778 tok.value
3779 ),
3780 line: tok.line,
3781 column: tok.column,
3782 ..Default::default()
3783 };
3784 if num.ttype != TokenType::Integer {
3785 return Err(bad(&num));
3786 }
3787 let value = num.value.parse::<u32>().map_err(|_| bad(&num))?;
3788 self.advance();
3789 node.requires_context = Some(value);
3790 }
3791 TokenType::Identifier if inner.value == "now" => {
3796 self.advance();
3797 self.consume(TokenType::Colon)?;
3798 let tz = self.current().clone();
3799 if tz.ttype != TokenType::StringLit {
3800 return Err(ParseError {
3801 message: format!(
3802 "`now:` must be an IANA timezone string literal like \
3803 \"America/Bogota\" or \"UTC\" (got '{}')",
3804 tz.value
3805 ),
3806 line: tz.line,
3807 column: tz.column,
3808 ..Default::default()
3809 });
3810 }
3811 self.advance();
3812 node.now_tz = Some(tz.value);
3813 }
3814 TokenType::Use => {
3824 let tool = self
3825 .tokens
3826 .get(self.pos + 1)
3827 .map(|t| t.value.as_str())
3828 .filter(|v| !v.is_empty())
3829 .unwrap_or("<Tool>");
3830 return Err(ParseError {
3831 message: format!(
3832 "`use` is not valid inside a `step {{ }}` body — the tool dispatch \
3833 would be silently dropped. To invoke a tool, either write the \
3834 flow-level step `use {tool} on <arg>` (outside this block), or bind \
3835 it inside this step with `apply: {tool}`. To attach a persona, put \
3836 it in the step header: `step <name> use <Persona> {{ … }}`."
3837 ),
3838 line: inner.line,
3839 column: inner.column,
3840 ..Default::default()
3841 });
3842 }
3843 TokenType::Mandate => {
3854 let g = self.parse_step_guard("mandate")?;
3855 node.guards.push(g);
3856 }
3857 TokenType::Shield => {
3858 let g = self.parse_step_guard("shield")?;
3859 node.guards.push(g);
3860 }
3861 TokenType::Ots => {
3862 let g = self.parse_step_guard("ots")?;
3863 node.guards.push(g);
3864 }
3865 TokenType::Lambda => {
3872 let g = self.parse_step_guard("lambda")?;
3873 node.guards.push(g);
3874 }
3875 TokenType::Probe
3883 if self
3884 .tokens
3885 .get(self.pos + 1)
3886 .is_some_and(|t| t.ttype != TokenType::Colon) =>
3887 {
3888 let tok = self.current().clone();
3889 self.advance();
3890 let target = self.parse_subject()?;
3893 let mut fields = Vec::new();
3894 if self.check(TokenType::For) {
3895 self.advance();
3896 self.consume(TokenType::LBracket)?;
3897 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
3898 fields.push(self.consume_any_ident_or_kw()?.value.clone());
3899 if self.check(TokenType::Comma) {
3900 self.advance();
3901 }
3902 }
3903 self.consume(TokenType::RBracket)?;
3904 }
3905 node.pix_ops.push(FlowStep::Probe(ProbeStep {
3906 target,
3907 fields,
3908 loc: Loc { line: tok.line, column: tok.column },
3909 }));
3910 }
3911 TokenType::Identifier if inner.value == "use_tool" => {
3918 let tok = self.current().clone();
3919 self.advance();
3920 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
3921 let args = if self.current().value == "with" {
3922 self.advance();
3923 let mut named: Vec<(String, String, String)> = Vec::new();
3924 loop {
3925 let k = self.consume_any_ident_or_kw()?.value.clone();
3926 self.consume(TokenType::Colon)?;
3927 let kind = if self.check(TokenType::StringLit) {
3931 "literal"
3932 } else {
3933 "reference"
3934 };
3935 let v = self.parse_expression_string()?;
3936 named.push((k, v, kind.to_string()));
3937 if self.check(TokenType::Comma) {
3938 self.advance();
3939 } else {
3940 break;
3941 }
3942 }
3943 UseArgs::Named(named)
3944 } else if self.current().value == "on" {
3945 self.advance();
3946 UseArgs::LegacyPositional(
3947 self.consume_any_ident_or_kw()?.value.clone(),
3948 )
3949 } else {
3950 UseArgs::LegacyPositional(String::new())
3951 };
3952 node.pix_ops.push(FlowStep::UseTool(UseToolStep {
3953 tool_name,
3954 args,
3955 loc: Loc { line: tok.line, column: tok.column },
3956 }));
3957 }
3958 TokenType::Par => {
3960 let block = self.parse_par_block()?;
3961 node.pix_ops.push(FlowStep::Par(block));
3962 }
3963 TokenType::Reason
3985 if self
3986 .tokens
3987 .get(self.pos + 1)
3988 .is_some_and(|t| t.ttype == TokenType::Colon) =>
3989 {
3990 let tok = self.current().clone();
3991 self.advance();
3992 self.consume(TokenType::Colon)?;
3993 let mut r = ReasonStep {
3994 strategy: String::new(),
3995 target: String::new(),
3996 given: String::new(),
3997 ask: String::new(),
3998 depth: None,
3999 loc: self.loc_of(&tok),
4000 };
4001 if self.check(TokenType::StringLit) {
4002 r.ask = self.consume(TokenType::StringLit)?.value;
4003 } else {
4004 r.target = self.parse_dotted_identifier()?;
4005 }
4006 node.pix_ops.push(FlowStep::Reason(r));
4007 }
4008 TokenType::Reason => {
4009 let r = self.parse_reason_step()?;
4010 node.pix_ops.push(FlowStep::Reason(r));
4011 }
4012 TokenType::Weave => {
4021 let w = self.parse_weave_step()?;
4022 node.pix_ops.push(w);
4023 }
4024 TokenType::Identifier
4034 if self
4035 .tokens
4036 .get(self.pos + 1)
4037 .is_some_and(|t| t.ttype == TokenType::LParen) =>
4038 {
4039 let tok = self.current().clone();
4040 let agent_name = self.consume_any_ident_or_kw()?.value.clone();
4041 self.consume(TokenType::LParen)?;
4042 let mut arguments = Vec::new();
4043 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
4044 arguments.push(self.parse_subject()?);
4045 if self.check(TokenType::Comma) {
4046 self.advance();
4047 }
4048 }
4049 self.consume(TokenType::RParen)?;
4050 node.pix_ops.push(FlowStep::AgentCall(AgentCallStep {
4051 agent_name,
4052 arguments,
4053 loc: self.loc_of(&tok),
4054 }));
4055 }
4056 TokenType::Retrieve => {
4066 let r = self.parse_retrieve_step()?;
4067 node.pix_ops.push(r);
4068 }
4069 TokenType::Stream => {
4087 let sb = self.parse_stream_block()?;
4088 if node.stream.is_some() {
4089 return Err(ParseError {
4090 message:
4091 "step declares two `stream` blocks; a step has one output stream, \
4092 and composing two has no defined meaning (which one is the \
4093 step's output?). Refused rather than silently keeping the last."
4094 .to_string(),
4095 line: inner.line,
4096 column: inner.column,
4097 ..Default::default()
4098 });
4099 }
4100 node.stream = Some(Box::new(sb));
4101 }
4102 TokenType::Perform => {
4119 let p = self.parse_perform_step()?;
4120 node.performs.push(p);
4121 }
4122 TokenType::Probe => {
4126 self.skip_flow_step_structural()?;
4127 }
4128 _ => {
4129 return Err(ParseError {
4130 message: format!(
4131 "Unexpected token in step body: '{}' — expected given, ask, \
4132 probe, reason, weave, stream, perform, output, confidence_floor, \
4133 navigate, apply, requires_context, now",
4134 inner.value
4135 ),
4136 line: inner.line,
4137 column: inner.column,
4138 ..Default::default()
4139 });
4140 }
4141 }
4142 }
4143 Ok(())
4144 }
4145
4146 fn skip_flow_step_structural(&mut self) -> Result<(), ParseError> {
4148 self.advance();
4150 while !self.check(TokenType::LBrace)
4152 && !self.check(TokenType::RBrace)
4153 && !self.check(TokenType::Eof)
4154 {
4155 let tt = &self.current().ttype;
4157 if matches!(
4158 tt,
4159 TokenType::Step
4160 | TokenType::Given
4161 | TokenType::Ask
4162 | TokenType::Output
4163 | TokenType::Navigate
4164 | TokenType::Use
4165 | TokenType::Probe
4166 | TokenType::Reason
4167 | TokenType::Weave
4168 | TokenType::Stream
4169 | TokenType::If
4170 | TokenType::For
4171 | TokenType::Let
4172 | TokenType::Return
4173 ) {
4174 return Ok(());
4175 }
4176 self.advance();
4177 }
4178 if self.check(TokenType::LBrace) {
4180 self.skip_braced_block()?;
4181 }
4182 Ok(())
4183 }
4184
4185 fn parse_intent(&mut self) -> Result<IntentNode, ParseError> {
4188 let tok = self.consume(TokenType::Intent)?;
4189 let loc = self.loc_of(&tok);
4190 let name = self.consume(TokenType::Identifier)?.value;
4191 self.consume(TokenType::LBrace)?;
4192
4193 let mut node = IntentNode {
4194 name,
4195 given: String::new(),
4196 ask: String::new(),
4197 output_type: None,
4198 confidence_floor: None,
4199 loc,
4200 leading_trivia: Vec::new(),
4201 trailing_trivia: Vec::new(),
4202 };
4203
4204 while !self.check(TokenType::RBrace) {
4205 let field_name = self.current().value.clone();
4206 self.advance();
4207 self.consume(TokenType::Colon)?;
4208
4209 match field_name.as_str() {
4210 "given" => node.given = self.consume(TokenType::Identifier)?.value,
4211 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
4212 "output" => node.output_type = Some(self.parse_type_expr()?),
4213 "confidence_floor" => node.confidence_floor = Some(self.consume_number()?),
4214 _ => self.skip_value(),
4215 }
4216 }
4217 self.consume(TokenType::RBrace)?;
4218 Ok(node)
4219 }
4220
4221 fn parse_run(&mut self) -> Result<RunStatement, ParseError> {
4224 let tok = self.consume(TokenType::Run)?;
4225 let loc = self.loc_of(&tok);
4226 let flow_name = self.consume(TokenType::Identifier)?.value;
4227
4228 self.consume(TokenType::LParen)?;
4229 let mut arguments = Vec::new();
4230 if !self.check(TokenType::RParen) {
4231 arguments = self.parse_argument_list()?;
4232 }
4233 self.consume(TokenType::RParen)?;
4234
4235 let mut node = RunStatement {
4236 flow_name,
4237 arguments,
4238 persona: String::new(),
4239 context: String::new(),
4240 anchors: Vec::new(),
4241 on_failure: String::new(),
4242 on_failure_params: Vec::new(),
4243 output_to: String::new(),
4244 effort: String::new(),
4245 loc,
4246 leading_trivia: Vec::new(),
4247 trailing_trivia: Vec::new(),
4248 };
4249
4250 while self.check_run_modifier() {
4251 let mod_tok = self.current().clone();
4252 if mod_tok.value == "with" && mod_tok.ttype != TokenType::As {
4254 self.advance();
4255 node.persona = self.consume(TokenType::Identifier)?.value;
4256 continue;
4257 }
4258 match mod_tok.ttype {
4259 TokenType::As => {
4260 self.advance();
4261 node.persona = self.consume(TokenType::Identifier)?.value;
4262 }
4263 TokenType::Within => {
4264 self.advance();
4265 node.context = self.consume(TokenType::Identifier)?.value;
4266 }
4267 TokenType::ConstrainedBy => {
4268 self.advance();
4269 node.anchors = self.parse_bracketed_identifiers()?;
4270 }
4271 TokenType::OnFailure => {
4272 self.advance();
4273 self.consume(TokenType::Colon)?;
4274 node.on_failure = self.consume_any_ident_or_kw()?.value;
4275 if self.check(TokenType::LParen) {
4277 self.advance();
4278 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
4279 let key = self.consume_any_ident_or_kw()?.value;
4280 self.consume(TokenType::Colon)?;
4281 let val = self.consume_any_ident_or_kw()?.value;
4282 node.on_failure_params.push((key, val));
4283 if self.check(TokenType::Comma) {
4284 self.advance();
4285 }
4286 }
4287 if self.check(TokenType::RParen) {
4288 self.advance();
4289 }
4290 }
4291 }
4292 TokenType::OutputTo => {
4293 self.advance();
4294 self.consume(TokenType::Colon)?;
4295 node.output_to = self.consume(TokenType::StringLit)?.value;
4296 }
4297 TokenType::Effort => {
4298 self.advance();
4299 self.consume(TokenType::Colon)?;
4300 node.effort = self.consume_any_ident_or_kw()?.value;
4301 }
4302 _ => break,
4303 }
4304 }
4305
4306 Ok(node)
4307 }
4308
4309 fn parse_epistemic_block(&mut self) -> Result<EpistemicBlock, ParseError> {
4312 let tok = self.current().clone();
4313 let mode = match tok.ttype {
4314 TokenType::Know => "know",
4315 TokenType::Believe => "believe",
4316 TokenType::Speculate => "speculate",
4317 TokenType::Doubt => "doubt",
4318 _ => unreachable!(),
4319 };
4320 self.advance();
4321 let loc = self.loc_of(&tok);
4322
4323 self.consume(TokenType::LBrace)?;
4324 let mut body = Vec::new();
4325 while !self.check(TokenType::RBrace) {
4326 body.push(self.parse_declaration()?);
4327 }
4328 self.consume(TokenType::RBrace)?;
4329
4330 Ok(EpistemicBlock {
4331 mode: mode.to_string(),
4332 body,
4333 loc,
4334 leading_trivia: Vec::new(),
4335 trailing_trivia: Vec::new(),
4336 })
4337 }
4338
4339 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
4347 self.parse_expr_bp(0)
4348 }
4349
4350 fn parse_expr_bp(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
4351 let mut lhs = match self.current().ttype {
4354 TokenType::Minus => {
4355 self.advance();
4356 Expr::Unary(UnOp::Neg, Box::new(self.parse_expr_bp(6)?))
4357 }
4358 TokenType::Not => {
4359 self.advance();
4360 Expr::Unary(UnOp::Not, Box::new(self.parse_expr_bp(6)?))
4361 }
4362 _ => self.parse_postfix()?,
4363 };
4364 while let Some((op, lbp)) = Self::binop_of(self.current().ttype.clone()) {
4366 if lbp < min_bp {
4367 break;
4368 }
4369 self.advance();
4370 let rhs = self.parse_expr_bp(lbp + 1)?;
4371 lhs = Expr::Binary(op, Box::new(lhs), Box::new(rhs));
4372 }
4373 Ok(lhs)
4374 }
4375
4376 fn binop_of(t: TokenType) -> Option<(BinOp, u8)> {
4379 Some(match t {
4380 TokenType::Or => (BinOp::Or, 1),
4381 TokenType::And => (BinOp::And, 2),
4382 TokenType::Eq => (BinOp::Eq, 3),
4383 TokenType::Neq => (BinOp::Ne, 3),
4384 TokenType::Lt => (BinOp::Lt, 3),
4385 TokenType::Lte => (BinOp::Le, 3),
4386 TokenType::Gt => (BinOp::Gt, 3),
4387 TokenType::Gte => (BinOp::Ge, 3),
4388 TokenType::Plus => (BinOp::Add, 4),
4389 TokenType::Minus => (BinOp::Sub, 4),
4390 TokenType::Star => (BinOp::Mul, 5),
4391 TokenType::Slash => (BinOp::Div, 5),
4392 TokenType::Percent => (BinOp::Mod, 5),
4393 _ => return None,
4394 })
4395 }
4396
4397 fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
4403 let mut expr = self.parse_expr_atom()?;
4404 loop {
4405 if self.check(TokenType::Dot) {
4406 self.advance();
4407 let name = self.consume_any_ident_or_kw()?.value;
4408 if let Some(builtin) = Builtin::from_name(&name) {
4409 let mut args = vec![expr];
4410 if self.check(TokenType::LParen) {
4411 self.advance();
4412 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
4413 args.push(self.parse_expr_bp(0)?);
4414 if self.check(TokenType::Comma) {
4415 self.advance();
4416 } else {
4417 break;
4418 }
4419 }
4420 self.consume(TokenType::RParen)?;
4421 }
4422 expr = Expr::Call(builtin, args);
4423 } else {
4424 expr = match expr {
4428 Expr::Ref(p) => Expr::Ref(format!("{p}.{name}")),
4429 other => Expr::Field(Box::new(other), name),
4430 };
4431 }
4432 } else if self.check(TokenType::LBracket) {
4433 self.advance();
4435 let index = self.parse_expr_bp(0)?;
4436 self.consume(TokenType::RBracket)?;
4437 expr = Expr::Index(Box::new(expr), Box::new(index));
4438 } else {
4439 break;
4440 }
4441 }
4442 Ok(expr)
4443 }
4444
4445 fn parse_expr_atom(&mut self) -> Result<Expr, ParseError> {
4446 let tok = self.current().clone();
4447 match tok.ttype {
4448 TokenType::Integer => {
4449 self.advance();
4450 let lit = tok
4451 .value
4452 .parse::<i64>()
4453 .map(ExprLit::Int)
4454 .or_else(|_| tok.value.parse::<f64>().map(ExprLit::Float))
4455 .map_err(|_| ParseError {
4456 message: format!("invalid integer literal '{}'", tok.value),
4457 line: tok.line,
4458 column: tok.column,
4459 ..Default::default()
4460 })?;
4461 Ok(Expr::Lit(lit))
4462 }
4463 TokenType::Float => {
4464 self.advance();
4465 let f = tok.value.parse::<f64>().map_err(|_| ParseError {
4466 message: format!("invalid float literal '{}'", tok.value),
4467 line: tok.line,
4468 column: tok.column,
4469 ..Default::default()
4470 })?;
4471 Ok(Expr::Lit(ExprLit::Float(f)))
4472 }
4473 TokenType::Bool => {
4474 self.advance();
4475 Ok(Expr::Lit(ExprLit::Bool(tok.value == "true")))
4476 }
4477 TokenType::StringLit => {
4478 self.advance();
4479 Ok(Expr::Lit(ExprLit::Str(tok.value)))
4480 }
4481 TokenType::LParen => {
4482 self.advance();
4483 let inner = self.parse_expr_bp(0)?;
4484 self.consume(TokenType::RParen)?;
4485 Ok(inner)
4486 }
4487 _ => {
4488 Ok(Expr::Ref(self.consume_any_ident_or_kw()?.value))
4492 }
4493 }
4494 }
4495
4496 fn expr_lit_surface(lit: &ExprLit) -> String {
4501 match lit {
4502 ExprLit::Int(i) => i.to_string(),
4503 ExprLit::Float(f) => f.to_string(),
4504 ExprLit::Bool(b) => b.to_string(),
4505 ExprLit::Str(s) => s.clone(),
4506 }
4507 }
4508
4509 fn expr_leaf_surface(expr: &Expr) -> Option<String> {
4510 match expr {
4511 Expr::Ref(p) => Some(p.clone()),
4512 Expr::Lit(l) => Some(Self::expr_lit_surface(l)),
4513 _ => None,
4514 }
4515 }
4516
4517 fn expr_legacy_leaf(expr: &Expr) -> Option<(String, String, String)> {
4521 match expr {
4522 Expr::Ref(p) => Some((p.clone(), String::new(), String::new())),
4523 Expr::Binary(op, l, r) => {
4524 let op_s = match op {
4525 BinOp::Eq => "==",
4526 BinOp::Ne => "!=",
4527 BinOp::Lt => "<",
4528 BinOp::Le => "<=",
4529 BinOp::Gt => ">",
4530 BinOp::Ge => ">=",
4531 _ => return None,
4532 };
4533 let lhs = match &**l {
4534 Expr::Ref(p) => p.clone(),
4535 _ => return None,
4536 };
4537 let rhs = Self::expr_leaf_surface(r)?;
4538 Some((lhs, op_s.to_string(), rhs))
4539 }
4540 _ => None,
4541 }
4542 }
4543
4544 fn collect_or_leaves(expr: &Expr, out: &mut Vec<(String, String, String)>) -> bool {
4547 match expr {
4548 Expr::Binary(BinOp::Or, l, r) => {
4549 Self::collect_or_leaves(l, out) && Self::collect_or_leaves(r, out)
4550 }
4551 _ => match Self::expr_legacy_leaf(expr) {
4552 Some(t) => {
4553 out.push(t);
4554 true
4555 }
4556 None => false,
4557 },
4558 }
4559 }
4560
4561 #[allow(clippy::type_complexity)]
4567 fn cond_as_legacy(
4568 expr: &Expr,
4569 ) -> Option<(String, String, String, Vec<(String, String, String)>, String)> {
4570 let mut leaves = Vec::new();
4571 if !Self::collect_or_leaves(expr, &mut leaves) || leaves.is_empty() {
4572 return None;
4573 }
4574 let (c0, o0, v0) = leaves[0].clone();
4575 let rest = leaves[1..].to_vec();
4576 let conjunctor = if rest.is_empty() {
4577 String::new()
4578 } else {
4579 "or".to_string()
4580 };
4581 Some((c0, o0, v0, rest, conjunctor))
4582 }
4583
4584 fn parse_if(&mut self) -> Result<ConditionalNode, ParseError> {
4585 let tok = self.consume(TokenType::If)?;
4586 let loc = self.loc_of(&tok);
4587
4588 let expr = self.parse_expr()?;
4593 let (condition, comparison_op, comparison_value, conditions, conjunctor, cond) =
4594 match Self::cond_as_legacy(&expr) {
4595 Some((c, o, v, more, conj)) => (c, o, v, more, conj, None),
4596 None => (
4597 String::new(),
4598 String::new(),
4599 String::new(),
4600 Vec::new(),
4601 String::new(),
4602 Some(expr),
4603 ),
4604 };
4605
4606 let mut then_body = Vec::new();
4607 let mut else_body = Vec::new();
4608
4609 if self.check(TokenType::Arrow) {
4611 self.advance();
4612 then_body.push(self.parse_flow_step()?);
4613 } else if self.check(TokenType::LBrace) {
4614 self.advance();
4615 while !self.check(TokenType::RBrace) {
4616 then_body.push(self.parse_flow_step()?);
4617 }
4618 self.consume(TokenType::RBrace)?;
4619 }
4620
4621 if self.check(TokenType::Else) {
4623 self.advance();
4624 if self.check(TokenType::Arrow) {
4625 self.advance();
4626 else_body.push(self.parse_flow_step()?);
4627 } else if self.check(TokenType::LBrace) {
4628 self.advance();
4629 while !self.check(TokenType::RBrace) {
4630 else_body.push(self.parse_flow_step()?);
4631 }
4632 self.consume(TokenType::RBrace)?;
4633 }
4634 }
4635
4636 Ok(ConditionalNode {
4637 condition,
4638 comparison_op,
4639 comparison_value,
4640 then_body,
4641 else_body,
4642 conditions,
4643 conjunctor,
4644 cond,
4645 loc,
4646 })
4647 }
4648
4649 fn parse_for_in(&mut self) -> Result<ForInStatement, ParseError> {
4652 let tok = self.consume(TokenType::For)?;
4653 let loc = self.loc_of(&tok);
4654 let variable = self.consume(TokenType::Identifier)?.value;
4655 self.consume(TokenType::In)?;
4656 let iterable = self.parse_dotted_identifier()?;
4657
4658 self.consume(TokenType::LBrace)?;
4659 self.loop_depth += 1;
4666 let body_result = (|| -> Result<Vec<FlowStep>, ParseError> {
4667 let mut body = Vec::new();
4668 while !self.check(TokenType::RBrace) {
4669 body.push(self.parse_flow_step()?);
4670 }
4671 Ok(body)
4672 })();
4673 self.loop_depth -= 1;
4674 let body = body_result?;
4675 self.consume(TokenType::RBrace)?;
4676
4677 Ok(ForInStatement {
4678 variable,
4679 iterable,
4680 body,
4681 loc,
4682 })
4683 }
4684
4685 fn parse_break(&mut self) -> Result<BreakStatement, ParseError> {
4688 let tok = self.consume(TokenType::Break)?;
4689 let loc = self.loc_of(&tok);
4690 if self.loop_depth == 0 {
4691 return Err(ParseError {
4692 message: "'break' outside of a for-in loop body".to_string(),
4693 line: tok.line,
4694 column: tok.column,
4695 ..Default::default()
4696 });
4697 }
4698 Ok(BreakStatement { loc })
4699 }
4700
4701 fn parse_continue(&mut self) -> Result<ContinueStatement, ParseError> {
4704 let tok = self.consume(TokenType::Continue)?;
4705 let loc = self.loc_of(&tok);
4706 if self.loop_depth == 0 {
4707 return Err(ParseError {
4708 message: "'continue' outside of a for-in loop body".to_string(),
4709 line: tok.line,
4710 column: tok.column,
4711 ..Default::default()
4712 });
4713 }
4714 Ok(ContinueStatement { loc })
4715 }
4716
4717 fn parse_let(&mut self) -> Result<LetStatement, ParseError> {
4720 let tok = self.consume(TokenType::Let)?;
4721 let loc = self.loc_of(&tok);
4722
4723 let name = self.consume_any_ident_or_kw()?.value;
4725 let type_annotation = if self.check(TokenType::Colon) {
4727 self.advance();
4728 Some(self.parse_type_expr()?)
4729 } else {
4730 None
4731 };
4732 self.consume(TokenType::Assign)?;
4733 self.last_let_value_kind = "literal".to_string();
4736 let (value, value_ast) = self.parse_let_value_expr_with_ast()?;
4737
4738 Ok(LetStatement {
4739 identifier: name,
4740 value_expr: value,
4741 value_kind: self.last_let_value_kind.clone(),
4742 type_annotation,
4743 value_ast,
4744 loc,
4745 leading_trivia: Vec::new(),
4746 trailing_trivia: Vec::new(),
4747 })
4748 }
4749
4750 fn parse_let_value_expr(&mut self) -> Result<String, ParseError> {
4751 let atom = self.parse_let_atom()?;
4752
4753 if matches!(
4755 self.current().ttype,
4756 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
4757 ) {
4758 let mut parts = vec![atom];
4759 while matches!(
4760 self.current().ttype,
4761 TokenType::Plus | TokenType::Minus | TokenType::Star | TokenType::Slash
4762 ) {
4763 parts.push(self.advance().value.clone());
4764 parts.push(self.parse_let_atom()?);
4765 }
4766 self.last_let_value_kind = "expression".to_string();
4767 return Ok(parts.join(" "));
4768 }
4769 Ok(atom)
4770 }
4771
4772 fn parse_let_value_expr_with_ast(&mut self) -> Result<(String, Option<Expr>), ParseError> {
4782 if self.check(TokenType::LBracket) {
4783 self.last_let_value_kind = "literal".to_string();
4784 return Ok((self.parse_let_list_literal()?, None));
4785 }
4786 let expr = self.parse_expr()?;
4787 Ok(match expr {
4788 Expr::Lit(lit) => {
4789 self.last_let_value_kind = "literal".to_string();
4790 (Self::expr_lit_surface(&lit), None)
4791 }
4792 Expr::Ref(p) => {
4793 self.last_let_value_kind = "reference".to_string();
4794 (p, None)
4795 }
4796 other => {
4797 self.last_let_value_kind = "expression".to_string();
4798 (Self::render_expr(&other), Some(other))
4799 }
4800 })
4801 }
4802
4803 fn render_expr(e: &Expr) -> String {
4806 match e {
4807 Expr::Lit(l) => Self::expr_lit_surface(l),
4808 Expr::Ref(p) => p.clone(),
4809 Expr::Let { name, value, body } => format!(
4813 "let {name} = {} in {}",
4814 Self::render_expr(value),
4815 Self::render_expr(body)
4816 ),
4817 Expr::Unary(UnOp::Neg, x) => format!("-{}", Self::render_expr(x)),
4818 Expr::Unary(UnOp::Not, x) => format!("not {}", Self::render_expr(x)),
4819 Expr::Binary(op, l, r) => {
4820 let sym = match op {
4821 BinOp::Add => "+",
4822 BinOp::Sub => "-",
4823 BinOp::Mul => "*",
4824 BinOp::Div => "/",
4825 BinOp::Mod => "%",
4826 BinOp::Eq => "==",
4827 BinOp::Ne => "!=",
4828 BinOp::Lt => "<",
4829 BinOp::Le => "<=",
4830 BinOp::Gt => ">",
4831 BinOp::Ge => ">=",
4832 BinOp::And => "and",
4833 BinOp::Or => "or",
4834 };
4835 format!("({} {sym} {})", Self::render_expr(l), Self::render_expr(r))
4836 }
4837 Expr::Call(b, args) => {
4838 let recv = args.first().map(Self::render_expr).unwrap_or_default();
4839 let rest: Vec<String> = args.iter().skip(1).map(Self::render_expr).collect();
4840 if rest.is_empty() {
4841 format!("{recv}.{}", b.surface())
4842 } else {
4843 format!("{recv}.{}({})", b.surface(), rest.join(", "))
4844 }
4845 }
4846 Expr::Field(b, f) => format!("{}.{f}", Self::render_expr(b)),
4847 Expr::Index(b, i) => format!("{}[{}]", Self::render_expr(b), Self::render_expr(i)),
4848 }
4849 }
4850
4851 fn parse_let_atom(&mut self) -> Result<String, ParseError> {
4852 let tok = self.current().clone();
4853
4854 match tok.ttype {
4855 TokenType::StringLit => {
4856 self.last_let_value_kind = "literal".to_string();
4857 self.advance();
4858 Ok(tok.value)
4859 }
4860 TokenType::Integer | TokenType::Float => {
4861 self.last_let_value_kind = "literal".to_string();
4862 self.advance();
4863 Ok(tok.value)
4864 }
4865 TokenType::Bool => {
4866 self.last_let_value_kind = "literal".to_string();
4867 self.advance();
4868 Ok(tok.value)
4869 }
4870 TokenType::Identifier => {
4871 self.last_let_value_kind = "reference".to_string();
4872 self.parse_dotted_identifier()
4873 }
4874 TokenType::LBracket => {
4875 self.last_let_value_kind = "literal".to_string();
4876 self.parse_let_list_literal()
4877 }
4878 _ => {
4879 if self.pos + 1 < self.tokens.len()
4881 && self.tokens[self.pos + 1].ttype == TokenType::Dot
4882 {
4883 self.last_let_value_kind = "reference".to_string();
4884 return self.parse_dotted_identifier();
4885 }
4886 Err(ParseError {
4887 message: format!(
4888 "Expected value expression, found {:?}('{}')",
4889 tok.ttype, tok.value
4890 ),
4891 line: tok.line,
4892 column: tok.column,
4893 ..Default::default()
4894 })
4895 }
4896 }
4897 }
4898
4899 fn parse_let_list_literal(&mut self) -> Result<String, ParseError> {
4900 self.consume(TokenType::LBracket)?;
4901 let mut items = Vec::new();
4902 if !self.check(TokenType::RBracket) {
4903 items.push(self.parse_let_value_expr()?);
4904 while self.check(TokenType::Comma) {
4905 self.advance();
4906 if self.check(TokenType::RBracket) {
4907 break; }
4909 items.push(self.parse_let_value_expr()?);
4910 }
4911 }
4912 self.consume(TokenType::RBracket)?;
4913 Ok(format!("[{}]", items.join(", ")))
4914 }
4915
4916 fn parse_return(&mut self) -> Result<ReturnStatement, ParseError> {
4919 let tok = self.consume(TokenType::Return)?;
4920 let loc = self.loc_of(&tok);
4921 let value = self.parse_let_value_expr()?;
4922 Ok(ReturnStatement {
4923 value_expr: value,
4924 loc,
4925 })
4926 }
4927
4928 fn parse_flow_step_simple(&mut self, _kw: &str) -> Result<(Loc, String), ParseError> {
4932 let tok = self.current().clone();
4933 self.advance(); let target = if self.at_declaration_start()
4935 || self.check(TokenType::RBrace)
4936 || self.check(TokenType::Eof)
4937 {
4938 String::new()
4939 } else {
4940 self.consume_any_ident_or_kw()?.value.clone()
4941 };
4942 if self.check(TokenType::LBrace) {
4944 self.skip_braced_block()?;
4945 }
4946 Ok((
4947 Loc {
4948 line: tok.line,
4949 column: tok.column,
4950 },
4951 target,
4952 ))
4953 }
4954
4955 fn parse_stream_block(&mut self) -> Result<StreamBlock, ParseError> {
4966 let tok = self.current().clone();
4967 let loc = self.loc_of(&tok);
4968 self.advance(); let mut chunk_type = String::new();
4976 if self.check(TokenType::Lt) {
4977 self.advance();
4978 let inner = self.parse_type_expr()?;
4979 chunk_type = if inner.generic_param.is_empty() {
4980 inner.name
4981 } else {
4982 format!("{}<{}>", inner.name, inner.generic_param)
4983 };
4984 self.consume(TokenType::Gt)?;
4985 }
4986
4987 while !self.check(TokenType::LBrace)
4991 && !self.check(TokenType::RBrace)
4992 && !self.check(TokenType::Eof)
4993 && !self.at_declaration_start()
4994 {
4995 self.advance();
4996 }
4997
4998 let mut block = StreamBlock {
4999 chunk_type,
5000 on_chunk: None,
5001 on_complete: None,
5002 on_error: None,
5003 body: Vec::new(),
5004 loc,
5005 };
5006
5007 if self.check(TokenType::LBrace) {
5008 self.advance();
5009 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5010 let name = self.current().value.clone();
5016 let is_arm = matches!(name.as_str(), "on_chunk" | "on_complete" | "on_error")
5017 && self
5018 .tokens
5019 .get(self.pos + 1)
5020 .is_some_and(|t| t.ttype == TokenType::Colon);
5021 if is_arm {
5022 let arm_tok = self.current().clone();
5023 self.advance(); self.advance(); let arm = self.parse_stream_handler_arm(&name, &arm_tok)?;
5026 let slot = match name.as_str() {
5027 "on_chunk" => &mut block.on_chunk,
5028 "on_complete" => &mut block.on_complete,
5029 _ => &mut block.on_error,
5030 };
5031 if slot.is_some() {
5032 return Err(ParseError {
5033 message: format!(
5034 "`{name}` is declared twice in this `stream` block. Two handlers \
5035 for one edge have no defined composition (whose output is the \
5036 stream's?), so the duplicate is refused rather than silently \
5037 overwriting the first."
5038 ),
5039 line: arm_tok.line,
5040 column: arm_tok.column,
5041 ..Default::default()
5042 });
5043 }
5044 *slot = Some(arm);
5045 continue;
5046 }
5047
5048 let next_two_are_block = self
5055 .tokens
5056 .get(self.pos + 1)
5057 .is_some_and(|t| t.ttype == TokenType::Colon)
5058 && self
5059 .tokens
5060 .get(self.pos + 2)
5061 .is_some_and(|t| t.ttype == TokenType::LBrace);
5062 if next_two_are_block {
5063 let bad = self.current().clone();
5064 return Err(ParseError {
5065 message: format!(
5066 "unknown `stream` handler `{name}` — this block accepts only \
5067 `on_chunk:` (run once per chunk, with the chunk bound as `chunk`), \
5068 `on_complete:` (run once, after the source closes, with the \
5069 accumulation bound as `complete`) and `on_error:` (run when the \
5070 SOURCE fails, with the failure bound as `error`). An unrecognised \
5071 handler is refused rather than skipped: a skipped handler removes \
5072 the processing the author wrote, and silence in that direction is \
5073 indistinguishable from a stream that had nothing to do."
5074 ),
5075 line: bad.line,
5076 column: bad.column,
5077 ..Default::default()
5078 });
5079 }
5080
5081 block.body.push(self.parse_flow_step()?);
5083 }
5084 self.consume(TokenType::RBrace)?;
5085 }
5086
5087 Ok(block)
5088 }
5089
5090 fn parse_stream_handler_arm(
5099 &mut self,
5100 name: &str,
5101 at: &Token,
5102 ) -> Result<StepNode, ParseError> {
5103 self.consume(TokenType::LBrace)?;
5104 let mut node = StepNode {
5105 name: name.to_string(),
5106 persona_ref: String::new(),
5107 given: String::new(),
5108 ask: String::new(),
5109 output_type: String::new(),
5110 confidence_floor: None,
5111 navigate_ref: String::new(),
5112 apply_ref: String::new(),
5113 requires_context: None,
5114 now_tz: None,
5115 guards: Vec::new(),
5116 pix_ops: Vec::new(),
5117 stream: None,
5118 performs: Vec::new(),
5119 loc: self.loc_of(at),
5120 };
5121 self.parse_step_body_into(&mut node)?;
5122 self.consume(TokenType::RBrace)?;
5123 Ok(node)
5124 }
5125
5126 fn parse_block_step(&mut self, _kw: &str) -> Result<Loc, ParseError> {
5127 let tok = self.current().clone();
5128 self.advance();
5129 while !self.check(TokenType::LBrace)
5131 && !self.check(TokenType::RBrace)
5132 && !self.check(TokenType::Eof)
5133 && !self.at_declaration_start()
5134 {
5135 self.advance();
5136 }
5137 if self.check(TokenType::LBrace) {
5138 self.skip_braced_block()?;
5139 }
5140 Ok(Loc {
5141 line: tok.line,
5142 column: tok.column,
5143 })
5144 }
5145
5146 fn parse_forge_step(&mut self) -> Result<ForgeBlock, ParseError> {
5153 let tok = self.consume(TokenType::Forge)?;
5154 let name = self.consume(TokenType::Identifier)?.value;
5155 let mut node = ForgeBlock {
5156 name,
5157 novelty: 0.5,
5158 depth: 1,
5159 branches: 1,
5160 loc: Loc { line: tok.line, column: tok.column },
5161 ..Default::default()
5162 };
5163 self.consume(TokenType::LParen)?;
5165 let arg = self.consume_any_ident_or_kw()?.value;
5166 self.consume(TokenType::Colon)?;
5167 if arg != "seed" {
5168 return Err(self.error(&format!(
5169 "forge '{}' expects `seed:` as its argument, found `{arg}`",
5170 node.name
5171 )));
5172 }
5173 node.seed = self.consume(TokenType::StringLit)?.value;
5174 self.consume(TokenType::RParen)?;
5175 self.consume(TokenType::Arrow)?;
5177 node.output_type = self.consume_any_ident_or_kw()?.value;
5178 self.consume(TokenType::LBrace)?;
5180 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5181 let field = self.consume_any_ident_or_kw()?.value;
5182 self.consume(TokenType::Colon)?;
5183 match field.as_str() {
5184 "mode" => node.mode = self.consume_any_ident_or_kw()?.value,
5185 "novelty" => node.novelty = self.consume_number()?,
5186 "depth" => {
5187 node.depth = self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
5188 }
5189 "branches" => {
5190 node.branches =
5191 self.consume(TokenType::Integer)?.value.parse::<i64>().unwrap_or(0)
5192 }
5193 "constraints" => node.constraints_ref = self.consume_any_ident_or_kw()?.value,
5194 other => {
5195 return Err(self.error(&format!("unknown forge field `{other}`")))
5196 }
5197 }
5198 if self.check(TokenType::Comma) {
5199 self.consume(TokenType::Comma)?;
5200 }
5201 }
5202 self.consume(TokenType::RBrace)?;
5203 Ok(node)
5204 }
5205
5206 fn parse_par_block(&mut self) -> Result<ParBlock, ParseError> {
5215 let tok = self.current().clone();
5216 self.advance(); self.consume(TokenType::LBrace)?;
5218 let mut branches: Vec<Vec<FlowStep>> = Vec::new();
5219 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5220 branches.push(vec![self.parse_flow_step()?]);
5221 }
5222 self.consume(TokenType::RBrace)?;
5223 Ok(ParBlock {
5224 branches,
5225 loc: Loc {
5226 line: tok.line,
5227 column: tok.column,
5228 },
5229 })
5230 }
5231
5232 fn parse_warden(&mut self) -> Result<WardenBlock, ParseError> {
5249 let tok = self.consume(TokenType::Warden)?;
5250 self.consume(TokenType::LParen)?;
5252 let target = self.consume_any_ident_or_kw()?.value;
5253 self.consume(TokenType::RParen)?;
5254 self.consume(TokenType::Within)?;
5256 let scope_ref = self.consume(TokenType::Identifier)?.value;
5257 let mut block = WardenBlock {
5258 target,
5259 scope_ref,
5260 body: Vec::new(),
5261 loc: Loc {
5262 line: tok.line,
5263 column: tok.column,
5264 },
5265 };
5266 self.consume(TokenType::LBrace)?;
5268 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5269 block.body.push(self.parse_flow_step()?);
5270 }
5271 self.consume(TokenType::RBrace)?;
5272 Ok(block)
5273 }
5274
5275 fn parse_scope(&mut self) -> Result<ScopeDefinition, ParseError> {
5280 let tok = self.consume(TokenType::Scope)?;
5281 let name = self.consume(TokenType::Identifier)?.value;
5282 let mut node = ScopeDefinition {
5283 name,
5284 loc: Loc {
5285 line: tok.line,
5286 column: tok.column,
5287 },
5288 ..Default::default()
5289 };
5290 self.consume(TokenType::LBrace)?;
5291 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5292 let key = self.consume_any_ident_or_kw()?.value;
5293 self.consume(TokenType::Colon)?;
5294 match key.as_str() {
5295 "targets" => {
5296 self.consume(TokenType::LBracket)?;
5297 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
5298 let t = if self.check(TokenType::StringLit) {
5299 self.consume(TokenType::StringLit)?.value
5300 } else {
5301 self.consume_any_ident_or_kw()?.value
5302 };
5303 node.targets.push(t);
5304 if self.check(TokenType::Comma) {
5305 self.advance();
5306 }
5307 }
5308 self.consume(TokenType::RBracket)?;
5309 }
5310 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
5311 "approver" => {
5312 if self.current().value == "requires" {
5314 self.advance();
5315 }
5316 node.approver = self.consume(TokenType::StringLit)?.value;
5317 }
5318 other => {
5319 return Err(self.error(&format!(
5320 "unknown scope field `{other}` in scope `{}` — expected \
5321 `targets` / `depth` / `approver`",
5322 node.name
5323 )))
5324 }
5325 }
5326 if self.check(TokenType::Comma) {
5327 self.consume(TokenType::Comma)?;
5328 }
5329 }
5330 self.consume(TokenType::RBrace)?;
5331 Ok(node)
5332 }
5333
5334 fn parse_quant(&mut self) -> Result<QuantBlock, ParseError> {
5335 let tok = self.current().clone();
5336 self.advance(); let mut block = QuantBlock {
5339 encoding: None,
5340 observable: None,
5341 qubits: None,
5342 depth: None,
5343 bandwidth: None,
5344 reupload: None,
5345 effect: "quant_sim".to_string(),
5348 body: Vec::new(),
5349 loc: Loc {
5350 line: tok.line,
5351 column: tok.column,
5352 },
5353 };
5354
5355 if self.check(TokenType::LParen) {
5357 self.advance();
5358 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
5359 let key = self.consume_any_ident_or_kw()?.value;
5360 self.consume(TokenType::Colon)?;
5361 match key.as_str() {
5362 "encoding" => {
5363 block.encoding = Some(self.consume_any_ident_or_kw()?.value)
5364 }
5365 "observable" => {
5366 block.observable = Some(self.parse_dotted_identifier()?)
5367 }
5368 "qubits" => block.qubits = Some(self.consume_number()? as i64),
5369 "depth" => block.depth = Some(self.consume_number()? as i64),
5370 "bandwidth" => block.bandwidth = Some(self.consume_number()?),
5371 "reupload" => block.reupload = Some(self.consume_number()? as i64),
5373 "backend" => block.effect = self.consume_any_ident_or_kw()?.value,
5375 other => {
5376 return Err(ParseError {
5377 message: format!(
5378 "Unknown `quant` attribute `{other}` — expected one of \
5379 encoding, observable, qubits, depth, bandwidth, reupload, backend"
5380 ),
5381 line: self.current().line,
5382 column: self.current().column,
5383 ..Default::default()
5384 });
5385 }
5386 }
5387 if self.check(TokenType::Comma) {
5389 self.advance();
5390 }
5391 }
5392 self.consume(TokenType::RParen)?;
5393 }
5394
5395 self.consume(TokenType::LBrace)?;
5397 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5398 block.body.push(self.parse_flow_step()?);
5399 }
5400 self.consume(TokenType::RBrace)?;
5401
5402 Ok(block)
5403 }
5404
5405 fn parse_yield(&mut self) -> Result<YieldStatement, ParseError> {
5409 let tok = self.consume(TokenType::Yield)?;
5410 let loc = self.loc_of(&tok);
5411 self.last_let_value_kind = "literal".to_string();
5412 let value_expr = self.parse_let_value_expr()?;
5413 Ok(YieldStatement {
5414 value_expr,
5415 value_kind: self.last_let_value_kind.clone(),
5416 loc,
5417 })
5418 }
5419
5420 fn parse_compute_apply(&mut self) -> Result<ComputeApplyStep, ParseError> {
5427 let tok = self.current().clone();
5428 let loc = self.loc_of(&tok);
5429 self.advance(); let compute_name = self.consume_any_ident_or_kw()?.value.clone();
5431
5432 let mut arguments = Vec::new();
5433 if self.current().value == "on" {
5434 self.advance();
5435 loop {
5436 arguments.push(self.parse_subject()?);
5442 if self.check(TokenType::Comma) {
5443 self.advance();
5444 } else {
5445 break;
5446 }
5447 }
5448 }
5449
5450 let mut output_name = String::new();
5451 if self.check(TokenType::Arrow) {
5452 self.advance();
5453 output_name = self.consume_any_ident_or_kw()?.value.clone();
5454 }
5455
5456 Ok(ComputeApplyStep {
5457 compute_name,
5458 arguments,
5459 output_name,
5460 loc,
5461 })
5462 }
5463
5464 fn parse_apply_step(&mut self, _kw: &str) -> Result<(Loc, String, String, String), ParseError> {
5465 let tok = self.current().clone();
5466 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
5468 let mut target = String::new();
5469 let mut output_type = String::new();
5470 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5472 let next = self.current().clone();
5473 if next.value == "on" {
5474 self.advance();
5475 target = self.parse_subject()?;
5478 }
5479 }
5480 if self.check(TokenType::Arrow) {
5482 self.advance();
5483 output_type = self.consume_any_ident_or_kw()?.value.clone();
5484 }
5485 if self.check(TokenType::LBrace) {
5487 self.skip_braced_block()?;
5488 }
5489 Ok((
5490 Loc {
5491 line: tok.line,
5492 column: tok.column,
5493 },
5494 name,
5495 target,
5496 output_type,
5497 ))
5498 }
5499
5500 fn parse_step_guard(&mut self, kind: &str) -> Result<StepGuardNode, ParseError> {
5511 let tok = self.current().clone();
5512 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
5514 let mut target = String::new();
5515 let mut binding = String::new();
5516 if self.current().value == "on" {
5517 self.advance();
5518 target = self.parse_subject()?;
5521 if self.check(TokenType::LParen) {
5524 let mut depth = 0usize;
5525 loop {
5526 let t = self.current().clone();
5527 match t.ttype {
5528 TokenType::LParen => depth += 1,
5529 TokenType::RParen => depth -= 1,
5530 TokenType::Eof => {
5531 return Err(ParseError {
5532 message: format!(
5533 "unterminated argument list in `{kind} {name} on {target}(…`"
5534 ),
5535 line: t.line,
5536 column: t.column,
5537 ..Default::default()
5538 })
5539 }
5540 _ => {}
5541 }
5542 target.push_str(&t.value);
5543 self.advance();
5544 if depth == 0 {
5545 break;
5546 }
5547 }
5548 }
5549 }
5550 if self.check(TokenType::Arrow) {
5551 self.advance();
5552 binding = self.consume_any_ident_or_kw()?.value.clone();
5553 }
5554 Ok(StepGuardNode {
5555 kind: kind.to_string(),
5556 name,
5557 target,
5558 binding,
5559 loc: Loc {
5560 line: tok.line,
5561 column: tok.column,
5562 },
5563 })
5564 }
5565
5566 fn parse_reason_step(&mut self) -> Result<ReasonStep, ParseError> {
5577 let tok = self.current().clone();
5578 let loc = self.loc_of(&tok);
5579 self.advance(); let next_is_field_key = self
5592 .tokens
5593 .get(self.pos + 1)
5594 .is_some_and(|t| t.ttype == TokenType::Colon);
5595 let target = if self.check(TokenType::LBrace)
5596 || self.at_declaration_start()
5597 || self.check(TokenType::RBrace)
5598 || self.check(TokenType::Eof)
5599 || next_is_field_key
5600 {
5601 String::new()
5602 } else {
5603 self.parse_dotted_identifier()?
5604 };
5605
5606 let mut node = ReasonStep {
5607 strategy: String::new(),
5608 target,
5609 given: String::new(),
5610 ask: String::new(),
5611 depth: None,
5612 loc,
5613 };
5614
5615 if !self.check(TokenType::LBrace) {
5616 return Ok(node);
5617 }
5618 self.advance();
5619 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5620 let key = self.current().clone();
5621 self.advance();
5622 self.consume(TokenType::Colon)?;
5623 match key.value.as_str() {
5624 "given" => {
5629 let mut parts = vec![self.parse_expression_string()?];
5630 while self.check(TokenType::Comma) {
5631 self.advance();
5632 parts.push(self.parse_expression_string()?);
5633 }
5634 node.given = parts.join(", ");
5635 }
5636 "ask" => node.ask = self.consume(TokenType::StringLit)?.value,
5637 "depth" => {
5638 let n = self.current().clone();
5639 if n.ttype != TokenType::Integer {
5640 return Err(ParseError {
5641 message: format!(
5642 "`depth:` in a `reason` block is a deliberation depth — a \
5643 positive integer (got '{}')",
5644 n.value
5645 ),
5646 line: n.line,
5647 column: n.column,
5648 ..Default::default()
5649 });
5650 }
5651 self.advance();
5652 node.depth = n.value.parse::<u32>().ok();
5653 }
5654 "chain_of_thought" => {
5658 let v = self.consume_any_ident_or_kw()?.value;
5659 if v == "enabled" {
5660 node.strategy = "chain_of_thought".to_string();
5661 }
5662 }
5663 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value,
5664 "target" => {
5673 let v = if self.check(TokenType::StringLit) {
5674 self.consume(TokenType::StringLit)?.value
5675 } else {
5676 self.parse_dotted_identifier()?
5677 };
5678 if !node.target.is_empty() {
5679 return Err(ParseError {
5680 message: format!(
5681 "`reason {} {{ target: … }}` declares the subject twice — \
5682 once positionally as `{}` and once as `target: {}`. They \
5683 are the same field. Write one of them.",
5684 node.target, node.target, v
5685 ),
5686 line: key.line,
5687 column: key.column,
5688 ..Default::default()
5689 });
5690 }
5691 node.target = v;
5692 }
5693 other => {
5694 return Err(ParseError {
5695 message: format!(
5696 "unknown field '{other}' in a `reason` block. Accepted: given, \
5697 ask, depth, strategy, chain_of_thought, target. A field this \
5698 block does not recognise is REFUSED rather than skipped — a \
5699 `reason` that silently loses its `ask:` deliberates over \
5700 nothing, and that failure is quiet."
5701 ),
5702 line: key.line,
5703 column: key.column,
5704 ..Default::default()
5705 })
5706 }
5707 }
5708 }
5709 self.consume(TokenType::RBrace)?;
5710 Ok(node)
5711 }
5712
5713 fn at_weave_field(&self) -> bool {
5722 const FIELDS: &[&str] = &["format", "include", "priority", "style"];
5723 self.field_ahead(FIELDS)
5724 }
5725
5726 fn parse_weave_step(&mut self) -> Result<FlowStep, ParseError> {
5735 let tok = self.current().clone();
5736 self.advance();
5737 let mut node = WeaveStep {
5738 sources: Vec::new(),
5739 target: String::new(),
5740 format_type: String::new(),
5741 priority: Vec::new(),
5742 style: String::new(),
5743 include: Vec::new(),
5744 loc: Loc {
5745 line: tok.line,
5746 column: tok.column,
5747 },
5748 };
5749 if self.check(TokenType::LBracket) {
5755 node.sources = self.parse_bracketed_dot_identifiers()?;
5756 } else if self.current().ttype == TokenType::Identifier
5757 && !self
5758 .tokens
5759 .get(self.pos + 1)
5760 .is_some_and(|t| t.ttype == TokenType::Colon)
5761 {
5762 node.sources = vec![self.parse_dotted_identifier()?];
5772 }
5773 if self.check(TokenType::Into) || self.current().value == "into" {
5775 self.advance();
5776 node.target = self.parse_dotted_identifier()?;
5777 }
5778 while self.at_weave_field() {
5780 let f = self.current().value.clone();
5781 self.advance();
5782 self.consume(TokenType::Colon)?;
5783 match f.as_str() {
5784 "format" => node.format_type = self.consume_any_ident_or_kw()?.value.clone(),
5785 "include" => node.include = self.parse_bracketed_dot_identifiers()?,
5786 "priority" => node.priority = self.parse_bracketed_dot_identifiers()?,
5787 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
5788 other => {
5791 return Err(ParseError {
5792 message: format!(
5793 "`{other}` passed the `weave` field test but has no handler — \
5794 the braceless catalog and its parser have drifted apart."
5795 ),
5796 line: tok.line,
5797 column: tok.column,
5798 ..Default::default()
5799 })
5800 }
5801 }
5802 }
5803 if self.check(TokenType::LBrace) {
5804 self.advance();
5805 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
5806 let f = self.current().value.clone();
5807 self.advance();
5808 if self.check(TokenType::Colon) {
5809 self.advance();
5810 match f.as_str() {
5811 "sources" => node.sources = self.parse_bracketed_dot_identifiers()?,
5812 "target" => node.target = self.consume_any_ident_or_kw()?.value.clone(),
5813 "format" => {
5814 node.format_type = self.consume_any_ident_or_kw()?.value.clone()
5815 }
5816 "priority" => node.priority = self.parse_bracketed_dot_identifiers()?,
5817 "style" => node.style = self.consume_any_ident_or_kw()?.value.clone(),
5818 "include" => node.include = self.parse_bracketed_dot_identifiers()?,
5822 _ => self.skip_value(),
5823 }
5824 }
5825 }
5826 if self.check(TokenType::RBrace) {
5827 self.advance();
5828 }
5829 }
5830 Ok(FlowStep::Weave(node))
5831 }
5832
5833 fn parse_use_step(&mut self) -> Result<FlowStep, ParseError> {
5834 let tok = self.current().clone();
5835 self.advance();
5836 let tool_name = self.consume_any_ident_or_kw()?.value.clone();
5837 let args = if self.check(TokenType::LParen) {
5848 UseArgs::Named(self.parse_named_arg_list()?)
5849 } else {
5850 let mut argument = String::new();
5851 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5852 let next = self.current().clone();
5853 if next.value == "on" {
5854 self.advance();
5855 argument = self.consume_any_ident_or_kw()?.value.clone();
5856 }
5857 }
5858 UseArgs::LegacyPositional(argument)
5859 };
5860 if self.check(TokenType::LBrace) {
5861 self.skip_braced_block()?;
5862 }
5863 Ok(FlowStep::UseTool(UseToolStep {
5864 tool_name,
5865 args,
5866 loc: Loc {
5867 line: tok.line,
5868 column: tok.column,
5869 },
5870 }))
5871 }
5872
5873 fn parse_named_arg_list(&mut self) -> Result<Vec<(String, String, String)>, ParseError> {
5879 self.consume(TokenType::LParen)?;
5880 let mut args = Vec::new();
5881 while !self.check(TokenType::RParen) {
5882 let name = self.consume_any_ident_or_kw()?.value;
5885 self.consume(TokenType::Assign)?;
5886 let value = self.parse_let_atom()?;
5887 let value_kind = self.last_let_value_kind.clone();
5891 args.push((name, value, value_kind));
5892 if self.check(TokenType::Comma) {
5893 self.advance();
5894 } else {
5895 break;
5896 }
5897 }
5898 self.consume(TokenType::RParen)?;
5899 Ok(args)
5900 }
5901
5902 fn parse_remember_step(&mut self) -> Result<FlowStep, ParseError> {
5903 let tok = self.current().clone();
5904 self.advance();
5905 let expr = self.consume_any_ident_or_kw()?.value.clone();
5906 let mut mem = String::new();
5907 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5908 let next = self.current().clone();
5909 if next.value == "in" || next.ttype == TokenType::In {
5910 self.advance();
5911 mem = self.consume_any_ident_or_kw()?.value.clone();
5912 }
5913 }
5914 Ok(FlowStep::Remember(RememberStep {
5915 expression: expr,
5916 memory_target: mem,
5917 loc: Loc {
5918 line: tok.line,
5919 column: tok.column,
5920 },
5921 }))
5922 }
5923
5924 fn parse_recall_step(&mut self) -> Result<FlowStep, ParseError> {
5925 let tok = self.current().clone();
5926 self.advance();
5927 let query = if self.check(TokenType::StringLit) {
5928 self.consume(TokenType::StringLit)?.value.clone()
5929 } else {
5930 self.consume_any_ident_or_kw()?.value.clone()
5931 };
5932 let mut mem = String::new();
5933 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5934 let next = self.current().clone();
5935 if next.value == "from" || next.ttype == TokenType::From {
5936 self.advance();
5937 mem = self.consume_any_ident_or_kw()?.value.clone();
5938 }
5939 }
5940 Ok(FlowStep::Recall(RecallStep {
5941 query,
5942 memory_source: mem,
5943 loc: Loc {
5944 line: tok.line,
5945 column: tok.column,
5946 },
5947 }))
5948 }
5949
5950 fn parse_hibernate_step(&mut self) -> Result<FlowStep, ParseError> {
5951 let tok = self.current().clone();
5952 self.advance();
5953 let mut event = String::new();
5954 let mut timeout = String::new();
5955 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5956 let first = self.consume_any_ident_or_kw()?.value.clone();
5961 if first == "until" && self.check(TokenType::StringLit) {
5962 event = self.consume(TokenType::StringLit)?.value.clone();
5963 } else {
5964 event = first;
5965 }
5966 }
5967 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
5968 let next = self.current().clone();
5969 if next.ttype == TokenType::Duration {
5970 self.advance();
5971 timeout = next.value.clone();
5972 }
5973 }
5974 Ok(FlowStep::Hibernate(HibernateStep {
5975 event_name: event,
5976 timeout,
5977 loc: Loc {
5978 line: tok.line,
5979 column: tok.column,
5980 },
5981 }))
5982 }
5983
5984 fn parse_grad_step(&mut self) -> Result<FlowStep, ParseError> {
5993 let tok = self.current().clone();
5994 self.advance();
5995 let target = self.consume_any_ident_or_kw()?.value.clone();
5996 let mut wrt: Vec<String> = Vec::new();
5997 let mut output = String::new();
5998 if !self.at_declaration_start() && self.current().value == "wrt" {
5999 self.advance();
6000 if self.check(TokenType::LBracket) {
6001 wrt = self.parse_bracketed_identifiers()?;
6002 } else {
6003 wrt.push(self.consume_any_ident_or_kw()?.value.clone());
6004 }
6005 }
6006 if !self.at_declaration_start() && self.current().value == "as" {
6007 self.advance();
6008 output = self.consume_any_ident_or_kw()?.value.clone();
6009 }
6010 Ok(FlowStep::Grad(GradStep {
6011 target,
6012 wrt,
6013 output,
6014 loc: Loc {
6015 line: tok.line,
6016 column: tok.column,
6017 },
6018 }))
6019 }
6020
6021 fn parse_focus_step(&mut self) -> Result<FlowStep, ParseError> {
6022 let tok = self.current().clone();
6023 self.advance();
6024 let expression = if self.at_declaration_start()
6025 || self.check(TokenType::RBrace)
6026 || self.check(TokenType::Eof)
6027 {
6028 String::new()
6029 } else {
6030 self.consume_any_ident_or_kw()?.value.clone()
6031 };
6032 let mut where_expr = String::new();
6033 let mut select: Vec<String> = Vec::new();
6034 let mut output = String::new();
6035 if self.check(TokenType::LBrace) {
6036 self.advance();
6037 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6038 if self.check(TokenType::Comma) {
6039 self.advance();
6040 continue;
6041 }
6042 let f = self.current().value.clone();
6043 self.advance();
6044 if self.check(TokenType::Colon) {
6045 self.advance();
6046 match f.as_str() {
6047 "where" => {
6048 where_expr = self.consume(TokenType::StringLit)?.value.clone()
6049 }
6050 "select" => select = self.parse_bracketed_identifiers()?,
6051 "as" | "alias" => {
6052 output = self.consume_any_ident_or_kw()?.value.clone()
6053 }
6054 _ => self.skip_value(),
6055 }
6056 }
6057 }
6058 if self.check(TokenType::RBrace) {
6059 self.advance();
6060 }
6061 }
6062 Ok(FlowStep::Focus(FocusStep {
6063 expression,
6064 where_expr,
6065 select,
6066 output,
6067 loc: Loc {
6068 line: tok.line,
6069 column: tok.column,
6070 },
6071 }))
6072 }
6073
6074 fn parse_associate_step(&mut self) -> Result<FlowStep, ParseError> {
6075 let tok = self.current().clone();
6076 self.advance();
6077 let left = self.consume_any_ident_or_kw()?.value.clone();
6078 let mut right = String::new();
6079 let mut using = String::new();
6080 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
6081 right = self.consume_any_ident_or_kw()?.value.clone();
6082 }
6083 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
6084 let next = self.current().clone();
6085 if next.value == "using" {
6086 self.advance();
6087 using = self.consume_any_ident_or_kw()?.value.clone();
6088 }
6089 }
6090 let mut output = String::new();
6091 if self.check(TokenType::LBrace) {
6092 self.advance();
6093 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6094 let f = self.current().value.clone();
6095 self.advance();
6096 if self.check(TokenType::Colon) {
6097 self.advance();
6098 match f.as_str() {
6099 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
6100 _ => self.skip_value(),
6101 }
6102 }
6103 }
6104 if self.check(TokenType::RBrace) {
6105 self.advance();
6106 }
6107 }
6108 Ok(FlowStep::Associate(AssociateStep {
6109 left,
6110 right,
6111 using_field: using,
6112 output,
6113 loc: Loc {
6114 line: tok.line,
6115 column: tok.column,
6116 },
6117 }))
6118 }
6119
6120 fn parse_aggregate_step(&mut self) -> Result<FlowStep, ParseError> {
6121 let tok = self.current().clone();
6122 self.advance();
6123 let target = self.consume_any_ident_or_kw()?.value.clone();
6124 let mut group_by = Vec::new();
6125 let mut alias = String::new();
6126 let mut compute: Vec<String> = Vec::new();
6127 let mut where_expr = String::new();
6128 if self.check(TokenType::LBrace) {
6129 self.advance();
6130 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6131 let f = self.current().value.clone();
6132 self.advance();
6133 if self.check(TokenType::Colon) {
6134 self.advance();
6135 match f.as_str() {
6136 "group_by" => group_by = self.parse_bracketed_identifiers()?,
6137 "alias" | "as" => alias = self.consume_any_ident_or_kw()?.value.clone(),
6138 "compute" => compute = self.parse_bracketed_aggregates()?,
6141 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
6143 _ => self.skip_value(),
6144 }
6145 }
6146 }
6147 if self.check(TokenType::RBrace) {
6148 self.advance();
6149 }
6150 }
6151 Ok(FlowStep::Aggregate(AggregateStep {
6152 target,
6153 group_by,
6154 alias,
6155 compute,
6156 where_expr,
6157 loc: Loc {
6158 line: tok.line,
6159 column: tok.column,
6160 },
6161 }))
6162 }
6163
6164 fn parse_explore_step(&mut self) -> Result<FlowStep, ParseError> {
6165 let tok = self.current().clone();
6166 self.advance();
6167 let target = self.consume_any_ident_or_kw()?.value.clone();
6168 let mut limit = None;
6169 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
6170 if self.current().ttype == TokenType::Integer {
6171 limit = self.current().value.parse::<i64>().ok();
6172 self.advance();
6173 }
6174 }
6175 let mut output = String::new();
6176 if self.check(TokenType::LBrace) {
6177 self.advance();
6178 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6179 let f = self.current().value.clone();
6180 self.advance();
6181 if self.check(TokenType::Colon) {
6182 self.advance();
6183 match f.as_str() {
6184 "as" | "alias" => output = self.consume_any_ident_or_kw()?.value.clone(),
6185 _ => self.skip_value(),
6186 }
6187 }
6188 }
6189 if self.check(TokenType::RBrace) {
6190 self.advance();
6191 }
6192 }
6193 Ok(FlowStep::ExploreStep(ExploreStepNode {
6194 target,
6195 limit,
6196 output,
6197 loc: Loc {
6198 line: tok.line,
6199 column: tok.column,
6200 },
6201 }))
6202 }
6203
6204 fn parse_bracketed_aggregates(&mut self) -> Result<Vec<String>, ParseError> {
6207 let mut out = Vec::new();
6208 self.consume(TokenType::LBracket)?;
6209 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
6210 let name = self.consume_any_ident_or_kw()?.value.clone();
6211 if self.check(TokenType::LParen) {
6212 self.advance();
6213 let col = self.consume_any_ident_or_kw()?.value.clone();
6214 self.consume(TokenType::RParen)?;
6215 out.push(format!("{name}({col})"));
6216 } else {
6217 out.push(name);
6218 }
6219 if self.check(TokenType::Comma) {
6220 self.advance();
6221 }
6222 }
6223 self.consume(TokenType::RBracket)?;
6224 Ok(out)
6225 }
6226
6227 fn parse_ingest_step(&mut self) -> Result<FlowStep, ParseError> {
6242 let tok = self.current().clone();
6243 self.advance();
6244 let source = self.consume_any_ident_or_kw()?.value.clone();
6245 let mut target = String::new();
6246 let mut format = String::new();
6247 let mut max_bytes: Option<u64> = None;
6248 let mut max_rows: Option<u64> = None;
6249 if !self.at_declaration_start() && !self.check(TokenType::RBrace) {
6250 let next = self.current().clone();
6251 if next.value == "into" || next.ttype == TokenType::Into {
6252 self.advance();
6253 target = self.consume_any_ident_or_kw()?.value.clone();
6254 }
6255 }
6256 if self.check(TokenType::LBrace) {
6257 self.consume(TokenType::LBrace)?;
6258 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6259 if self.check(TokenType::Comma) {
6261 self.advance();
6262 continue;
6263 }
6264 let entry = self.current().clone();
6265 match entry.value.as_str() {
6266 "format" => {
6267 self.advance();
6268 self.consume(TokenType::Colon)?;
6269 format = self.consume_any_ident_or_kw()?.value.clone();
6270 }
6271 "limits" => {
6272 self.advance();
6273 self.consume(TokenType::LBrace)?;
6274 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6275 let bound = self.current().clone();
6276 self.advance();
6277 self.consume(TokenType::Colon)?;
6278 let num_tok = self.consume(TokenType::Integer)?.clone();
6279 let value = num_tok.value.parse::<u64>().map_err(|_| ParseError {
6280 message: format!(
6281 "ingest `limits` bound `{}` must be a non-negative \
6282 integer byte/row count, got `{}`.",
6283 bound.value, num_tok.value
6284 ),
6285 line: num_tok.line,
6286 column: num_tok.column,
6287 ..Default::default()
6288 })?;
6289 match bound.value.as_str() {
6290 "max_bytes" => max_bytes = Some(value),
6291 "max_rows" => max_rows = Some(value),
6292 other => {
6293 return Err(ParseError {
6294 message: format!(
6295 "Unknown ingest limit `{other}`. The closed \
6296 limits grammar is `max_bytes: <N>` and \
6297 `max_rows: <N>` — bounds enforced on the raw \
6298 stream BEFORE parsing (§100).",
6299 ),
6300 line: bound.line,
6301 column: bound.column,
6302 ..Default::default()
6303 });
6304 }
6305 }
6306 if self.check(TokenType::Comma) {
6307 self.advance();
6308 }
6309 }
6310 self.consume(TokenType::RBrace)?;
6311 }
6312 other => {
6313 return Err(ParseError {
6314 message: format!(
6315 "Unknown entry `{other}` in ingest body. The closed \
6316 grammar is `format: csv|json` and \
6317 `limits {{ max_bytes: <N>, max_rows: <N> }}`.",
6318 ),
6319 line: entry.line,
6320 column: entry.column,
6321 ..Default::default()
6322 });
6323 }
6324 }
6325 }
6326 self.consume(TokenType::RBrace)?;
6327 }
6328 Ok(FlowStep::Ingest(IngestStep {
6329 source,
6330 target,
6331 format,
6332 max_bytes,
6333 max_rows,
6334 loc: Loc {
6335 line: tok.line,
6336 column: tok.column,
6337 },
6338 }))
6339 }
6340
6341 fn at_navigate_field(&self) -> bool {
6348 const FIELDS: &[&str] = &[
6349 "corpus", "query", "trail", "as", "from", "budget", "where",
6357 "depth", "recall",
6358 ];
6359 self.field_ahead(FIELDS)
6360 }
6361
6362 fn at_drill_field(&self) -> bool {
6364 const FIELDS: &[&str] = &["subtree", "path", "query", "as"];
6367 self.field_ahead(FIELDS)
6368 }
6369
6370 fn field_ahead(&self, names: &[&str]) -> bool {
6372 let cur = self.current();
6373 if !names.contains(&cur.value.as_str()) {
6374 return false;
6375 }
6376 self.tokens
6377 .get(self.pos + 1)
6378 .is_some_and(|t| t.ttype == TokenType::Colon)
6379 }
6380
6381 fn parse_config_key(&mut self) -> Result<String, ParseError> {
6391 if self.check(TokenType::StringLit) {
6392 return Ok(self.consume(TokenType::StringLit)?.value.clone());
6393 }
6394 let scheme = self.consume_any_ident_or_kw()?.value.clone();
6395 if self.check(TokenType::Colon) {
6396 self.advance();
6397 let key = self.consume_any_ident_or_kw()?.value.clone();
6398 return Ok(format!("{scheme}:{key}"));
6399 }
6400 Ok(scheme)
6401 }
6402
6403 fn parse_pix_value(&mut self) -> Result<String, ParseError> {
6408 if self.check(TokenType::StringLit) {
6409 return Ok(self.consume(TokenType::StringLit)?.value.clone());
6410 }
6411 Ok(self.consume_any_ident_or_kw()?.value.clone())
6412 }
6413
6414 fn parse_navigate_step(&mut self) -> Result<FlowStep, ParseError> {
6415 let tok = self.current().clone();
6416 self.advance();
6417 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
6418 let mut node = NavigateStep {
6419 depth: None,
6420 pix_name,
6421 corpus_name: String::new(),
6422 query_expr: String::new(),
6423 trail_enabled: false,
6424 output_name: String::new(),
6425 seed: String::new(),
6426 budget: None,
6427 where_expr: String::new(),
6428 loc: Loc {
6429 line: tok.line,
6430 column: tok.column,
6431 },
6432 };
6433 if !self.check(TokenType::LBrace) {
6447 while self.at_navigate_field() {
6448 let f = self.current().value.clone();
6449 self.advance();
6450 self.consume(TokenType::Colon)?;
6451 match f.as_str() {
6452 "corpus" => node.corpus_name = self.consume_any_ident_or_kw()?.value.clone(),
6453 "query" => node.query_expr = self.parse_pix_value()?,
6454 "trail" => {
6455 let v = self.consume_any_ident_or_kw()?.value;
6456 node.trail_enabled = matches!(v.as_str(), "true" | "enabled" | "on");
6457 }
6458 "output" | "as" => {
6459 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
6460 }
6461 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
6462 "budget" => node.budget = self.parse_optional_int(),
6463 "where" => node.where_expr = self.parse_pix_value()?,
6464 "depth" => node.depth = self.parse_optional_int(),
6465 "recall" => {
6473 let mode = self.consume_any_ident_or_kw()?.value.clone();
6474 if node.seed.is_empty() {
6475 node.seed = format!("recall:{mode}");
6476 }
6477 }
6478 _ => self.skip_value(),
6479 }
6480 }
6481 }
6482 if self.check(TokenType::LBrace) {
6483 self.advance();
6484 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6485 let f = self.current().value.clone();
6486 self.advance();
6487 if self.check(TokenType::Colon) {
6488 self.advance();
6489 match f.as_str() {
6490 "corpus" => {
6491 node.corpus_name = self.consume_any_ident_or_kw()?.value.clone()
6492 }
6493 "query" => node.query_expr = self.parse_pix_value()?,
6494 "trail" => {
6495 let v = self.consume_any_ident_or_kw()?.value;
6496 node.trail_enabled =
6497 matches!(v.as_str(), "true" | "enabled" | "on");
6498 }
6499 "output" | "as" => {
6500 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
6501 }
6502 "from" => node.seed = self.consume_any_ident_or_kw()?.value.clone(),
6504 "budget" => node.budget = self.parse_optional_int(),
6505 "where" => {
6513 node.where_expr = self.consume(TokenType::StringLit)?.value.clone()
6514 }
6515 _ => self.skip_value(),
6516 }
6517 }
6518 }
6519 if self.check(TokenType::RBrace) {
6520 self.advance();
6521 }
6522 }
6523 Ok(FlowStep::Navigate(node))
6524 }
6525
6526 fn parse_drill_step(&mut self) -> Result<FlowStep, ParseError> {
6527 let tok = self.current().clone();
6528 self.advance();
6529 let pix_name = self.consume_any_ident_or_kw()?.value.clone();
6530 let mut node = DrillStep {
6531 pix_name,
6532 subtree_path: String::new(),
6533 query_expr: String::new(),
6534 output_name: String::new(),
6535 loc: Loc {
6536 line: tok.line,
6537 column: tok.column,
6538 },
6539 };
6540 if self.current().value == "into" {
6544 self.advance();
6545 node.subtree_path = if self.check(TokenType::StringLit) {
6550 self.consume(TokenType::StringLit)?.value.clone()
6551 } else {
6552 self.parse_dotted_identifier()?
6553 };
6554 }
6555 if !self.check(TokenType::LBrace) {
6556 while self.at_drill_field() {
6557 let f = self.current().value.clone();
6558 self.advance();
6559 self.consume(TokenType::Colon)?;
6560 match f.as_str() {
6561 "subtree" | "path" => {
6562 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
6563 }
6564 "query" => node.query_expr = self.parse_pix_value()?,
6565 "output" | "as" => {
6566 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
6567 }
6568 _ => self.skip_value(),
6569 }
6570 }
6571 }
6572 if self.check(TokenType::LBrace) {
6573 self.advance();
6574 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6575 let f = self.current().value.clone();
6576 self.advance();
6577 if self.check(TokenType::Colon) {
6578 self.advance();
6579 match f.as_str() {
6580 "subtree" | "path" => {
6581 node.subtree_path = self.consume(TokenType::StringLit)?.value.clone()
6582 }
6583 "query" => node.query_expr = self.parse_pix_value()?,
6584 "output" | "as" => {
6585 node.output_name = self.consume_any_ident_or_kw()?.value.clone()
6586 }
6587 _ => self.skip_value(),
6588 }
6589 }
6590 }
6591 if self.check(TokenType::RBrace) {
6592 self.advance();
6593 }
6594 }
6595 Ok(FlowStep::Drill(node))
6596 }
6597
6598 fn parse_corroborate_step(&mut self) -> Result<FlowStep, ParseError> {
6599 let tok = self.current().clone();
6600 self.advance();
6601 let nav_ref = self.consume_any_ident_or_kw()?.value.clone();
6602 let mut output = String::new();
6603 if self.check(TokenType::Arrow) {
6604 self.advance();
6605 output = self.consume_any_ident_or_kw()?.value.clone();
6606 }
6607 Ok(FlowStep::Corroborate(CorroborateStep {
6608 navigate_ref: nav_ref,
6609 output_name: output,
6610 loc: Loc {
6611 line: tok.line,
6612 column: tok.column,
6613 },
6614 }))
6615 }
6616
6617 fn parse_listen_step(&mut self) -> Result<FlowStep, ParseError> {
6618 let tok = self.current().clone();
6619 self.advance();
6620 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
6624 (self.consume(TokenType::StringLit)?.value.clone(), false)
6625 } else {
6626 (self.consume_any_ident_or_kw()?.value.clone(), true)
6627 };
6628 let mut alias = String::new();
6629 if !self.at_declaration_start()
6630 && !self.check(TokenType::RBrace)
6631 && !self.check(TokenType::LBrace)
6632 {
6633 let next = self.current().clone();
6634 if next.value == "as" || next.ttype == TokenType::As {
6635 self.advance();
6636 alias = self.consume_any_ident_or_kw()?.value.clone();
6637 }
6638 }
6639 let body = self.parse_listener_body()?;
6643 Ok(FlowStep::Listen(ListenStep {
6644 channel,
6645 channel_is_ref,
6646 event_alias: alias,
6647 body,
6648 loc: Loc {
6649 line: tok.line,
6650 column: tok.column,
6651 },
6652 }))
6653 }
6654
6655 fn parse_listener_body(&mut self) -> Result<Vec<FlowStep>, ParseError> {
6660 let mut body = Vec::new();
6661 if self.check(TokenType::LBrace) {
6662 self.advance(); while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6664 body.push(self.parse_flow_step()?);
6665 }
6666 self.consume(TokenType::RBrace)?;
6667 }
6668 Ok(body)
6669 }
6670
6671 fn parse_retrieve_step(&mut self) -> Result<FlowStep, ParseError> {
6678 let tok = self.current().clone();
6679 self.advance();
6680 if self.check(TokenType::From) || self.current().value == "from" {
6683 self.advance();
6684 }
6685 let store = self.consume_any_ident_or_kw()?.value.clone();
6686 let mut where_expr = String::new();
6687 let mut alias = String::new();
6688 let mut order_by = String::new();
6689 let mut limit_expr = String::new();
6690 let mut aggregate = String::new();
6691 let mut group_by = String::new();
6692 let mut cache = String::new();
6693 loop {
6700 match self.current().value.as_str() {
6701 "where" if !self.check(TokenType::LBrace) => {
6702 self.advance();
6703 where_expr = self.consume(TokenType::StringLit)?.value.clone();
6704 }
6705 "as" => {
6706 self.advance();
6707 alias = self.consume_any_ident_or_kw()?.value.clone();
6708 }
6709 _ => break,
6710 }
6711 }
6712 if self.check(TokenType::LBrace) {
6713 self.advance();
6714 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6715 let f = self.current().value.clone();
6716 self.advance();
6717 if self.check(TokenType::Colon) {
6718 self.advance();
6719 match f.as_str() {
6720 "where" => where_expr = self.consume(TokenType::StringLit)?.value.clone(),
6721 "as" | "alias" => alias = self.consume_any_ident_or_kw()?.value.clone(),
6722 "order_by" => {
6725 order_by = self.consume(TokenType::StringLit)?.value.clone()
6726 }
6727 "limit" => {
6732 let t = self.current().clone();
6733 match t.ttype {
6734 TokenType::Integer | TokenType::StringLit => {
6735 limit_expr = t.value.clone();
6736 self.advance();
6737 }
6738 _ => self.skip_value(),
6739 }
6740 }
6741 "aggregate" => {
6748 aggregate = self.consume(TokenType::StringLit)?.value.clone()
6749 }
6750 "group_by" => {
6751 group_by = self.consume(TokenType::StringLit)?.value.clone()
6752 }
6753 "cache" => cache = self.consume_any_ident_or_kw()?.value.clone(),
6759 _ => self.skip_value(),
6760 }
6761 }
6762 }
6763 if self.check(TokenType::RBrace) {
6764 self.advance();
6765 }
6766 }
6767 Ok(FlowStep::Retrieve(RetrieveStep {
6768 store_name: store,
6769 where_expr,
6770 alias,
6771 order_by,
6772 limit_expr,
6773 aggregate,
6774 group_by,
6775 cache,
6776 loc: Loc {
6777 line: tok.line,
6778 column: tok.column,
6779 },
6780 }))
6781 }
6782
6783 fn parse_store_where_step(
6796 &mut self,
6797 ) -> Result<(Loc, String, String), ParseError> {
6798 let tok = self.current().clone();
6799 self.advance(); let store = if self.at_declaration_start()
6801 || self.check(TokenType::RBrace)
6802 || self.check(TokenType::Eof)
6803 {
6804 String::new()
6805 } else {
6806 self.consume_any_ident_or_kw()?.value.clone()
6807 };
6808 let mut where_expr = String::new();
6809 if self.check(TokenType::LBrace) {
6810 self.advance();
6811 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6812 let field = self.current().value.clone();
6813 self.advance();
6814 if self.check(TokenType::Colon) {
6815 self.advance();
6816 match field.as_str() {
6817 "where" => {
6818 where_expr =
6819 self.consume(TokenType::StringLit)?.value.clone()
6820 }
6821 _ => self.skip_value(),
6822 }
6823 }
6824 }
6825 if self.check(TokenType::RBrace) {
6826 self.advance();
6827 }
6828 }
6829 Ok((
6830 Loc {
6831 line: tok.line,
6832 column: tok.column,
6833 },
6834 store,
6835 where_expr,
6836 ))
6837 }
6838
6839 fn parse_persist_step(&mut self) -> Result<FlowStep, ParseError> {
6858 let tok = self.current().clone();
6859 self.advance(); if self.current().value == "into" && !self.check(TokenType::LBrace) {
6863 self.advance();
6864 }
6865 let store = if self.at_declaration_start()
6866 || self.check(TokenType::LBrace)
6867 || self.check(TokenType::RBrace)
6868 || self.check(TokenType::Eof)
6869 {
6870 String::new()
6871 } else {
6872 self.consume_any_ident_or_kw()?.value.clone()
6873 };
6874 let mut fields: Vec<(String, String)> = Vec::new();
6875 if self.check(TokenType::LBrace) {
6876 self.advance();
6877 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6878 let col = self.current().value.clone();
6879 self.advance();
6880 if self.check(TokenType::Colon) {
6881 self.advance();
6882 let value = if self.check(TokenType::StringLit) {
6883 self.consume(TokenType::StringLit)?.value.clone()
6884 } else if self.check(TokenType::RBrace)
6885 || self.check(TokenType::Eof)
6886 || self.check(TokenType::Colon)
6887 {
6888 String::new()
6889 } else {
6890 let v = self.current().clone();
6891 self.advance();
6892 v.value.clone()
6893 };
6894 fields.push((col, value));
6895 }
6896 }
6897 if self.check(TokenType::RBrace) {
6898 self.advance();
6899 }
6900 }
6901 Ok(FlowStep::Persist(PersistStep {
6902 store_name: store,
6903 fields,
6904 loc: Loc {
6905 line: tok.line,
6906 column: tok.column,
6907 },
6908 }))
6909 }
6910
6911 fn parse_mutate_step(&mut self) -> Result<FlowStep, ParseError> {
6925 let tok = self.current().clone();
6926 self.advance(); let store = if self.at_declaration_start()
6928 || self.check(TokenType::LBrace)
6929 || self.check(TokenType::RBrace)
6930 || self.check(TokenType::Eof)
6931 {
6932 String::new()
6933 } else {
6934 self.consume_any_ident_or_kw()?.value.clone()
6935 };
6936 let mut where_expr = String::new();
6937 let mut fields: Vec<(String, String)> = Vec::new();
6938 if self.check(TokenType::LBrace) {
6939 self.advance();
6940 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
6941 let key = self.current().value.clone();
6942 self.advance();
6943 if self.check(TokenType::Colon) {
6944 self.advance();
6945 if key == "where" {
6946 where_expr =
6947 self.consume(TokenType::StringLit)?.value.clone();
6948 } else {
6949 let value = if self.check(TokenType::StringLit) {
6950 self.consume(TokenType::StringLit)?.value.clone()
6951 } else if self.check(TokenType::RBrace)
6952 || self.check(TokenType::Eof)
6953 || self.check(TokenType::Colon)
6954 {
6955 String::new()
6956 } else {
6957 let v = self.current().clone();
6958 self.advance();
6959 v.value.clone()
6960 };
6961 fields.push((key, value));
6962 }
6963 }
6964 }
6965 if self.check(TokenType::RBrace) {
6966 self.advance();
6967 }
6968 }
6969 Ok(FlowStep::Mutate(MutateStep {
6970 store_name: store,
6971 where_expr,
6972 fields,
6973 loc: Loc {
6974 line: tok.line,
6975 column: tok.column,
6976 },
6977 }))
6978 }
6979
6980 fn parse_agent(&mut self) -> Result<AgentDefinition, ParseError> {
6983 let tok = self.consume(TokenType::Agent)?;
6984 let name = self.consume(TokenType::Identifier)?.value;
6985 let mut node = AgentDefinition {
6986 name,
6987 goal: String::new(),
6988 tools: Vec::new(),
6989 memory_ref: String::new(),
6990 strategy: String::new(),
6991 on_stuck: String::new(),
6992 shield_ref: String::new(),
6993 max_iterations: None,
6994 max_tokens: None,
6995 max_time: String::new(),
6996 max_cost: None,
6997 loc: Loc {
6998 line: tok.line,
6999 column: tok.column,
7000 },
7001 leading_trivia: Vec::new(),
7002 trailing_trivia: Vec::new(),
7003 };
7004 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
7006 self.advance();
7007 }
7008 self.consume(TokenType::LBrace)?;
7009 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7010 let field = self.current().clone();
7011 let field_name = field.value.clone();
7012 self.advance();
7013 if self.check(TokenType::Colon) {
7014 self.advance();
7015 match field_name.as_str() {
7016 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
7017 "tools" => node.tools = self.parse_bracketed_identifiers()?,
7018 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
7019 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
7020 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
7021 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
7022 "max_iterations" => node.max_iterations = self.parse_optional_int(),
7023 "max_tokens" => node.max_tokens = self.parse_optional_int(),
7024 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
7025 "max_cost" => node.max_cost = self.parse_optional_float(),
7026 _ => self.skip_value(),
7027 }
7028 } else if self.check(TokenType::LBrace) {
7029 self.skip_braced_block()?;
7030 }
7031 }
7032 self.consume(TokenType::RBrace)?;
7033 Ok(node)
7034 }
7035
7036 fn parse_extension(&mut self) -> Result<ExtensionDefinition, ParseError> {
7041 let tok = self.consume(TokenType::Extension)?;
7042 let name = self.consume(TokenType::Identifier)?.value;
7043 let mut node = ExtensionDefinition {
7044 name,
7045 category: String::new(),
7046 members: Vec::new(),
7047 loc: Loc {
7048 line: tok.line,
7049 column: tok.column,
7050 },
7051 leading_trivia: Vec::new(),
7052 trailing_trivia: Vec::new(),
7053 };
7054 self.consume(TokenType::LBrace)?;
7055 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7056 let field_name = self.current().value.clone();
7057 self.advance();
7058 if self.check(TokenType::Colon) {
7059 self.advance();
7060 match field_name.as_str() {
7061 "category" => {
7062 node.category = self.consume_any_ident_or_kw()?.value.clone()
7063 }
7064 "members" => node.members = self.parse_extension_members()?,
7065 _ => self.skip_value(),
7066 }
7067 } else if self.check(TokenType::LBrace) {
7068 self.skip_braced_block()?;
7069 }
7070 }
7071 self.consume(TokenType::RBrace)?;
7072 Ok(node)
7073 }
7074
7075 fn parse_extension_members(&mut self) -> Result<Vec<ExtensionMember>, ParseError> {
7079 let mut members = Vec::new();
7080 self.consume(TokenType::LBracket)?;
7081 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
7082 let name_tok = self.consume(TokenType::StringLit)?;
7083 let mut member = ExtensionMember {
7084 name: name_tok.value.clone(),
7085 semantics: None,
7086 default_confidence: None,
7087 loc: Loc {
7088 line: name_tok.line,
7089 column: name_tok.column,
7090 },
7091 };
7092 if self.check(TokenType::Colon) {
7094 self.advance();
7095 self.consume(TokenType::LBrace)?;
7096 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7097 let mkey = self.current().value.clone();
7098 self.advance();
7099 if self.check(TokenType::Colon) {
7100 self.advance();
7101 match mkey.as_str() {
7102 "semantics" => {
7103 member.semantics =
7104 Some(self.consume(TokenType::StringLit)?.value.clone())
7105 }
7106 "default_confidence" => {
7107 member.default_confidence = self.parse_optional_float()
7108 }
7109 _ => self.skip_value(),
7110 }
7111 }
7112 if self.check(TokenType::Comma) {
7113 self.advance();
7114 }
7115 }
7116 self.consume(TokenType::RBrace)?;
7117 }
7118 members.push(member);
7119 if self.check(TokenType::Comma) {
7120 self.advance();
7121 }
7122 }
7123 self.consume(TokenType::RBracket)?;
7124 Ok(members)
7125 }
7126
7127 fn parse_window(&mut self) -> Result<WindowDefinition, ParseError> {
7130 let tok = self.consume(TokenType::Window)?;
7131 let name = self.consume(TokenType::Identifier)?.value;
7132 let mut node = WindowDefinition {
7133 name,
7134 timezone: String::new(),
7135 allow: Vec::new(),
7136 exclude: Vec::new(),
7137 on_outside: String::new(),
7138 loc: Loc {
7139 line: tok.line,
7140 column: tok.column,
7141 },
7142 leading_trivia: Vec::new(),
7143 trailing_trivia: Vec::new(),
7144 };
7145 self.consume(TokenType::LBrace)?;
7146 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7147 let field_name = self.consume_any_ident_or_kw()?.value;
7148 self.consume(TokenType::Colon)?;
7149 match field_name.as_str() {
7150 "timezone" => node.timezone = self.consume(TokenType::StringLit)?.value,
7151 "allow" => node.allow = self.parse_window_allow()?,
7152 "exclude" => node.exclude = self.parse_window_exclude()?,
7153 "on_outside" => node.on_outside = self.consume_any_ident_or_kw()?.value,
7154 _ => self.skip_value(),
7155 }
7156 }
7157 self.consume(TokenType::RBrace)?;
7158 Ok(node)
7159 }
7160
7161 fn parse_window_allow(&mut self) -> Result<Vec<WindowSpan>, ParseError> {
7163 self.consume(TokenType::LBracket)?;
7164 let mut spans = Vec::new();
7165 if !self.check(TokenType::RBracket) {
7166 spans.push(self.parse_window_span()?);
7167 while self.check(TokenType::Comma) {
7168 self.advance();
7169 if self.check(TokenType::RBracket) {
7170 break; }
7172 spans.push(self.parse_window_span()?);
7173 }
7174 }
7175 self.consume(TokenType::RBracket)?;
7176 Ok(spans)
7177 }
7178
7179 fn parse_window_exclude(&mut self) -> Result<Vec<String>, ParseError> {
7183 self.consume(TokenType::LBracket)?;
7184 let mut dates = Vec::new();
7185 if !self.check(TokenType::RBracket) {
7186 dates.push(self.consume(TokenType::StringLit)?.value);
7187 while self.check(TokenType::Comma) {
7188 self.advance();
7189 if self.check(TokenType::RBracket) {
7190 break; }
7192 dates.push(self.consume(TokenType::StringLit)?.value);
7193 }
7194 }
7195 self.consume(TokenType::RBracket)?;
7196 Ok(dates)
7197 }
7198
7199 fn parse_window_span(&mut self) -> Result<WindowSpan, ParseError> {
7201 let tok = self.consume(TokenType::LBrace)?;
7202 let mut span = WindowSpan {
7203 day_start: String::new(),
7204 day_end: String::new(),
7205 hour_start: 0,
7206 hour_end: 0,
7207 loc: Loc {
7208 line: tok.line,
7209 column: tok.column,
7210 },
7211 };
7212 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7213 let field = self.consume_any_ident_or_kw()?.value;
7214 self.consume(TokenType::Colon)?;
7215 match field.as_str() {
7216 "days" => {
7217 span.day_start = self.consume_any_ident_or_kw()?.value;
7218 self.consume(TokenType::DotDot)?;
7219 span.day_end = self.consume_any_ident_or_kw()?.value;
7220 }
7221 "hours" => {
7222 span.hour_start = self.consume_number()? as i64;
7223 self.consume(TokenType::DotDot)?;
7224 span.hour_end = self.consume_number()? as i64;
7225 }
7226 _ => self.skip_value(),
7227 }
7228 if self.check(TokenType::Comma) {
7229 self.advance();
7230 }
7231 }
7232 self.consume(TokenType::RBrace)?;
7233 Ok(span)
7234 }
7235
7236 fn parse_shield(&mut self) -> Result<ShieldDefinition, ParseError> {
7237 let tok = self.consume(TokenType::Shield)?;
7238 let name = self.consume(TokenType::Identifier)?.value;
7239 let mut node = ShieldDefinition {
7240 name,
7241 scan: Vec::new(),
7242 strategy: String::new(),
7243 on_breach: String::new(),
7244 severity: String::new(),
7245 quarantine: String::new(),
7246 max_retries: None,
7247 confidence_threshold: None,
7248 allow_tools: Vec::new(),
7249 deny_tools: Vec::new(),
7250 sandbox: None,
7251 redact: Vec::new(),
7252 log: String::new(),
7253 deflect_message: String::new(),
7254 taint: String::new(),
7255 compliance: Vec::new(),
7256 sign: String::new(),
7257 unknown_fields: Vec::new(),
7258 loc: Loc {
7259 line: tok.line,
7260 column: tok.column,
7261 },
7262 leading_trivia: Vec::new(),
7263 trailing_trivia: Vec::new(),
7264 };
7265 self.consume(TokenType::LBrace)?;
7266 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7267 let field_name = self.current().value.clone();
7268 let field_loc = Loc {
7269 line: self.current().line,
7270 column: self.current().column,
7271 };
7272 self.advance();
7273 if self.check(TokenType::Colon) {
7274 self.advance();
7275 match field_name.as_str() {
7276 "scan" => node.scan = self.parse_bracketed_identifiers()?,
7277 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
7278 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
7279 "severity" => node.severity = self.consume_any_ident_or_kw()?.value.clone(),
7280 "quarantine" => {
7281 node.quarantine = self.consume(TokenType::StringLit)?.value.clone()
7282 }
7283 "max_retries" => node.max_retries = self.parse_optional_int(),
7284 "confidence_threshold" => {
7285 node.confidence_threshold = self.parse_optional_float()
7286 }
7287 "allow_tools" => node.allow_tools = self.parse_bracketed_identifiers()?,
7288 "deny_tools" => node.deny_tools = self.parse_bracketed_identifiers()?,
7289 "sandbox" => {
7290 node.sandbox = Some(self.consume_any_ident_or_kw()?.value == "true")
7291 }
7292 "redact" => node.redact = self.parse_bracketed_identifiers()?,
7293 "log" => node.log = self.consume_any_ident_or_kw()?.value.clone(),
7294 "deflect_message" => {
7295 node.deflect_message = self.consume(TokenType::StringLit)?.value.clone()
7296 }
7297 "taint" => node.taint = self.consume_any_ident_or_kw()?.value.clone(),
7298 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
7300 "sign" => node.sign = self.consume_any_ident_or_kw()?.value.clone(),
7303 _ => {
7307 node.unknown_fields.push((field_name.clone(), field_loc));
7308 self.skip_value()
7309 }
7310 }
7311 } else if self.check(TokenType::LBrace) {
7312 self.skip_braced_block()?;
7313 }
7314 }
7315 self.consume(TokenType::RBrace)?;
7316 Ok(node)
7317 }
7318
7319 fn parse_pix(&mut self) -> Result<PixDefinition, ParseError> {
7320 let tok = self.consume(TokenType::Pix)?;
7321 let name = self.consume(TokenType::Identifier)?.value;
7322 let mut node = PixDefinition {
7323 name,
7324 source: String::new(),
7325 depth: None,
7326 branching: None,
7327 model: String::new(),
7328 loc: Loc {
7329 line: tok.line,
7330 column: tok.column,
7331 },
7332 leading_trivia: Vec::new(),
7333 trailing_trivia: Vec::new(),
7334 };
7335 self.consume(TokenType::LBrace)?;
7336 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7337 let field_name = self.current().value.clone();
7338 self.advance();
7339 if self.check(TokenType::Colon) {
7340 self.advance();
7341 match field_name.as_str() {
7342 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
7343 "depth" => node.depth = self.parse_optional_int(),
7344 "branching" => node.branching = self.parse_optional_int(),
7345 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
7346 _ => self.skip_value(),
7347 }
7348 } else if self.check(TokenType::LBrace) {
7349 self.skip_braced_block()?;
7350 }
7351 }
7352 self.consume(TokenType::RBrace)?;
7353 Ok(node)
7354 }
7355
7356 fn parse_ledger(&mut self) -> Result<LedgerDefinition, ParseError> {
7362 let tok = self.consume(TokenType::Ledger)?;
7363 let name = self.consume(TokenType::Identifier)?.value;
7364 let mut node = LedgerDefinition {
7365 name,
7366 source: String::new(),
7367 depth: None,
7368 branching: None,
7369 model: String::new(),
7370 loc: Loc {
7371 line: tok.line,
7372 column: tok.column,
7373 },
7374 leading_trivia: Vec::new(),
7375 trailing_trivia: Vec::new(),
7376 };
7377 self.consume(TokenType::LBrace)?;
7378 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7379 let field_name = self.current().value.clone();
7380 self.advance();
7381 if self.check(TokenType::Colon) {
7382 self.advance();
7383 match field_name.as_str() {
7384 "source" => node.source = self.consume(TokenType::StringLit)?.value.clone(),
7385 "depth" => node.depth = self.parse_optional_int(),
7386 "branching" => node.branching = self.parse_optional_int(),
7387 "model" => node.model = self.consume_any_ident_or_kw()?.value.clone(),
7388 _ => self.skip_value(),
7389 }
7390 } else if self.check(TokenType::LBrace) {
7391 self.skip_braced_block()?;
7392 }
7393 }
7394 self.consume(TokenType::RBrace)?;
7395 Ok(node)
7396 }
7397
7398 fn parse_psyche(&mut self) -> Result<PsycheDefinition, ParseError> {
7399 let tok = self.consume(TokenType::Psyche)?;
7400 let name = self.consume(TokenType::Identifier)?.value;
7401 let mut node = PsycheDefinition {
7402 name,
7403 dimensions: Vec::new(),
7404 manifold_noise: None,
7405 manifold_momentum: None,
7406 safety_constraints: Vec::new(),
7407 quantum_enabled: None,
7408 inference_mode: String::new(),
7409 loc: Loc {
7410 line: tok.line,
7411 column: tok.column,
7412 },
7413 leading_trivia: Vec::new(),
7414 trailing_trivia: Vec::new(),
7415 };
7416 self.consume(TokenType::LBrace)?;
7417 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7418 let field_name = self.current().value.clone();
7419 self.advance();
7420 if self.check(TokenType::Colon) {
7421 self.advance();
7422 match field_name.as_str() {
7423 "dimensions" => node.dimensions = self.parse_bracketed_identifiers()?,
7424 "manifold_noise" => node.manifold_noise = self.parse_optional_float(),
7425 "manifold_momentum" => node.manifold_momentum = self.parse_optional_float(),
7426 "safety_constraints" | "safety" => {
7431 node.safety_constraints = self.parse_bracketed_identifiers()?
7432 }
7433 "quantum_enabled" => {
7434 node.quantum_enabled = Some(self.consume_any_ident_or_kw()?.value == "true")
7435 }
7436 "inference_mode" => {
7437 node.inference_mode = self.consume_any_ident_or_kw()?.value.clone()
7438 }
7439 _ => self.skip_value(),
7440 }
7441 } else if self.check(TokenType::LBrace) {
7442 self.skip_braced_block()?;
7443 }
7444 }
7445 self.consume(TokenType::RBrace)?;
7446 Ok(node)
7447 }
7448
7449 fn parse_corpus(&mut self) -> Result<CorpusDefinition, ParseError> {
7450 let tok = self.consume(TokenType::Corpus)?;
7451 let name = self.consume(TokenType::Identifier)?.value;
7452 let mut node = CorpusDefinition {
7453 name,
7454 documents: Vec::new(),
7455 relations: Vec::new(),
7456 adaptive: false,
7457 mcp_server: String::new(),
7458 mcp_resource_uri: String::new(),
7459 store_source: None,
7460 loc: Loc {
7461 line: tok.line,
7462 column: tok.column,
7463 },
7464 leading_trivia: Vec::new(),
7465 trailing_trivia: Vec::new(),
7466 };
7467 let mut dynamic = false;
7471 if self.check(TokenType::From) {
7472 self.advance();
7473 if self.check(TokenType::AxonStore) {
7474 self.advance();
7475 dynamic = true;
7476 } else {
7477 self.consume(TokenType::Mcp)?;
7478 self.consume(TokenType::LParen)?;
7479 node.mcp_server = self.consume(TokenType::StringLit)?.value.clone();
7480 self.consume(TokenType::Comma)?;
7481 node.mcp_resource_uri = self.consume(TokenType::StringLit)?.value.clone();
7482 self.consume(TokenType::RParen)?;
7483 return Ok(node);
7484 }
7485 }
7486 self.consume(TokenType::LBrace)?;
7487 let mut src = CorpusStoreSource {
7490 doc_store: String::new(),
7491 doc_id_col: String::new(),
7492 doc_title_col: String::new(),
7493 edge_store: String::new(),
7494 edge_from_col: String::new(),
7495 edge_to_col: String::new(),
7496 edge_type_col: String::new(),
7497 edge_weight_col: String::new(),
7498 loc: Loc {
7499 line: tok.line,
7500 column: tok.column,
7501 },
7502 };
7503 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7504 let field_name = self.current().value.clone();
7505 self.advance();
7506 if self.check(TokenType::Colon) {
7507 self.advance();
7508 match field_name.as_str() {
7509 "documents" if dynamic => {
7511 let (store, cols) = self.parse_corpus_store_mapping(2)?;
7512 src.doc_store = store;
7513 src.doc_id_col = cols[0].clone();
7514 src.doc_title_col = cols[1].clone();
7515 }
7516 "documents" => node.documents = self.parse_bracketed_identifiers()?,
7517 "relations" if dynamic => {
7519 let (store, cols) = self.parse_corpus_store_mapping(4)?;
7520 src.edge_store = store;
7521 src.edge_from_col = cols[0].clone();
7522 src.edge_to_col = cols[1].clone();
7523 src.edge_type_col = cols[2].clone();
7524 src.edge_weight_col = cols[3].clone();
7525 }
7526 "relations" => node.relations = self.parse_corpus_relations()?,
7528 "adaptive" => node.adaptive = self.consume_any_ident_or_kw()?.value == "true",
7530 _ => self.skip_value(),
7531 }
7532 } else if self.check(TokenType::LBrace) {
7533 self.skip_braced_block()?;
7534 }
7535 }
7536 self.consume(TokenType::RBrace)?;
7537 if dynamic {
7538 node.store_source = Some(src);
7539 }
7540 Ok(node)
7541 }
7542
7543 fn parse_corpus_store_mapping(&mut self, n: usize) -> Result<(String, Vec<String>), ParseError> {
7550 let store = self.consume(TokenType::Identifier)?.value.clone();
7551 self.consume(TokenType::LParen)?;
7552 let mut cols = Vec::with_capacity(n);
7553 for i in 0..n {
7554 if i > 0 {
7555 self.consume(TokenType::Comma)?;
7556 }
7557 cols.push(self.consume_any_ident_or_kw()?.value.clone());
7558 }
7559 self.consume(TokenType::RParen)?;
7560 Ok((store, cols))
7561 }
7562
7563 fn parse_corpus_relations(&mut self) -> Result<Vec<CorpusRelation>, ParseError> {
7569 let mut out = Vec::new();
7570 self.consume(TokenType::LBracket)?;
7571 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
7572 if self.check(TokenType::Comma) {
7573 self.advance();
7574 continue;
7575 }
7576 let tok = self.current().clone();
7577 let etype = self.consume_any_ident_or_kw()?.value.clone();
7578 self.consume(TokenType::LParen)?;
7579 let from = self.consume_any_ident_or_kw()?.value.clone();
7580 self.consume(TokenType::Comma)?;
7581 let to = self.consume_any_ident_or_kw()?.value.clone();
7582 self.consume(TokenType::Comma)?;
7583 let weight = self.consume_number()?;
7584 self.consume(TokenType::RParen)?;
7585 out.push(CorpusRelation {
7586 etype,
7587 from,
7588 to,
7589 weight,
7590 loc: Loc { line: tok.line, column: tok.column },
7591 });
7592 }
7593 self.consume(TokenType::RBracket)?;
7594 Ok(out)
7595 }
7596
7597 fn parse_dataspace(&mut self) -> Result<DataspaceDefinition, ParseError> {
7614 let tok = self.consume(TokenType::Dataspace)?;
7615 let name = self.consume(TokenType::Identifier)?.value;
7616 let mut node = DataspaceDefinition {
7617 name,
7618 columns: Vec::new(),
7619 loc: Loc {
7620 line: tok.line,
7621 column: tok.column,
7622 },
7623 leading_trivia: Vec::new(),
7624 trailing_trivia: Vec::new(),
7625 };
7626 if self.check(TokenType::LBrace) {
7627 self.consume(TokenType::LBrace)?;
7628 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7629 let entry = self.current().clone();
7630 if entry.value != "column" {
7631 return Err(ParseError {
7632 message: format!(
7633 "Unknown entry `{}` in dataspace `{}`. A dataspace body \
7634 declares its columnar schema: `column <name>: <Type>` \
7635 (one per line, over the closed type catalog — \
7636 Text, Int, Float, Bool, Timestamp, Json).",
7637 entry.value, node.name
7638 ),
7639 line: entry.line,
7640 column: entry.column,
7641 ..Default::default()
7642 });
7643 }
7644 self.advance(); let col_tok = self.current().clone();
7646 let col_name = self.consume_any_ident_or_kw()?.value.clone();
7647 self.consume(TokenType::Colon)?;
7648 let declared_type = self.consume_any_ident_or_kw()?.value.clone();
7649 node.columns.push(crate::ast::DataspaceColumn {
7650 name: col_name,
7651 declared_type,
7652 loc: Loc {
7653 line: col_tok.line,
7654 column: col_tok.column,
7655 },
7656 });
7657 }
7658 self.consume(TokenType::RBrace)?;
7659 }
7660 Ok(node)
7661 }
7662
7663 fn parse_ots(&mut self) -> Result<OtsDefinition, ParseError> {
7664 let tok = self.consume(TokenType::Ots)?;
7665 let name = self.consume(TokenType::Identifier)?.value;
7666 let mut node = OtsDefinition {
7667 name,
7668 teleology: String::new(),
7669 homotopy_search: String::new(),
7670 loss_function: String::new(),
7671 loc: Loc {
7672 line: tok.line,
7673 column: tok.column,
7674 },
7675 leading_trivia: Vec::new(),
7676 trailing_trivia: Vec::new(),
7677 };
7678 if self.check(TokenType::Lt) {
7680 while !self.check(TokenType::Gt) && !self.check(TokenType::Eof) {
7681 self.advance();
7682 }
7683 if self.check(TokenType::Gt) {
7684 self.advance();
7685 }
7686 }
7687 self.consume(TokenType::LBrace)?;
7688 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7689 let field_name = self.current().value.clone();
7690 self.advance();
7691 if self.check(TokenType::Colon) {
7692 self.advance();
7693 match field_name.as_str() {
7694 "teleology" => {
7695 node.teleology = self.consume(TokenType::StringLit)?.value.clone()
7696 }
7697 "homotopy_search" => {
7698 node.homotopy_search = self.consume_any_ident_or_kw()?.value.clone()
7699 }
7700 "loss_function" => {
7706 node.loss_function = if self.check(TokenType::StringLit) {
7707 self.consume(TokenType::StringLit)?.value.clone()
7708 } else {
7709 self.consume_any_ident_or_kw()?.value.clone()
7710 }
7711 }
7712 _ => self.skip_value(),
7713 }
7714 } else if self.check(TokenType::LBrace) {
7715 self.skip_braced_block()?;
7716 }
7717 }
7718 self.consume(TokenType::RBrace)?;
7719 Ok(node)
7720 }
7721
7722 fn parse_mandate(&mut self) -> Result<MandateDefinition, ParseError> {
7723 let tok = self.consume(TokenType::Mandate)?;
7724 let name = self.consume(TokenType::Identifier)?.value;
7725 let mut node = MandateDefinition {
7726 name,
7727 constraint: String::new(),
7728 kp: None,
7729 ki: None,
7730 kd: None,
7731 tolerance: None,
7732 max_steps: None,
7733 drift_bound: None,
7734 lipschitz: None,
7735 on_violation: String::new(),
7736 loc: Loc {
7737 line: tok.line,
7738 column: tok.column,
7739 },
7740 leading_trivia: Vec::new(),
7741 trailing_trivia: Vec::new(),
7742 };
7743 self.consume(TokenType::LBrace)?;
7744 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7745 let field_name = self.current().value.clone();
7746 self.advance();
7747 if self.check(TokenType::Colon) {
7748 self.advance();
7749 match field_name.as_str() {
7750 "constraint" => {
7751 node.constraint = self.consume(TokenType::StringLit)?.value.clone()
7752 }
7753 "kp" | "Kp" => node.kp = self.parse_optional_float(),
7754 "ki" | "Ki" => node.ki = self.parse_optional_float(),
7755 "kd" | "Kd" => node.kd = self.parse_optional_float(),
7756 "max_steps" => node.max_steps = self.parse_optional_int(),
7757 "tolerance" | "epsilon" => node.tolerance = self.parse_optional_float(),
7762 "on_violation" => {
7763 node.on_violation = self.consume_any_ident_or_kw()?.value.clone()
7764 }
7765 _ => self.skip_value(),
7766 }
7767 } else if self.check(TokenType::LBrace) {
7768 if field_name == "pid" {
7778 self.parse_pid_block(&mut node)?;
7779 } else if field_name == "stability" {
7780 self.parse_stability_block(&mut node)?;
7781 } else {
7782 self.skip_braced_block()?;
7783 }
7784 }
7785 }
7786 self.consume(TokenType::RBrace)?;
7787 Ok(node)
7788 }
7789
7790 fn parse_pid_block(&mut self, node: &mut MandateDefinition) -> Result<(), ParseError> {
7813 self.consume(TokenType::LBrace)?;
7814 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7815 let key_token = self.current().clone();
7816 let key = key_token.value.clone();
7817 self.advance();
7818 if self.check(TokenType::Colon) {
7819 self.advance();
7820 match key.as_str() {
7821 "kp" | "Kp" => node.kp = self.parse_optional_float(),
7822 "ki" | "Ki" => node.ki = self.parse_optional_float(),
7823 "kd" | "Kd" => node.kd = self.parse_optional_float(),
7824 _ => {
7825 return Err(ParseError {
7826 message: format!(
7827 "`{key}` is not a gain of the PID controller. The block accepts \
7828 exactly `Kp`, `Ki` and `Kd` (lower-case spellings too). \
7829 Skipping what it does not recognise would let a typo drop a \
7830 gain, and the stability band is computed from all three."
7831 ),
7832 line: key_token.line,
7833 column: key_token.column,
7834 ..Default::default()
7835 });
7836 }
7837 }
7838 }
7839 if self.check(TokenType::Comma) {
7840 self.advance();
7841 }
7842 }
7843 self.consume(TokenType::RBrace)?;
7844 Ok(())
7845 }
7846
7847 fn parse_stability_block(
7863 &mut self,
7864 node: &mut MandateDefinition,
7865 ) -> Result<(), ParseError> {
7866 let open = self.consume(TokenType::LBrace)?;
7867 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7868 let key_token = self.current().clone();
7869 let key = key_token.value.clone();
7870 self.advance();
7871 if self.check(TokenType::Colon) {
7872 self.advance();
7873 match key.as_str() {
7874 "D" | "d" | "drift_bound" => {
7875 node.drift_bound = self.parse_optional_float()
7876 }
7877 "L" | "l" | "lipschitz" => node.lipschitz = self.parse_optional_float(),
7878 _ => {
7882 return Err(ParseError {
7883 message: format!(
7884 "`{key}` is not a hypothesis of the stability theorem. The block \
7885 accepts exactly `D` (the drift bound, also spelled `d` or \
7886 `drift_bound`) and `L` (the Lipschitz constant, also `l` or \
7887 `lipschitz`). This is an error rather than a skipped key \
7888 because a bound that fails to parse is a bound that is not \
7889 declared, and the compiler would then verify the band it can \
7890 see — the sign conditions — and admit the mandate as if the \
7891 rest had been checked."
7892 ),
7893 line: key_token.line,
7894 column: key_token.column,
7895 ..Default::default()
7896 });
7897 }
7898 }
7899 }
7900 if self.check(TokenType::Comma) {
7901 self.advance();
7902 }
7903 }
7904 self.consume(TokenType::RBrace)?;
7905 if node.drift_bound.is_none() && node.lipschitz.is_none() {
7906 return Err(ParseError {
7907 message: "the `stability { }` block declares neither `D` nor `L` — it asserts nothing and can discharge nothing. Declare the drift bound (`D:`), the Lipschitz constant (`L:`), or both; or remove the block."
7908 .to_string(),
7909 line: open.line,
7910 column: open.column,
7911 ..Default::default()
7912 });
7913 }
7914 Ok(())
7915 }
7916
7917 fn parse_compute(&mut self) -> Result<ComputeDefinition, ParseError> {
7946 let tok = self.consume(TokenType::Compute)?;
7947 let name = self.consume(TokenType::Identifier)?.value;
7948 let mut node = ComputeDefinition {
7949 name,
7950 shield_ref: String::new(),
7951 parameters: Vec::new(),
7952 return_type: String::new(),
7953 body: None,
7954 loc: Loc {
7955 line: tok.line,
7956 column: tok.column,
7957 },
7958 leading_trivia: Vec::new(),
7959 trailing_trivia: Vec::new(),
7960 };
7961
7962 if self.check(TokenType::LParen) {
7964 self.advance();
7965 while !self.check(TokenType::RParen) && !self.check(TokenType::Eof) {
7966 let ptok = self.current().clone();
7967 let pname = self.consume_any_ident_or_kw()?.value.clone();
7968 self.consume(TokenType::Colon)?;
7969 let ptype = self.parse_type_expr()?;
7970 node.parameters.push(Parameter {
7971 name: pname,
7972 type_expr: ptype,
7973 loc: self.loc_of(&ptok),
7974 });
7975 if self.check(TokenType::Comma) {
7976 self.advance();
7977 }
7978 }
7979 self.consume(TokenType::RParen)?;
7980 }
7981
7982 if self.check(TokenType::Arrow) {
7984 self.advance();
7985 node.return_type = self.consume_any_ident_or_kw()?.value.clone();
7986 }
7987
7988 self.consume(TokenType::LBrace)?;
7989 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
7990 let is_field = self
8000 .tokens
8001 .get(self.pos + 1)
8002 .map(|t| t.ttype == TokenType::Colon)
8003 .unwrap_or(false);
8004 if is_field {
8005 let field_tok = self.current().clone();
8006 let field_name = self.current().value.clone();
8007 self.advance();
8008 self.consume(TokenType::Colon)?;
8009 match field_name.as_str() {
8010 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
8011 "input" => self.parse_compute_input_list(&mut node)?,
8021 "output" => {
8024 node.return_type = self.parse_output_type_string()?;
8025 }
8026 _ => self.skip_value(),
8027 }
8028 let _ = field_tok;
8029 } else if self.current().value == "logic"
8030 && self
8031 .tokens
8032 .get(self.pos + 1)
8033 .is_some_and(|t| t.ttype == TokenType::LBrace)
8034 {
8035 if node.body.is_some() {
8040 return Err(ParseError {
8041 message: "compute declares two bodies; a pure function has one result, \
8042 and keeping the last silently would discard the first"
8043 .to_string(),
8044 line: self.current().line,
8045 column: self.current().column,
8046 ..Default::default()
8047 });
8048 }
8049 node.body = Some(self.parse_logic_block()?);
8050 } else {
8051 node.body = Some(self.parse_expr()?);
8052 }
8053 }
8054 self.consume(TokenType::RBrace)?;
8055 Ok(node)
8056 }
8057
8058 fn parse_compute_input_list(&mut self, node: &mut ComputeDefinition) -> Result<(), ParseError> {
8064 loop {
8065 let ptok = self.current().clone();
8066 let pname = self.consume_any_ident_or_kw()?.value.clone();
8067 let type_expr = if self.check(TokenType::LParen) {
8071 self.advance();
8072 let t = self.parse_type_expr()?;
8073 self.consume(TokenType::RParen)?;
8074 t
8075 } else {
8076 TypeExpr {
8077 name: String::new(),
8078 generic_param: String::new(),
8079 optional: false,
8080 loc: self.loc_of(&ptok),
8081 }
8082 };
8083 node.parameters.push(Parameter {
8084 name: pname,
8085 type_expr,
8086 loc: self.loc_of(&ptok),
8087 });
8088 if self.check(TokenType::Comma) {
8089 self.advance();
8090 } else {
8091 break;
8092 }
8093 }
8094 Ok(())
8095 }
8096
8097 fn parse_logic_block(&mut self) -> Result<Expr, ParseError> {
8110 let open = self.current().clone();
8111 self.advance(); self.consume(TokenType::LBrace)?;
8113
8114 let mut bindings: Vec<(String, Expr)> = Vec::new();
8115 let mut result: Option<Expr> = None;
8116
8117 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8118 if self.check(TokenType::Let) {
8119 if result.is_some() {
8120 return Err(ParseError {
8121 message: "a `let` after the `return` in a `logic { }` block is \
8122 unreachable — the block's value is already decided. Move it \
8123 above the `return`."
8124 .to_string(),
8125 line: self.current().line,
8126 column: self.current().column,
8127 ..Default::default()
8128 });
8129 }
8130 self.advance(); let name = self.consume_any_ident_or_kw()?.value.clone();
8132 self.consume(TokenType::Assign)?;
8133 bindings.push((name, self.parse_expr()?));
8134 } else if self.check(TokenType::Return) {
8135 self.advance();
8136 result = Some(self.parse_expr()?);
8137 } else {
8138 let bad = self.current().clone();
8139 return Err(ParseError {
8140 message: format!(
8141 "unexpected `{}` in a `logic {{ }}` block — it admits only `let <name> = \
8142 <expr>` bindings and a closing `return <expr>`. `compute` is a PURE \
8143 function (its own paper: \"pureza categórica de los morfismos \
8144 funcionales\"), so a statement that could have an effect is refused \
8145 rather than parsed and dropped.",
8146 bad.value
8147 ),
8148 line: bad.line,
8149 column: bad.column,
8150 ..Default::default()
8151 });
8152 }
8153 }
8154 self.consume(TokenType::RBrace)?;
8155
8156 let mut expr = result.ok_or_else(|| ParseError {
8157 message: "a `logic { }` block must end in `return <expr>`. Without it the block binds \
8158 names and yields nothing, and the compute would have to invent a result — \
8159 which is precisely what a deterministic primitive must never do."
8160 .to_string(),
8161 line: open.line,
8162 column: open.column,
8163 ..Default::default()
8164 })?;
8165
8166 for (name, value) in bindings.into_iter().rev() {
8168 expr = Expr::Let {
8169 name,
8170 value: Box::new(value),
8171 body: Box::new(expr),
8172 };
8173 }
8174 Ok(expr)
8175 }
8176
8177 fn parse_daemon(&mut self) -> Result<DaemonDefinition, ParseError> {
8178 let tok = self.consume(TokenType::Daemon)?;
8179 let name = self.consume(TokenType::Identifier)?.value;
8180 let mut node = DaemonDefinition {
8181 name,
8182 goal: String::new(),
8183 tools: Vec::new(),
8184 memory_ref: String::new(),
8185 strategy: String::new(),
8186 on_stuck: String::new(),
8187 shield_ref: String::new(),
8188 window_ref: String::new(),
8189 budget: None,
8190 max_tokens: None,
8191 max_time: String::new(),
8192 max_cost: None,
8193 listeners: Vec::new(),
8194 requires_capabilities: Vec::new(),
8195 loc: Loc {
8196 line: tok.line,
8197 column: tok.column,
8198 },
8199 leading_trivia: Vec::new(),
8200 trailing_trivia: Vec::new(),
8201 };
8202 while !self.check(TokenType::LBrace) && !self.check(TokenType::Eof) {
8204 self.advance();
8205 }
8206 self.consume(TokenType::LBrace)?;
8207 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8208 let field = self.current().clone();
8209 let field_name = field.value.clone();
8210 self.advance();
8211 if self.check(TokenType::Colon) {
8212 self.advance();
8213 match field_name.as_str() {
8214 "goal" => node.goal = self.consume(TokenType::StringLit)?.value.clone(),
8215 "tools" => node.tools = self.parse_bracketed_identifiers()?,
8216 "memory" => node.memory_ref = self.consume_any_ident_or_kw()?.value.clone(),
8217 "strategy" => node.strategy = self.consume_any_ident_or_kw()?.value.clone(),
8218 "on_stuck" => node.on_stuck = self.consume_any_ident_or_kw()?.value.clone(),
8219 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
8220 "window" => node.window_ref = self.consume_any_ident_or_kw()?.value.clone(),
8222 "max_tokens" => node.max_tokens = self.parse_optional_int(),
8223 "max_time" => node.max_time = self.consume_any_ident_or_kw()?.value.clone(),
8224 "max_cost" => node.max_cost = self.parse_optional_float(),
8225 "requires" => {
8230 let bracket_tok = self.current().clone();
8231 let items = self.parse_bracketed_dot_identifiers()?;
8232 for slug in &items {
8233 if !is_valid_capability_slug(slug) {
8234 return Err(ParseError {
8235 message: format!(
8236 "Invalid capability slug '{slug}' in daemon '{}' \
8237 `requires:`. Capability slugs must match \
8238 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
8239 lowercase identifiers. Examples: `daemon.run`, \
8240 `memory.write`, `flow.execute`.",
8241 node.name
8242 ),
8243 line: bracket_tok.line,
8244 column: bracket_tok.column,
8245 ..Default::default()
8246 });
8247 }
8248 }
8249 node.requires_capabilities = items;
8250 }
8251 _ => self.skip_value(),
8252 }
8253 } else if field.ttype == TokenType::Listen {
8254 let (channel, channel_is_ref) = if self.check(TokenType::StringLit) {
8260 (self.consume(TokenType::StringLit)?.value.clone(), false)
8261 } else {
8262 (self.consume_any_ident_or_kw()?.value.clone(), true)
8263 };
8264 let mut alias = String::new();
8265 if !self.at_declaration_start()
8266 && !self.check(TokenType::RBrace)
8267 && !self.check(TokenType::LBrace)
8268 {
8269 let next = self.current().clone();
8270 if next.value == "as" || next.ttype == TokenType::As {
8271 self.advance();
8272 alias = self.consume_any_ident_or_kw()?.value.clone();
8273 }
8274 }
8275 let listen_loc = Loc {
8276 line: field.line,
8277 column: field.column,
8278 };
8279 let body = self.parse_listener_body()?;
8283 node.listeners.push(ListenStep {
8284 channel,
8285 channel_is_ref,
8286 event_alias: alias,
8287 body,
8288 loc: listen_loc,
8289 });
8290 } else if field_name == "budget" && self.check(TokenType::LBrace) {
8291 node.budget = Some(self.parse_budget_block(field.line, field.column)?);
8293 } else if self.check(TokenType::LBrace) {
8294 self.skip_braced_block()?;
8295 }
8296 }
8297 self.consume(TokenType::RBrace)?;
8298 Ok(node)
8299 }
8300
8301 fn parse_top_level_budget(&mut self) -> Result<BudgetBlock, ParseError> {
8310 let kw = self.consume(TokenType::Budget)?; let name = self.consume(TokenType::Identifier)?.value;
8312 let mut block = self.parse_budget_block(kw.line, kw.column)?;
8313 block.name = name;
8314 Ok(block)
8315 }
8316
8317 fn parse_budget_block(&mut self, line: u32, column: u32) -> Result<BudgetBlock, ParseError> {
8319 self.consume(TokenType::LBrace)?;
8320 let mut quotas = Vec::new();
8321 let mut on_exhausted = String::new();
8322 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8323 let field = self.current().clone();
8324 let field_name = self.consume_any_ident_or_kw()?.value;
8325 match field_name.as_str() {
8326 "rate" | "max" => {
8327 quotas.push(self.parse_budget_quota(field_name, field.line, field.column)?);
8328 }
8329 "on_exhausted" => {
8330 self.consume(TokenType::Colon)?;
8331 on_exhausted = self.consume_any_ident_or_kw()?.value;
8332 }
8333 _ => self.skip_value(),
8334 }
8335 }
8336 self.consume(TokenType::RBrace)?;
8337 Ok(BudgetBlock {
8338 name: String::new(),
8339 quotas,
8340 on_exhausted,
8341 loc: Loc { line, column },
8342 leading_trivia: Vec::new(),
8343 trailing_trivia: Vec::new(),
8344 })
8345 }
8346
8347 fn parse_budget_quota(
8350 &mut self,
8351 kind: String,
8352 line: u32,
8353 column: u32,
8354 ) -> Result<BudgetQuota, ParseError> {
8355 self.consume(TokenType::Colon)?;
8356 let limit = self.consume_number()? as i64;
8357 let _per = self.consume_any_ident_or_kw()?; let period = self.consume_any_ident_or_kw()?.value;
8360 let _on = self.consume_any_ident_or_kw()?; let _tool = self.consume_any_ident_or_kw()?; self.consume(TokenType::LParen)?;
8364 let effect = self.consume_any_ident_or_kw()?.value;
8365 self.consume(TokenType::RParen)?;
8366 Ok(BudgetQuota {
8367 kind,
8368 limit,
8369 period,
8370 effect,
8371 loc: Loc { line, column },
8372 })
8373 }
8374
8375 fn parse_axonstore(&mut self) -> Result<AxonStoreDefinition, ParseError> {
8376 let tok = self.consume(TokenType::AxonStore)?;
8377 let name = self.consume(TokenType::Identifier)?.value;
8378 let mut node = AxonStoreDefinition {
8379 name,
8380 backend: String::new(),
8381 connection: String::new(),
8382 resource_ref: String::new(),
8383 confidence_floor: None,
8384 isolation: String::new(),
8385 on_breach: String::new(),
8386 capability: String::new(),
8387 class: String::new(),
8388 column_schema: None,
8389 loc: Loc {
8390 line: tok.line,
8391 column: tok.column,
8392 },
8393 leading_trivia: Vec::new(),
8394 trailing_trivia: Vec::new(),
8395 };
8396 self.consume(TokenType::LBrace)?;
8397 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8398 let field = self.current().clone();
8399 let field_name = field.value.clone();
8400 if field.ttype == TokenType::Schema {
8406 self.advance();
8407 let parsed = self.parse_store_schema_declaration(&node.name, field.line, field.column)?;
8408 node.column_schema = Some(parsed);
8409 continue;
8410 }
8411 self.advance();
8412 if self.check(TokenType::Colon) {
8413 self.advance();
8414 match field_name.as_str() {
8415 "backend" => node.backend = self.consume_any_ident_or_kw()?.value.clone(),
8416 "class" => node.class = self.parse_dotted_identifier()?,
8424 "connection" => node.connection = self.parse_config_key()?,
8425 "resource" => {
8431 node.resource_ref = self.consume_any_ident_or_kw()?.value.clone()
8432 }
8433 "confidence_floor" => node.confidence_floor = self.parse_optional_float(),
8434 "isolation" => node.isolation = self.consume_any_ident_or_kw()?.value.clone(),
8435 "on_breach" => node.on_breach = self.consume_any_ident_or_kw()?.value.clone(),
8436 "capability" => {
8440 let slug_tok = self.consume(TokenType::StringLit)?.clone();
8441 if !is_valid_capability_slug(&slug_tok.value) {
8442 return Err(ParseError {
8443 message: format!(
8444 "Invalid capability slug '{}' in axonstore '{}' \
8445 `capability:`. Capability slugs must match \
8446 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
8447 lowercase identifiers starting with a letter. Examples: \
8448 `admin`, `tenant.read`, `hipaa.phi.read`.",
8449 slug_tok.value, node.name
8450 ),
8451 line: slug_tok.line,
8452 column: slug_tok.column,
8453 ..Default::default()
8454 });
8455 }
8456 node.capability = slug_tok.value.clone();
8457 }
8458 _ => self.skip_value(),
8459 }
8460 } else if self.check(TokenType::LBrace) {
8461 self.skip_braced_block()?;
8462 }
8463 }
8464 self.consume(TokenType::RBrace)?;
8465 Ok(node)
8466 }
8467
8468 fn parse_store_schema_declaration(
8480 &mut self,
8481 store_name: &str,
8482 sch_line: u32,
8483 sch_col: u32,
8484 ) -> Result<crate::store_schema::StoreColumnSchema, ParseError> {
8485 use crate::store_schema::{StoreColumn, StoreColumnSchema, StoreColumnType};
8486
8487 if self.check(TokenType::LBrace) {
8489 self.consume(TokenType::LBrace)?;
8490 let mut columns: Vec<StoreColumn> = Vec::new();
8491 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8492 let col_tok = self.current().clone();
8493 let col_name = self.consume_any_ident_or_kw()?.value.clone();
8494 self.consume(TokenType::Colon)?;
8495 let type_tok = self.consume_any_ident_or_kw()?.clone();
8496 let col_type = StoreColumnType::from_token(&type_tok.value).ok_or_else(|| {
8497 let names = StoreColumnType::all_canonical_names();
8498 let suggestion =
8499 crate::smart_suggest::suggest_for(&type_tok.value, &names);
8500 let suggest_suffix = if suggestion.is_empty() {
8501 String::new()
8502 } else {
8503 format!(" {suggestion}")
8504 };
8505 let known = names.join(", ");
8506 ParseError {
8507 message: format!(
8508 "Unknown column type `{}` for column `{}` in \
8509 axonstore `{}` `schema:` block. The closed \
8510 v1.38.0 column-type catalog (Fase 38.b D1) \
8511 is {{{known}}} (plus common lowercase \
8512 aliases — `int`/`integer`/`int4` for \
8513 `Int`, `bool`/`boolean` for `Bool`, etc.).\
8514 {suggest_suffix}",
8515 type_tok.value, col_name, store_name
8516 ),
8517 line: type_tok.line,
8518 column: type_tok.column,
8519 ..Default::default()
8520 }
8521 })?;
8522
8523 let mut json_shape: Option<String> = None;
8533 if self.check(TokenType::Lt) {
8534 self.advance();
8535 let shape_tok = self.consume_any_ident_or_kw()?.clone();
8536 self.consume(TokenType::Gt)?;
8537 if matches!(col_type, StoreColumnType::Json | StoreColumnType::Jsonb) {
8538 json_shape = Some(shape_tok.value.clone());
8539 } else {
8540 return Err(ParseError {
8541 message: format!(
8542 "axon-T841 a shape lens `<{shape}>` may refine \
8543 only a `Json` / `Jsonb` column, but column \
8544 `{col}` in axonstore `{store}` is `{ty}`. Drop \
8545 the `<{shape}>` (a rigid column already has a \
8546 fixed shape), or change the column type to \
8547 `Json<{shape}>` if it carries open documents.",
8548 shape = shape_tok.value,
8549 col = col_name,
8550 store = store_name,
8551 ty = col_type.canonical_name(),
8552 ),
8553 line: shape_tok.line,
8554 column: shape_tok.column,
8555 ..Default::default()
8556 });
8557 }
8558 }
8559
8560 let mut col = StoreColumn {
8561 name: col_name,
8562 col_type,
8563 json_shape,
8564 primary_key: false,
8565 auto_increment: false,
8566 not_null: false,
8567 unique: false,
8568 indexed: false,
8569 default_value: String::new(),
8570 identity: false,
8575 line: col_tok.line,
8576 column: col_tok.column,
8577 };
8578
8579 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8582 if self.current().ttype != TokenType::Identifier {
8583 break;
8586 }
8587 let constraint = self.current().value.clone();
8588 match constraint.as_str() {
8589 "primary_key" => {
8590 col.primary_key = true;
8591 self.advance();
8592 }
8593 "auto_increment" => {
8594 col.auto_increment = true;
8595 self.advance();
8596 }
8597 "not_null" => {
8598 col.not_null = true;
8599 self.advance();
8600 }
8601 "unique" => {
8602 col.unique = true;
8603 self.advance();
8604 }
8605 "index" => {
8611 col.indexed = true;
8612 self.advance();
8613 }
8614 "identity" => {
8625 col.identity = true;
8626 self.advance();
8627 }
8628 "default" => {
8629 self.advance();
8630 let dv = self.current().clone();
8631 if matches!(
8632 dv.ttype,
8633 TokenType::StringLit
8634 | TokenType::Integer
8635 | TokenType::Float
8636 ) {
8637 col.default_value = dv.value.clone();
8638 self.advance();
8639 } else {
8640 col.default_value =
8641 self.consume_any_ident_or_kw()?.value.clone();
8642 }
8643 }
8644 _ => break,
8645 }
8646 }
8647
8648 columns.push(col);
8649 }
8650 self.consume(TokenType::RBrace)?;
8651 return Ok(StoreColumnSchema::Inline {
8652 columns,
8653 leading_trivia: Vec::new(),
8654 line: sch_line,
8655 column: sch_col,
8656 });
8657 }
8658
8659 if !self.check(TokenType::Colon) {
8661 let cur = self.current().clone();
8662 return Err(ParseError {
8663 message: format!(
8664 "axonstore `{store_name}` `schema:` declaration expects \
8665 `{{ … }}` (inline columns), `: \"manifest.ref\"` \
8666 (manifest reference), or `: env:VAR` (per-tenant schema \
8667 namespace). Got `{}` instead.",
8668 cur.value
8669 ),
8670 line: cur.line,
8671 column: cur.column,
8672 ..Default::default()
8673 });
8674 }
8675 self.consume(TokenType::Colon)?;
8676
8677 if self.check(TokenType::StringLit) {
8679 let lit = self.consume(TokenType::StringLit)?.clone();
8680 let value = lit.value.clone();
8681 if let Some(var) = value.strip_prefix("env:") {
8682 let var = var.trim();
8683 if var.is_empty() {
8684 return Err(ParseError {
8685 message: format!(
8686 "axonstore `{store_name}` `schema: \"env:\"` is \
8687 missing the variable name after the `env:` \
8688 prefix."
8689 ),
8690 line: lit.line,
8691 column: lit.column,
8692 ..Default::default()
8693 });
8694 }
8695 return Ok(StoreColumnSchema::EnvVar {
8696 var_name: var.to_string(),
8697 line: sch_line,
8698 column: sch_col,
8699 });
8700 }
8701 if value.trim().is_empty() {
8703 return Err(ParseError {
8704 message: format!(
8705 "axonstore `{store_name}` `schema:` manifest reference \
8706 is empty. Expected `\"qualified.name\"` — e.g. \
8707 `\"public.tenants\"`."
8708 ),
8709 line: lit.line,
8710 column: lit.column,
8711 ..Default::default()
8712 });
8713 }
8714 return Ok(StoreColumnSchema::ManifestRef {
8715 qualified_name: value,
8716 line: sch_line,
8717 column: sch_col,
8718 });
8719 }
8720
8721 let env_tok = self.current().clone();
8724 if env_tok.value == "env" {
8725 self.advance();
8726 if !self.check(TokenType::Colon) {
8727 return Err(ParseError {
8728 message: format!(
8729 "axonstore `{store_name}` `schema: env` is missing the \
8730 `:` separator. Expected `schema: env:VAR`."
8731 ),
8732 line: env_tok.line,
8733 column: env_tok.column,
8734 ..Default::default()
8735 });
8736 }
8737 self.advance(); let var_tok = self.consume_any_ident_or_kw()?.clone();
8739 if var_tok.value.trim().is_empty() {
8740 return Err(ParseError {
8741 message: format!(
8742 "axonstore `{store_name}` `schema: env:` is missing \
8743 the variable name."
8744 ),
8745 line: var_tok.line,
8746 column: var_tok.column,
8747 ..Default::default()
8748 });
8749 }
8750 return Ok(StoreColumnSchema::EnvVar {
8751 var_name: var_tok.value.clone(),
8752 line: sch_line,
8753 column: sch_col,
8754 });
8755 }
8756
8757 Err(ParseError {
8758 message: format!(
8759 "axonstore `{store_name}` `schema:` declaration expects \
8760 `{{ … }}` (inline columns), `\"manifest.ref\"` (manifest \
8761 reference), or `env:VAR` (per-tenant schema namespace). \
8762 Got `{}` instead.",
8763 env_tok.value
8764 ),
8765 line: env_tok.line,
8766 column: env_tok.column,
8767 ..Default::default()
8768 })
8769 }
8770
8771 fn parse_resource(&mut self) -> Result<ResourceDefinition, ParseError> {
8778 let tok = self.consume(TokenType::Resource)?;
8779 let name = self.consume(TokenType::Identifier)?.value;
8780 let mut node = ResourceDefinition {
8781 name,
8782 kind: String::new(),
8783 endpoint: String::new(),
8784 capacity: None,
8785 lifetime: "affine".to_string(),
8786 certainty_floor: None,
8787 shield_ref: String::new(),
8788 within: String::new(),
8789 loc: Loc {
8790 line: tok.line,
8791 column: tok.column,
8792 },
8793 leading_trivia: Vec::new(),
8794 trailing_trivia: Vec::new(),
8795 };
8796 self.consume(TokenType::LBrace)?;
8797 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8798 let field_tok = self.current().clone();
8799 let field_name = field_tok.value.clone();
8800 self.advance();
8801 if !self.check(TokenType::Colon) {
8802 if self.check(TokenType::LBrace) {
8804 self.skip_braced_block()?;
8805 }
8806 continue;
8807 }
8808 self.advance(); match field_name.as_str() {
8810 "kind" => node.kind = self.consume_any_ident_or_kw()?.value,
8811 "endpoint" => {
8824 node.endpoint = if self.check(TokenType::StringLit) {
8825 self.consume(TokenType::StringLit)?.value
8826 } else {
8827 self.parse_dotted_identifier()?
8828 };
8829 }
8830 "capacity" => {
8831 node.capacity = self.parse_optional_int();
8832 }
8833 "lifetime" => {
8834 let lt_tok = self.consume_any_ident_or_kw()?;
8835 let lt = lt_tok.value;
8836 if !matches!(lt.as_str(), "linear" | "affine" | "persistent") {
8837 return Err(ParseError {
8838 message: format!(
8839 "Invalid lifetime '{lt}' in resource '{}' — \
8840 expected linear | affine | persistent",
8841 node.name
8842 ),
8843 line: lt_tok.line,
8844 column: lt_tok.column,
8845 ..Default::default()
8846 });
8847 }
8848 node.lifetime = lt;
8849 }
8850 "certainty_floor" => {
8851 node.certainty_floor = self.parse_optional_float();
8852 }
8853 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
8854 "within" => node.within = self.consume_any_ident_or_kw()?.value,
8858 unknown => {
8868 return Err(ParseError {
8869 message: format!(
8870 "Unknown field '{unknown}' in resource '{}' — expected one of: \
8871 kind, endpoint, capacity, lifetime, certainty_floor, shield, within",
8872 node.name
8873 ),
8874 line: field_tok.line,
8875 column: field_tok.column,
8876 ..Default::default()
8877 });
8878 }
8879 }
8880 }
8881 self.consume(TokenType::RBrace)?;
8882 Ok(node)
8883 }
8884
8885 fn parse_fabric(&mut self) -> Result<FabricDefinition, ParseError> {
8887 let tok = self.consume(TokenType::Fabric)?;
8888 let name = self.consume(TokenType::Identifier)?.value;
8889 let mut node = FabricDefinition {
8890 name,
8891 provider: String::new(),
8892 region: String::new(),
8893 zones: None,
8894 ephemeral: None,
8895 shield_ref: String::new(),
8896 loc: Loc {
8897 line: tok.line,
8898 column: tok.column,
8899 },
8900 leading_trivia: Vec::new(),
8901 trailing_trivia: Vec::new(),
8902 };
8903 self.consume(TokenType::LBrace)?;
8904 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8905 let field_name = self.current().value.clone();
8906 self.advance();
8907 if !self.check(TokenType::Colon) {
8908 if self.check(TokenType::LBrace) {
8909 self.skip_braced_block()?;
8910 }
8911 continue;
8912 }
8913 self.advance(); match field_name.as_str() {
8915 "provider" => node.provider = self.consume_any_ident_or_kw()?.value,
8916 "region" => node.region = self.consume(TokenType::StringLit)?.value,
8917 "zones" => node.zones = self.parse_optional_int(),
8918 "ephemeral" => {
8919 let b = self.parse_bool()?;
8920 node.ephemeral = Some(b);
8921 }
8922 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
8923 _ => self.skip_value(),
8924 }
8925 }
8926 self.consume(TokenType::RBrace)?;
8927 Ok(node)
8928 }
8929
8930 fn parse_manifest(&mut self) -> Result<ManifestDefinition, ParseError> {
8932 let tok = self.consume(TokenType::Manifest)?;
8933 let name = self.consume(TokenType::Identifier)?.value;
8934 let mut node = ManifestDefinition {
8935 name,
8936 resources: Vec::new(),
8937 fabric_ref: String::new(),
8938 region: String::new(),
8939 zones: None,
8940 compliance: Vec::new(),
8941 loc: Loc {
8942 line: tok.line,
8943 column: tok.column,
8944 },
8945 leading_trivia: Vec::new(),
8946 trailing_trivia: Vec::new(),
8947 };
8948 self.consume(TokenType::LBrace)?;
8949 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8950 let field_name = self.current().value.clone();
8951 self.advance();
8952 if !self.check(TokenType::Colon) {
8953 if self.check(TokenType::LBrace) {
8954 self.skip_braced_block()?;
8955 }
8956 continue;
8957 }
8958 self.advance();
8959 match field_name.as_str() {
8960 "resources" => node.resources = self.parse_bracketed_identifiers()?,
8961 "fabric" => node.fabric_ref = self.consume_any_ident_or_kw()?.value,
8962 "region" => node.region = self.consume(TokenType::StringLit)?.value,
8963 "zones" => node.zones = self.parse_optional_int(),
8964 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
8965 _ => self.skip_value(),
8966 }
8967 }
8968 self.consume(TokenType::RBrace)?;
8969 Ok(node)
8970 }
8971
8972 fn parse_observe(&mut self) -> Result<ObserveDefinition, ParseError> {
8974 let tok = self.consume(TokenType::Observe)?;
8975 let name = self.consume(TokenType::Identifier)?.value;
8976 self.consume(TokenType::From)?;
8978 let target = self.consume(TokenType::Identifier)?.value;
8979 let mut node = ObserveDefinition {
8980 name,
8981 target,
8982 sources: Vec::new(),
8983 quorum: None,
8984 timeout: String::new(),
8985 on_partition: "fail".to_string(),
8986 certainty_floor: None,
8987 loc: Loc {
8988 line: tok.line,
8989 column: tok.column,
8990 },
8991 leading_trivia: Vec::new(),
8992 trailing_trivia: Vec::new(),
8993 };
8994 self.consume(TokenType::LBrace)?;
8995 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
8996 let field_name = self.current().value.clone();
8997 self.advance();
8998 if !self.check(TokenType::Colon) {
8999 if self.check(TokenType::LBrace) {
9000 self.skip_braced_block()?;
9001 }
9002 continue;
9003 }
9004 self.advance();
9005 match field_name.as_str() {
9006 "sources" => node.sources = self.parse_bracketed_identifiers()?,
9007 "quorum" => node.quorum = self.parse_optional_int(),
9008 "timeout" => {
9009 let t = self.current().clone();
9010 match t.ttype {
9011 TokenType::Duration | TokenType::StringLit => {
9012 self.advance();
9013 node.timeout = t.value;
9014 }
9015 _ => node.timeout = self.consume_any_ident_or_kw()?.value,
9016 }
9017 }
9018 "on_partition" => {
9019 let p_tok = self.consume_any_ident_or_kw()?;
9020 let p = p_tok.value;
9021 if !matches!(p.as_str(), "fail" | "shield_quarantine") {
9022 return Err(ParseError {
9023 message: format!(
9024 "Invalid on_partition '{p}' in observe '{}' — \
9025 expected fail | shield_quarantine",
9026 node.name
9027 ),
9028 line: p_tok.line,
9029 column: p_tok.column,
9030 ..Default::default()
9031 });
9032 }
9033 node.on_partition = p;
9034 }
9035 "certainty_floor" => node.certainty_floor = self.parse_optional_float(),
9036 _ => self.skip_value(),
9037 }
9038 }
9039 self.consume(TokenType::RBrace)?;
9040 Ok(node)
9041 }
9042
9043 fn parse_reconcile(&mut self) -> Result<ReconcileDefinition, ParseError> {
9047 let tok = self.consume(TokenType::Reconcile)?;
9048 let name = self.consume(TokenType::Identifier)?.value;
9049 let mut node = ReconcileDefinition {
9050 name,
9051 observe_ref: String::new(),
9052 threshold: None,
9053 tolerance: None,
9054 on_drift: "provision".to_string(),
9055 shield_ref: String::new(),
9056 mandate_ref: String::new(),
9057 max_retries: 3,
9058 loc: Loc {
9059 line: tok.line,
9060 column: tok.column,
9061 },
9062 leading_trivia: Vec::new(),
9063 trailing_trivia: Vec::new(),
9064 };
9065 self.consume(TokenType::LBrace)?;
9066 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9067 let field_name = self.current().value.clone();
9068 self.advance();
9069 if !self.check(TokenType::Colon) {
9070 if self.check(TokenType::LBrace) {
9071 self.skip_braced_block()?;
9072 }
9073 continue;
9074 }
9075 self.advance();
9076 match field_name.as_str() {
9077 "observe" => node.observe_ref = self.consume_any_ident_or_kw()?.value,
9078 "threshold" => node.threshold = self.parse_optional_float(),
9079 "tolerance" => node.tolerance = self.parse_optional_float(),
9080 "on_drift" => {
9081 let d_tok = self.consume_any_ident_or_kw()?;
9082 let d = d_tok.value;
9083 if !matches!(d.as_str(), "provision" | "alert" | "refine") {
9084 return Err(ParseError {
9085 message: format!(
9086 "Invalid on_drift '{d}' in reconcile '{}' — \
9087 expected provision | alert | refine",
9088 node.name
9089 ),
9090 line: d_tok.line,
9091 column: d_tok.column,
9092 ..Default::default()
9093 });
9094 }
9095 node.on_drift = d;
9096 }
9097 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
9098 "mandate" => node.mandate_ref = self.consume_any_ident_or_kw()?.value,
9099 "max_retries" => {
9100 if let Some(v) = self.parse_optional_int() {
9101 node.max_retries = v;
9102 }
9103 }
9104 _ => self.skip_value(),
9105 }
9106 }
9107 self.consume(TokenType::RBrace)?;
9108 Ok(node)
9109 }
9110
9111 fn parse_lease(&mut self) -> Result<LeaseDefinition, ParseError> {
9113 let tok = self.consume(TokenType::Lease)?;
9114 let name = self.consume(TokenType::Identifier)?.value;
9115 let mut node = LeaseDefinition {
9116 name,
9117 resource_ref: String::new(),
9118 duration: String::new(),
9119 acquire: "on_start".to_string(),
9120 on_expire: "anchor_breach".to_string(),
9121 loc: Loc {
9122 line: tok.line,
9123 column: tok.column,
9124 },
9125 leading_trivia: Vec::new(),
9126 trailing_trivia: Vec::new(),
9127 };
9128 self.consume(TokenType::LBrace)?;
9129 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9130 let field_name = self.current().value.clone();
9131 self.advance();
9132 if !self.check(TokenType::Colon) {
9133 if self.check(TokenType::LBrace) {
9134 self.skip_braced_block()?;
9135 }
9136 continue;
9137 }
9138 self.advance();
9139 match field_name.as_str() {
9140 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
9141 "duration" => {
9142 let t = self.current().clone();
9143 match t.ttype {
9144 TokenType::Duration | TokenType::StringLit => {
9145 self.advance();
9146 node.duration = t.value;
9147 }
9148 _ => node.duration = self.consume_any_ident_or_kw()?.value,
9149 }
9150 }
9151 "acquire" => {
9152 let a_tok = self.consume_any_ident_or_kw()?;
9153 let a = a_tok.value;
9154 if !matches!(a.as_str(), "on_start" | "on_demand") {
9155 return Err(ParseError {
9156 message: format!(
9157 "Invalid acquire '{a}' in lease '{}' — \
9158 expected on_start | on_demand",
9159 node.name
9160 ),
9161 line: a_tok.line,
9162 column: a_tok.column,
9163 ..Default::default()
9164 });
9165 }
9166 node.acquire = a;
9167 }
9168 "on_expire" => {
9169 let e_tok = self.consume_any_ident_or_kw()?;
9170 let e = e_tok.value;
9171 if !matches!(e.as_str(), "anchor_breach" | "release" | "extend") {
9172 return Err(ParseError {
9173 message: format!(
9174 "Invalid on_expire '{e}' in lease '{}' — \
9175 expected anchor_breach | release | extend",
9176 node.name
9177 ),
9178 line: e_tok.line,
9179 column: e_tok.column,
9180 ..Default::default()
9181 });
9182 }
9183 node.on_expire = e;
9184 }
9185 _ => self.skip_value(),
9186 }
9187 }
9188 self.consume(TokenType::RBrace)?;
9189 Ok(node)
9190 }
9191
9192 fn parse_ensemble(&mut self) -> Result<EnsembleDefinition, ParseError> {
9194 let tok = self.consume(TokenType::Ensemble)?;
9195 let name = self.consume(TokenType::Identifier)?.value;
9196 let mut node = EnsembleDefinition {
9197 name,
9198 observations: Vec::new(),
9199 quorum: None,
9200 aggregation: "majority".to_string(),
9201 certainty_mode: "min".to_string(),
9202 loc: Loc {
9203 line: tok.line,
9204 column: tok.column,
9205 },
9206 leading_trivia: Vec::new(),
9207 trailing_trivia: Vec::new(),
9208 };
9209 self.consume(TokenType::LBrace)?;
9210 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9211 let field_name = self.current().value.clone();
9212 self.advance();
9213 if !self.check(TokenType::Colon) {
9214 if self.check(TokenType::LBrace) {
9215 self.skip_braced_block()?;
9216 }
9217 continue;
9218 }
9219 self.advance();
9220 match field_name.as_str() {
9221 "observations" => node.observations = self.parse_bracketed_identifiers()?,
9222 "quorum" => node.quorum = self.parse_optional_int(),
9223 "aggregation" => {
9224 let a_tok = self.consume_any_ident_or_kw()?;
9225 let a = a_tok.value;
9226 if !matches!(a.as_str(), "majority" | "weighted" | "byzantine") {
9227 return Err(ParseError {
9228 message: format!(
9229 "Invalid aggregation '{a}' in ensemble '{}' — \
9230 expected majority | weighted | byzantine",
9231 node.name
9232 ),
9233 line: a_tok.line,
9234 column: a_tok.column,
9235 ..Default::default()
9236 });
9237 }
9238 node.aggregation = a;
9239 }
9240 "certainty_mode" => {
9241 let c_tok = self.consume_any_ident_or_kw()?;
9242 let c = c_tok.value;
9243 if !matches!(c.as_str(), "min" | "weighted" | "harmonic") {
9244 return Err(ParseError {
9245 message: format!(
9246 "Invalid certainty_mode '{c}' in ensemble '{}' — \
9247 expected min | weighted | harmonic",
9248 node.name
9249 ),
9250 line: c_tok.line,
9251 column: c_tok.column,
9252 ..Default::default()
9253 });
9254 }
9255 node.certainty_mode = c;
9256 }
9257 _ => self.skip_value(),
9258 }
9259 }
9260 self.consume(TokenType::RBrace)?;
9261 Ok(node)
9262 }
9263
9264 fn parse_session_definition(&mut self) -> Result<SessionDefinition, ParseError> {
9272 let tok = self.consume(TokenType::Session)?;
9273 let name = self.consume(TokenType::Identifier)?.value;
9274 let mut node = SessionDefinition {
9275 name,
9276 roles: Vec::new(),
9277 loc: Loc {
9278 line: tok.line,
9279 column: tok.column,
9280 },
9281 leading_trivia: Vec::new(),
9282 trailing_trivia: Vec::new(),
9283 };
9284 self.consume(TokenType::LBrace)?;
9285 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9286 let role_tok = self.consume_any_ident_or_kw()?;
9287 self.consume(TokenType::Colon)?;
9288 let steps = self.parse_session_steps()?;
9289 node.roles.push(SessionRole {
9290 name: role_tok.value,
9291 steps,
9292 loc: Loc {
9293 line: role_tok.line,
9294 column: role_tok.column,
9295 },
9296 });
9297 }
9298 self.consume(TokenType::RBrace)?;
9299 Ok(node)
9300 }
9301
9302 fn parse_observable(&mut self) -> Result<ObservableDefinition, ParseError> {
9315 let tok = self.consume(TokenType::Observable)?;
9316 let name = self.consume(TokenType::Identifier)?.value;
9317 let mut node = ObservableDefinition {
9318 name,
9319 qubits: None,
9320 terms: Vec::new(),
9321 loc: Loc {
9322 line: tok.line,
9323 column: tok.column,
9324 },
9325 leading_trivia: Vec::new(),
9326 trailing_trivia: Vec::new(),
9327 };
9328 self.consume(TokenType::LBrace)?;
9329 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9330 let key_tok = self.consume_any_ident_or_kw()?;
9331 self.consume(TokenType::Colon)?;
9332 match key_tok.value.as_str() {
9333 "qubits" => node.qubits = Some(self.consume_number()? as i64),
9334 "term" => {
9335 let term_loc = Loc {
9336 line: key_tok.line,
9337 column: key_tok.column,
9338 };
9339 let mut negative = false;
9341 if self.check(TokenType::Minus) {
9342 self.advance();
9343 negative = true;
9344 } else if self.check(TokenType::Plus) {
9345 self.advance();
9346 }
9347 let mag = self.consume_number()?;
9348 let coefficient = if negative { -mag } else { mag };
9349 self.consume(TokenType::Star)?;
9351 let pauli = self.consume(TokenType::StringLit)?.value;
9352 node.terms.push(PauliTerm {
9353 coefficient,
9354 pauli,
9355 loc: term_loc,
9356 });
9357 }
9358 _ => self.skip_value(),
9359 }
9360 }
9361 self.consume(TokenType::RBrace)?;
9362 Ok(node)
9363 }
9364
9365 fn parse_witness(&mut self) -> Result<WitnessDefinition, ParseError> {
9373 let tok = self.consume(TokenType::Witness)?;
9374 let name = self.consume(TokenType::Identifier)?.value;
9375 let mut node = WitnessDefinition {
9376 name,
9377 claim: String::new(),
9378 baseline: String::new(),
9379 metric: String::new(),
9380 threshold: 0.0,
9381 data: String::new(),
9382 loc: Loc {
9383 line: tok.line,
9384 column: tok.column,
9385 },
9386 leading_trivia: Vec::new(),
9387 trailing_trivia: Vec::new(),
9388 };
9389 self.consume(TokenType::LBrace)?;
9390 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9391 let key_tok = self.consume_any_ident_or_kw()?;
9392 self.consume(TokenType::Colon)?;
9393 match key_tok.value.as_str() {
9394 "claim" => node.claim = self.consume_any_ident_or_kw()?.value,
9395 "against" => node.baseline = self.consume_any_ident_or_kw()?.value,
9398 "metric" => node.metric = self.consume_any_ident_or_kw()?.value,
9399 "threshold" => node.threshold = self.consume_number()?,
9400 "data" => node.data = self.consume_any_ident_or_kw()?.value,
9401 _ => self.skip_value(),
9402 }
9403 }
9404 self.consume(TokenType::RBrace)?;
9405 Ok(node)
9406 }
9407
9408 fn parse_socket(&mut self) -> Result<SocketDefinition, ParseError> {
9413 let tok = self.consume(TokenType::Socket)?;
9414 let name = self.consume(TokenType::Identifier)?.value;
9415 let mut node = SocketDefinition {
9416 name,
9417 loc: Loc { line: tok.line, column: tok.column },
9418 ..Default::default()
9419 };
9420 self.consume(TokenType::LBrace)?;
9421 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9422 let key = self.consume_any_ident_or_kw()?.value;
9423 self.consume(TokenType::Colon)?;
9424 match key.as_str() {
9425 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
9426 "backpressure" => {
9427 let kind = self.consume_any_ident_or_kw()?.value;
9429 if kind != "credit" {
9430 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
9431 }
9432 self.consume(TokenType::LParen)?;
9433 let n = self
9434 .consume(TokenType::Integer)?
9435 .value
9436 .parse::<i64>()
9437 .map_err(|_| self.error("backpressure credit must be an integer"))?;
9438 self.consume(TokenType::RParen)?;
9439 node.backpressure_credit = Some(n);
9440 }
9441 "reconnect" => {
9442 let mode = self.consume_any_ident_or_kw()?.value;
9443 node.reconnect = mode == "cognitive_state";
9444 }
9445 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
9446 other => return Err(self.error(&format!("unknown socket field `{other}`"))),
9447 }
9448 if self.check(TokenType::Comma) {
9450 self.consume(TokenType::Comma)?;
9451 }
9452 }
9453 self.consume(TokenType::RBrace)?;
9454 Ok(node)
9455 }
9456
9457 fn parse_upstream(&mut self) -> Result<UpstreamDefinition, ParseError> {
9465 let tok = self.consume(TokenType::Upstream)?;
9466 let name = self.consume(TokenType::Identifier)?.value;
9467 let mut node = UpstreamDefinition {
9468 name,
9469 loc: Loc { line: tok.line, column: tok.column },
9470 ..Default::default()
9471 };
9472 if self.check(TokenType::From) {
9474 self.advance();
9475 let base = self.consume(TokenType::Identifier)?.value;
9476 self.consume(TokenType::At)?;
9477 let version = self.consume_any_ident_or_kw()?.value;
9478 node.preset = Some(format!("{base}@{version}"));
9479 }
9480 self.consume(TokenType::LBrace)?;
9481 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9482 let key = self.consume_any_ident_or_kw()?.value;
9483 self.consume(TokenType::Colon)?;
9484 match key.as_str() {
9485 "transport" => node.transport = self.consume_any_ident_or_kw()?.value,
9486 "protocol" => node.protocol = self.consume_any_ident_or_kw()?.value,
9487 "role" => node.role = self.consume_any_ident_or_kw()?.value,
9488 "resolve" => node.resolve = self.parse_dotted_identifier()?,
9489 "resource" => node.resource_ref = self.consume_any_ident_or_kw()?.value,
9493 "secret" => node.secret = self.parse_dotted_identifier()?,
9494 "auth" => {
9495 node.auth_kind = self.consume_any_ident_or_kw()?.value;
9498 if self.check(TokenType::LParen) {
9499 self.consume(TokenType::LParen)?;
9500 node.auth_name = Some(self.consume(TokenType::StringLit)?.value);
9501 if self.check(TokenType::Comma) {
9502 self.consume(TokenType::Comma)?;
9503 node.auth_prefix = Some(self.consume(TokenType::StringLit)?.value);
9504 }
9505 self.consume(TokenType::RParen)?;
9506 }
9507 }
9508 "map" => node.map = self.parse_upstream_map()?,
9509 "reconnect" => node.reconnect = Some(self.parse_upstream_reconnect()?),
9510 "overflow" => node.overflow = Some(self.consume_any_ident_or_kw()?.value),
9511 "backpressure" => {
9512 let kind = self.consume_any_ident_or_kw()?.value;
9514 if kind != "credit" {
9515 return Err(self.error(&format!("expected `credit(n)` for backpressure, got `{kind}`")));
9516 }
9517 self.consume(TokenType::LParen)?;
9518 let n = self
9519 .consume(TokenType::Integer)?
9520 .value
9521 .parse::<i64>()
9522 .map_err(|_| self.error("backpressure credit must be an integer"))?;
9523 self.consume(TokenType::RParen)?;
9524 node.backpressure_credit = Some(n);
9525 }
9526 other => return Err(self.error(&format!("unknown upstream field `{other}`"))),
9527 }
9528 if self.check(TokenType::Comma) {
9530 self.consume(TokenType::Comma)?;
9531 }
9532 }
9533 self.consume(TokenType::RBrace)?;
9534 Ok(node)
9535 }
9536
9537 fn parse_cors(&mut self) -> Result<CorsDefinition, ParseError> {
9546 let tok = self.consume(TokenType::Cors)?;
9547 let name = self.consume(TokenType::Identifier)?.value;
9548 let mut node = CorsDefinition {
9549 name,
9550 loc: Loc { line: tok.line, column: tok.column },
9551 ..Default::default()
9552 };
9553 self.consume(TokenType::LBrace)?;
9554 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9555 let key = self.consume_any_ident_or_kw()?.value;
9556 self.consume(TokenType::Colon)?;
9557 match key.as_str() {
9558 "allow_origins" => node.allow_origins = self.parse_bracketed_strings()?,
9559 "allow_methods" => node.allow_methods = self.parse_bracketed_identifiers()?,
9560 "allow_headers" => node.allow_headers = self.parse_bracketed_strings()?,
9561 "allow_credentials" => {
9562 node.allow_credentials = self.consume_any_ident_or_kw()?.value == "true"
9563 }
9564 "max_age" => node.max_age = Some(self.consume(TokenType::Duration)?.value),
9565 "expose_headers" => node.expose_headers = self.parse_bracketed_strings()?,
9566 other => return Err(self.error(&format!("unknown cors field `{other}`"))),
9567 }
9568 if self.check(TokenType::Comma) {
9570 self.consume(TokenType::Comma)?;
9571 }
9572 }
9573 self.consume(TokenType::RBrace)?;
9574 Ok(node)
9575 }
9576
9577 fn parse_credential(&mut self) -> Result<CredentialDefinition, ParseError> {
9585 let tok = self.consume(TokenType::Credential)?;
9586 let name = self.consume(TokenType::Identifier)?.value;
9587 let mut node = CredentialDefinition {
9588 name,
9589 loc: Loc { line: tok.line, column: tok.column },
9590 ..Default::default()
9591 };
9592 self.consume(TokenType::LBrace)?;
9593 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9594 let key = self.consume_any_ident_or_kw()?.value;
9595 self.consume(TokenType::Colon)?;
9596 match key.as_str() {
9597 "ttl" => node.ttl = self.consume(TokenType::Duration)?.value,
9598 "grants" => {
9599 let bracket_tok = self.current().clone();
9600 let items = self.parse_bracketed_dot_identifiers()?;
9601 for slug in &items {
9602 if !is_valid_capability_slug(slug) {
9603 return Err(ParseError {
9604 message: format!(
9605 "Invalid capability slug '{slug}' in credential '{}' \
9606 `grants:`. Capability slugs must match \
9607 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
9608 lowercase identifiers starting with a letter. Examples: \
9609 `chat.invoke`, `flow.execute`.",
9610 node.name
9611 ),
9612 line: bracket_tok.line,
9613 column: bracket_tok.column,
9614 ..Default::default()
9615 });
9616 }
9617 }
9618 node.grants = items;
9619 }
9620 other => return Err(self.error(&format!("unknown credential field `{other}`"))),
9621 }
9622 if self.check(TokenType::Comma) {
9624 self.consume(TokenType::Comma)?;
9625 }
9626 }
9627 self.consume(TokenType::RBrace)?;
9628 Ok(node)
9629 }
9630
9631 fn parse_cache(&mut self) -> Result<CacheDefinition, ParseError> {
9638 let tok = self.consume(TokenType::Cache)?;
9639 let name = self.consume(TokenType::Identifier)?.value;
9640 let mut node = CacheDefinition {
9641 name,
9642 loc: Loc { line: tok.line, column: tok.column },
9643 ..Default::default()
9644 };
9645 self.consume(TokenType::LBrace)?;
9646 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9647 let key = self.consume_any_ident_or_kw()?.value;
9648 self.consume(TokenType::Colon)?;
9649 match key.as_str() {
9650 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
9651 "ttl" => node.ttl = Some(self.consume(TokenType::Duration)?.value),
9652 "key" => node.key_params = self.parse_bracketed_identifiers()?,
9653 "default" => {
9654 node.default_policy = self.consume_any_ident_or_kw()?.value == "true"
9655 }
9656 "apply_to_effects" => {
9657 node.apply_to_effects = self.parse_bracketed_identifiers()?
9658 }
9659 "invalidate_on" => node.invalidate_on = self.parse_bracketed_identifiers()?,
9660 other => return Err(self.error(&format!("unknown cache field `{other}`"))),
9661 }
9662 if self.check(TokenType::Comma) {
9663 self.consume(TokenType::Comma)?;
9664 }
9665 }
9666 self.consume(TokenType::RBrace)?;
9667 Ok(node)
9668 }
9669
9670 fn parse_document(&mut self) -> Result<crate::ast::DocumentDefinition, ParseError> {
9678 let tok = self.consume(TokenType::Document)?;
9679 let name = self.consume(TokenType::Identifier)?.value;
9680 let mut node = crate::ast::DocumentDefinition {
9681 name,
9682 loc: Loc {
9683 line: tok.line,
9684 column: tok.column,
9685 },
9686 ..Default::default()
9687 };
9688 self.consume(TokenType::LBrace)?;
9689 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9690 let field = self.current().clone();
9691 let field_name = field.value.clone();
9692 self.advance();
9693 if self.check(TokenType::Colon) {
9694 self.advance();
9695 match field_name.as_str() {
9696 "target" => node.target = self.consume_any_ident_or_kw()?.value,
9697 "template" => node.template = self.parse_dotted_identifier()?,
9698 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
9699 "effects" => node.effects = Some(self.parse_effect_row()?),
9700 other => {
9701 return Err(self.error(&format!(
9702 "unknown document field `{other}` in document `{}` — expected \
9703 `target:` / `template:` / `provenance:` / `effects:`, or a body \
9704 block (`section {{ … }}` / `slide {{ … }}` / `sheet {{ … }}`)",
9705 node.name
9706 )))
9707 }
9708 }
9709 } else if self.check(TokenType::LBrace) {
9710 node.blocks
9711 .push(self.parse_doc_block_body(field_name, field.line, field.column)?);
9712 } else {
9713 return Err(self.error(&format!(
9714 "unexpected `{field_name}` in document `{}` body — expected a `field:` or a \
9715 body block `{field_name} {{ … }}`",
9716 node.name
9717 )));
9718 }
9719 if self.check(TokenType::Comma) {
9720 self.advance();
9721 }
9722 }
9723 self.consume(TokenType::RBrace)?;
9724 Ok(node)
9725 }
9726
9727 fn parse_doc_block_body(
9733 &mut self,
9734 kind: String,
9735 line: u32,
9736 column: u32,
9737 ) -> Result<crate::ast::DocBlock, ParseError> {
9738 let mut block = crate::ast::DocBlock {
9739 kind,
9740 loc: Loc { line, column },
9741 ..Default::default()
9742 };
9743 self.consume(TokenType::LBrace)?;
9744 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9745 let name_tok = self.current().clone();
9746 let name = self.consume_any_ident_or_kw()?.value;
9747 if self.check(TokenType::Colon) {
9748 self.advance();
9749 let value = self.parse_doc_scalar()?;
9750 block.fields.push((name, value));
9751 } else if self.check(TokenType::LBrace) {
9752 let child = self.parse_doc_block_body(name, name_tok.line, name_tok.column)?;
9753 block.children.push(child);
9754 } else {
9755 return Err(self.error(&format!(
9756 "in document block `{}`: `{name}` must be a `field:` value or open a nested \
9757 block `{name} {{ … }}`",
9758 block.kind
9759 )));
9760 }
9761 if self.check(TokenType::Comma) {
9762 self.advance();
9763 }
9764 }
9765 self.consume(TokenType::RBrace)?;
9766 Ok(block)
9767 }
9768
9769 fn parse_doc_scalar(&mut self) -> Result<crate::ast::DocScalar, ParseError> {
9774 let tok = self.current().clone();
9775 match tok.ttype {
9776 TokenType::StringLit => {
9777 self.advance();
9778 Ok(crate::ast::DocScalar::Text(tok.value))
9779 }
9780 TokenType::Integer => {
9781 self.advance();
9782 Ok(crate::ast::DocScalar::Int(tok.value.parse::<i64>().unwrap_or(0)))
9783 }
9784 TokenType::Bool => {
9785 self.advance();
9786 Ok(crate::ast::DocScalar::Bool(tok.value == "true"))
9787 }
9788 TokenType::LBracket => {
9789 let items = self.parse_bracketed_strings()?;
9790 Ok(crate::ast::DocScalar::List(items))
9791 }
9792 _ => {
9793 let name = self.consume_any_ident_or_kw()?.value;
9794 Ok(crate::ast::DocScalar::Ref(name))
9795 }
9796 }
9797 }
9798
9799 fn parse_notify(&mut self) -> Result<crate::ast::NotifyDefinition, ParseError> {
9824 let tok = self.consume(TokenType::Notify)?;
9825 let name = self.consume(TokenType::Identifier)?.value;
9826 let mut node = crate::ast::NotifyDefinition {
9827 name,
9828 loc: Loc {
9829 line: tok.line,
9830 column: tok.column,
9831 },
9832 ..Default::default()
9833 };
9834 self.consume(TokenType::LBrace)?;
9835 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9836 let field = self.current().clone();
9837 let field_name = field.value.clone();
9838 self.advance();
9839 if self.check(TokenType::Colon) {
9840 self.advance();
9841 match field_name.as_str() {
9842 "channel" => node.channel = self.consume_any_ident_or_kw()?.value,
9843 "to" => {
9844 if self.current().value == "secret" && self.peek_is_lparen() {
9848 self.advance(); self.consume(TokenType::LParen)?;
9850 node.to_secret = self.parse_dotted_identifier()?;
9851 self.consume(TokenType::RParen)?;
9852 node.to_is_secret = true;
9853 } else if self.check(TokenType::StringLit) {
9854 node.to_secret = self.consume(TokenType::StringLit)?.value.clone();
9855 node.to_is_secret = false;
9856 } else {
9857 node.to_secret = self.consume_any_ident_or_kw()?.value.clone();
9858 node.to_is_secret = false;
9859 }
9860 }
9861 "template" => {
9862 node.template = self.consume(TokenType::StringLit)?.value.clone()
9863 }
9864 "window" => {
9865 if self.check(TokenType::Integer) {
9868 let n = self.consume(TokenType::Integer)?.value.clone();
9869 let unit = self.consume_any_ident_or_kw()?.value.clone();
9870 node.window = format!("{n}{unit}");
9871 } else {
9872 node.window = self.consume_any_ident_or_kw()?.value.clone();
9873 }
9874 }
9875 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
9876 "effects" => node.effects = Some(self.parse_effect_row()?),
9877 other => {
9878 return Err(self.error(&format!(
9879 "unknown notify field `{other}` in notify `{}` — expected \
9880 `channel:` / `to:` / `template:` / `window:` / `provenance:` / \
9881 `effects:`",
9882 node.name
9883 )))
9884 }
9885 }
9886 }
9887 }
9888 self.consume(TokenType::RBrace)?;
9889 Ok(node)
9890 }
9891
9892 fn peek_is_identifier(&self) -> bool {
9896 self.tokens
9897 .get(self.pos + 1)
9898 .map(|t| t.ttype == TokenType::Identifier)
9899 .unwrap_or(false)
9900 }
9901
9902 fn peek_is_lparen(&self) -> bool {
9903 self.tokens
9904 .get(self.pos + 1)
9905 .map(|t| t.ttype == TokenType::LParen)
9906 .unwrap_or(false)
9907 }
9908
9909 fn parse_deliver(&mut self) -> Result<crate::ast::DeliverDefinition, ParseError> {
9911 let tok = self.consume(TokenType::Deliver)?;
9912 let name = self.consume(TokenType::Identifier)?.value;
9913 let mut node = crate::ast::DeliverDefinition {
9914 name,
9915 loc: Loc {
9916 line: tok.line,
9917 column: tok.column,
9918 },
9919 ..Default::default()
9920 };
9921 self.consume(TokenType::LBrace)?;
9922 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9923 let field = self.current().clone();
9924 let field_name = field.value.clone();
9925 self.advance();
9926 if self.check(TokenType::Colon) {
9927 self.advance();
9928 match field_name.as_str() {
9929 "target" => node.target = self.consume_any_ident_or_kw()?.value,
9930 "provenance" => node.provenance = self.consume_any_ident_or_kw()?.value,
9931 "secret" => node.secret = self.consume_any_ident_or_kw()?.value,
9932 "effects" => node.effects = Some(self.parse_effect_row()?),
9933 other => {
9934 return Err(self.error(&format!(
9935 "unknown deliver field `{other}` in deliver `{}` — expected \
9936 `target:` / `provenance:` / `secret:` / `effects:`, or an operation \
9937 block (`upsert_contact {{ … }}` / `create_deal {{ … }}` / \
9938 `add_note {{ … }}`)",
9939 node.name
9940 )))
9941 }
9942 }
9943 } else if self.check(TokenType::LBrace) {
9944 node.ops
9945 .push(self.parse_deliver_op(field_name, field.line, field.column)?);
9946 } else {
9947 return Err(self.error(&format!(
9948 "unexpected `{field_name}` in deliver `{}` body — expected a `field:` or an \
9949 operation block `{field_name} {{ … }}`",
9950 node.name
9951 )));
9952 }
9953 if self.check(TokenType::Comma) {
9954 self.advance();
9955 }
9956 }
9957 self.consume(TokenType::RBrace)?;
9958 Ok(node)
9959 }
9960
9961 fn parse_deliver_op(
9965 &mut self,
9966 kind: String,
9967 line: u32,
9968 column: u32,
9969 ) -> Result<crate::ast::DeliverOp, ParseError> {
9970 let mut op = crate::ast::DeliverOp {
9971 kind,
9972 loc: Loc { line, column },
9973 ..Default::default()
9974 };
9975 self.consume(TokenType::LBrace)?;
9976 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
9977 let name = self.consume_any_ident_or_kw()?.value;
9978 self.consume(TokenType::Colon).map_err(|_| {
9979 self.error(&format!(
9980 "in deliver operation `{}`: `{name}` must be a `field: value` pair — an \
9981 operation binds scalar fields, it takes no nested blocks",
9982 op.kind
9983 ))
9984 })?;
9985 let value = self.parse_doc_scalar()?;
9986 op.fields.push((name, value));
9987 if self.check(TokenType::Comma) {
9988 self.advance();
9989 }
9990 }
9991 self.consume(TokenType::RBrace)?;
9992 Ok(op)
9993 }
9994
9995 fn parse_savant(&mut self) -> Result<SavantDefinition, ParseError> {
10001 let tok = self.consume(TokenType::Savant)?;
10002 let name = self.consume(TokenType::Identifier)?.value;
10003 let mut node = SavantDefinition {
10004 name,
10005 loc: Loc {
10006 line: tok.line,
10007 column: tok.column,
10008 },
10009 ..Default::default()
10010 };
10011 self.consume(TokenType::LBrace)?;
10012 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10013 let field = self.current().clone();
10014 let field_name = field.value.clone();
10015 self.advance();
10016 if self.check(TokenType::Colon) {
10017 self.advance();
10018 match field_name.as_str() {
10019 "domain" => node.domain = self.consume(TokenType::StringLit)?.value,
10020 other => {
10021 return Err(self.error(&format!(
10022 "unknown savant field `{other}` in savant `{}` — expected \
10023 `domain:` or a `cognition` / `memory` / `budget` / `mandate` block",
10024 node.name
10025 )))
10026 }
10027 }
10028 } else if field_name == "cognition" {
10029 node.cognition = Some(self.parse_savant_cognition(field.line, field.column)?);
10030 } else if field_name == "memory" {
10031 node.memory = Some(self.parse_savant_memory(field.line, field.column)?);
10032 } else if field_name == "budget" {
10033 node.budget = Some(self.parse_savant_budget(field.line, field.column)?);
10034 } else if field_name == "mandate" {
10035 node.mandates
10036 .push(self.parse_savant_mandate(field.line, field.column)?);
10037 } else {
10038 return Err(self.error(&format!(
10039 "unexpected `{field_name}` in savant `{}` body — expected `domain:` or a \
10040 `cognition` / `memory` / `budget` / `mandate` block",
10041 node.name
10042 )));
10043 }
10044 if self.check(TokenType::Comma) {
10045 self.advance();
10046 }
10047 }
10048 self.consume(TokenType::RBrace)?;
10049 Ok(node)
10050 }
10051
10052 fn parse_savant_cognition(
10055 &mut self,
10056 line: u32,
10057 column: u32,
10058 ) -> Result<SavantCognition, ParseError> {
10059 self.consume(TokenType::LBrace)?;
10060 let mut node = SavantCognition {
10061 loc: Loc { line, column },
10062 ..Default::default()
10063 };
10064 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10065 let key = self.consume_any_ident_or_kw()?.value;
10066 self.consume(TokenType::Colon)?;
10067 match key.as_str() {
10068 "depth" => node.depth = self.consume_any_ident_or_kw()?.value,
10069 "entropic_threshold" => node.entropic_threshold = self.parse_optional_float(),
10070 "divergence" => node.divergence = self.consume_any_ident_or_kw()?.value,
10071 other => {
10072 return Err(self.error(&format!(
10073 "unknown savant `cognition` field `{other}` — expected \
10074 `depth` / `entropic_threshold` / `divergence`"
10075 )))
10076 }
10077 }
10078 if self.check(TokenType::Comma) {
10079 self.advance();
10080 }
10081 }
10082 self.consume(TokenType::RBrace)?;
10083 Ok(node)
10084 }
10085
10086 fn parse_savant_memory(
10089 &mut self,
10090 line: u32,
10091 column: u32,
10092 ) -> Result<SavantMemory, ParseError> {
10093 self.consume(TokenType::LBrace)?;
10094 let mut node = SavantMemory {
10095 loc: Loc { line, column },
10096 ..Default::default()
10097 };
10098 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10099 let key = self.consume_any_ident_or_kw()?.value;
10100 self.consume(TokenType::Colon)?;
10101 match key.as_str() {
10102 "backend" => node.backend = self.consume_any_ident_or_kw()?.value,
10103 "corpus_graph" => {
10104 node.corpus_graph = self.consume_any_ident_or_kw()?.value == "true"
10105 }
10106 "isolation_level" => node.isolation_level = self.consume_any_ident_or_kw()?.value,
10107 other => {
10108 return Err(self.error(&format!(
10109 "unknown savant `memory` field `{other}` — expected \
10110 `backend` / `corpus_graph` / `isolation_level`"
10111 )))
10112 }
10113 }
10114 if self.check(TokenType::Comma) {
10115 self.advance();
10116 }
10117 }
10118 self.consume(TokenType::RBrace)?;
10119 Ok(node)
10120 }
10121
10122 fn parse_savant_budget(
10125 &mut self,
10126 line: u32,
10127 column: u32,
10128 ) -> Result<SavantBudget, ParseError> {
10129 self.consume(TokenType::LBrace)?;
10130 let mut node = SavantBudget {
10131 loc: Loc { line, column },
10132 ..Default::default()
10133 };
10134 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10135 let key = self.consume_any_ident_or_kw()?.value;
10136 self.consume(TokenType::Colon)?;
10137 match key.as_str() {
10138 "max_iterations" => node.max_iterations = self.parse_optional_int(),
10139 "max_tool_synth" => node.max_tool_synth = self.parse_optional_int(),
10140 other => {
10141 return Err(self.error(&format!(
10142 "unknown savant `budget` field `{other}` — expected \
10143 `max_iterations` / `max_tool_synth`"
10144 )))
10145 }
10146 }
10147 if self.check(TokenType::Comma) {
10148 self.advance();
10149 }
10150 }
10151 self.consume(TokenType::RBrace)?;
10152 Ok(node)
10153 }
10154
10155 fn parse_savant_mandate(
10158 &mut self,
10159 line: u32,
10160 column: u32,
10161 ) -> Result<SavantMandate, ParseError> {
10162 let name = self.consume(TokenType::Identifier)?.value;
10163 let mut node = SavantMandate {
10164 name,
10165 loc: Loc { line, column },
10166 ..Default::default()
10167 };
10168 self.consume(TokenType::LBrace)?;
10169 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10170 let key = self.consume_any_ident_or_kw()?.value;
10171 self.consume(TokenType::Colon)?;
10172 match key.as_str() {
10173 "objective" => node.objective = self.consume(TokenType::StringLit)?.value,
10174 "output" => node.output_type = self.consume_any_ident_or_kw()?.value,
10175 other => {
10176 return Err(self.error(&format!(
10177 "unknown savant `mandate` field `{other}` — expected `objective` / `output`"
10178 )))
10179 }
10180 }
10181 if self.check(TokenType::Comma) {
10182 self.advance();
10183 }
10184 }
10185 self.consume(TokenType::RBrace)?;
10186 Ok(node)
10187 }
10188
10189 fn parse_synth(&mut self) -> Result<SynthDefinition, ParseError> {
10194 let tok = self.consume(TokenType::Synth)?;
10195 let name = self.consume(TokenType::Identifier)?.value;
10196 let mut node = SynthDefinition {
10197 name,
10198 loc: Loc {
10199 line: tok.line,
10200 column: tok.column,
10201 },
10202 ..Default::default()
10203 };
10204 self.consume(TokenType::LBrace)?;
10205 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10206 let key = self.consume_any_ident_or_kw()?.value;
10207 self.consume(TokenType::Colon)?;
10208 match key.as_str() {
10209 "target" => node.target = self.consume(TokenType::StringLit)?.value,
10210 "risk" => node.risk = self.consume_any_ident_or_kw()?.value,
10211 "language" => node.language = self.consume_any_ident_or_kw()?.value,
10212 "sandbox" => node.sandbox = self.consume_any_ident_or_kw()?.value,
10213 "review" => node.review = self.consume_any_ident_or_kw()?.value,
10214 "max_lines" => node.max_lines = self.parse_optional_int(),
10215 other => {
10216 return Err(self.error(&format!(
10217 "unknown synth field `{other}` in synth `{}` — expected `target` / `risk` \
10218 / `language` / `sandbox` / `review` / `max_lines`",
10219 node.name
10220 )))
10221 }
10222 }
10223 if self.check(TokenType::Comma) {
10224 self.consume(TokenType::Comma)?;
10225 }
10226 }
10227 self.consume(TokenType::RBrace)?;
10228 Ok(node)
10229 }
10230
10231 fn parse_voice(&mut self) -> Result<VoiceDefinition, ParseError> {
10236 let tok = self.consume(TokenType::Voice)?;
10237 let name = self.consume(TokenType::Identifier)?.value;
10238 let mut node = VoiceDefinition {
10239 name,
10240 loc: Loc { line: tok.line, column: tok.column },
10241 ..Default::default()
10242 };
10243 self.consume(TokenType::LBrace)?;
10244 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10245 let key = self.consume_any_ident_or_kw()?.value;
10246 self.consume(TokenType::Colon)?;
10247 match key.as_str() {
10248 "stt" => node.stt = Some(self.parse_upstream_ref()?),
10250 "tts" => node.tts = Some(self.parse_upstream_ref()?),
10251 "realtime" => node.realtime = Some(self.parse_upstream_ref()?),
10252 "carrier" => node.carrier = self.consume_any_ident_or_kw()?.value,
10253 "interruptible" => {
10254 let v = self.consume_any_ident_or_kw()?.value;
10255 node.interruptible = v == "true";
10256 }
10257 "legal_basis" => node.legal_basis = Some(self.consume_any_ident_or_kw()?.value),
10258 "persona" => node.persona = Some(self.consume(TokenType::Identifier)?.value),
10259 "context" => node.context = Some(self.consume(TokenType::Identifier)?.value),
10260 other => return Err(self.error(&format!("unknown voice field `{other}`"))),
10261 }
10262 if self.check(TokenType::Comma) {
10263 self.consume(TokenType::Comma)?;
10264 }
10265 }
10266 self.consume(TokenType::RBrace)?;
10267 Ok(node)
10268 }
10269
10270 fn parse_upstream_ref(&mut self) -> Result<String, ParseError> {
10273 let base = self.consume(TokenType::Identifier)?.value;
10274 if self.check(TokenType::At) {
10275 self.advance();
10276 let version = self.consume_any_ident_or_kw()?.value;
10277 Ok(format!("{base}@{version}"))
10278 } else {
10279 Ok(base)
10280 }
10281 }
10282
10283 fn parse_upstream_map(&mut self) -> Result<Vec<UpstreamMapRule>, ParseError> {
10289 self.consume(TokenType::LBracket)?;
10290 let mut rules = Vec::new();
10291 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
10292 let dir_tok = self.current().clone();
10293 let direction = match dir_tok.ttype {
10294 TokenType::Send => "send",
10295 TokenType::Receive => "receive",
10296 _ => {
10297 return Err(self.error(&format!(
10298 "upstream map rule must start with `send` or `receive`, got `{}`",
10299 dir_tok.value
10300 )))
10301 }
10302 };
10303 self.advance();
10304 let message = self.consume(TokenType::Identifier)?.value;
10305 self.consume(TokenType::As)?;
10306 let framing = self.consume_any_ident_or_kw()?.value;
10307 let mut rule = UpstreamMapRule {
10308 direction: direction.to_string(),
10309 message,
10310 framing,
10311 loc: Loc { line: dir_tok.line, column: dir_tok.column },
10312 ..Default::default()
10313 };
10314 if self.current().value == "tag" {
10316 self.advance();
10317 rule.tag = Some(self.consume(TokenType::StringLit)?.value);
10318 } else if self.current().value == "when" {
10319 self.advance();
10324 rule.when_field = Some(self.consume(TokenType::StringLit)?.value);
10325 if self.check(TokenType::Assign) {
10326 self.advance();
10327 rule.when_value = Some(self.consume(TokenType::StringLit)?.value);
10328 }
10329 }
10330 rules.push(rule);
10331 if self.check(TokenType::Comma) {
10332 self.advance();
10333 }
10334 }
10335 self.consume(TokenType::RBracket)?;
10336 Ok(rules)
10337 }
10338
10339 fn parse_upstream_reconnect(&mut self) -> Result<UpstreamReconnect, ParseError> {
10343 self.consume(TokenType::LBrace)?;
10344 let mut backoff_ms: Option<i64> = None;
10345 let mut max_attempts: Option<i64> = None;
10346 let mut on_exhausted: Option<String> = None;
10347 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10348 let key = self.consume_any_ident_or_kw()?.value;
10349 self.consume(TokenType::Colon)?;
10350 match key.as_str() {
10351 "backoff_ms" => {
10352 backoff_ms = Some(
10353 self.consume(TokenType::Integer)?
10354 .value
10355 .parse::<i64>()
10356 .map_err(|_| self.error("backoff_ms must be an integer"))?,
10357 )
10358 }
10359 "max_attempts" => {
10360 max_attempts = Some(
10361 self.consume(TokenType::Integer)?
10362 .value
10363 .parse::<i64>()
10364 .map_err(|_| self.error("max_attempts must be an integer"))?,
10365 )
10366 }
10367 "on_exhausted" => on_exhausted = Some(self.consume_any_ident_or_kw()?.value),
10368 other => return Err(self.error(&format!("unknown reconnect field `{other}`"))),
10369 }
10370 if self.check(TokenType::Comma) {
10371 self.consume(TokenType::Comma)?;
10372 }
10373 }
10374 self.consume(TokenType::RBrace)?;
10375 match (backoff_ms, max_attempts, on_exhausted) {
10376 (Some(b), Some(m), Some(o)) => Ok(UpstreamReconnect { backoff_ms: b, max_attempts: m, on_exhausted: o }),
10377 _ => Err(self.error(
10378 "reconnect requires all of `backoff_ms:`, `max_attempts:`, `on_exhausted:` — a reconnection policy with a hole is not a policy",
10379 )),
10380 }
10381 }
10382
10383 fn parse_session_steps(&mut self) -> Result<Vec<SessionStep>, ParseError> {
10385 self.consume(TokenType::LBracket)?;
10386 let mut steps = Vec::new();
10387 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
10388 steps.push(self.parse_session_step()?);
10389 if self.check(TokenType::Comma) {
10390 self.advance();
10391 }
10392 }
10393 self.consume(TokenType::RBracket)?;
10394 Ok(steps)
10395 }
10396
10397 fn parse_session_step_block(&mut self) -> Result<Vec<SessionStep>, ParseError> {
10401 self.consume(TokenType::LBrace)?;
10402 let mut steps = Vec::new();
10403 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10404 steps.push(self.parse_session_step()?);
10405 if self.check(TokenType::Comma) {
10406 self.advance();
10407 }
10408 }
10409 self.consume(TokenType::RBrace)?;
10410 Ok(steps)
10411 }
10412
10413 fn parse_session_step(&mut self) -> Result<SessionStep, ParseError> {
10414 let tok = self.current().clone();
10415 let loc = Loc { line: tok.line, column: tok.column };
10416 match tok.ttype {
10417 TokenType::Send => {
10418 self.advance();
10419 let msg = self.consume_any_ident_or_kw()?;
10420 Ok(SessionStep { op: "send".into(), message_type: msg.value, loc, ..Default::default() })
10421 }
10422 TokenType::Receive => {
10423 self.advance();
10424 let msg = self.consume_any_ident_or_kw()?;
10425 Ok(SessionStep { op: "receive".into(), message_type: msg.value, loc, ..Default::default() })
10426 }
10427 TokenType::Loop => {
10428 self.advance();
10429 Ok(SessionStep { op: "loop".into(), loc, ..Default::default() })
10430 }
10431 TokenType::End => {
10432 self.advance();
10433 Ok(SessionStep { op: "end".into(), loc, ..Default::default() })
10434 }
10435 TokenType::Identifier if tok.value == "select" || tok.value == "branch" => {
10438 self.parse_session_choice(&tok.value, loc)
10439 }
10440 TokenType::Identifier if tok.value == "interrupt" => {
10443 self.parse_session_interrupt(loc)
10444 }
10445 _ if tok.value == "resume" => {
10463 self.advance();
10464 Ok(SessionStep { op: "resume".into(), loc, ..Default::default() })
10465 }
10466 _ => Err(ParseError {
10467 message: format!(
10468 "Invalid session step '{}' — expected send | receive | loop | end | select | branch | interrupt | resume",
10469 tok.value
10470 ),
10471 line: tok.line,
10472 column: tok.column,
10473 ..Default::default()
10474 }),
10475 }
10476 }
10477
10478 fn consume_contextual(&mut self, kw: &str) -> Result<(), ParseError> {
10483 let t = self.current().clone();
10484 if t.value != kw {
10485 return Err(ParseError {
10486 message: format!("expected `{kw}` in interrupt step, got `{}`", t.value),
10487 line: t.line,
10488 column: t.column,
10489 ..Default::default()
10490 });
10491 }
10492 self.advance();
10493 Ok(())
10494 }
10495
10496 fn parse_session_interrupt(&mut self, loc: Loc) -> Result<SessionStep, ParseError> {
10504 self.advance(); let body = self.parse_session_step_block()?;
10508 self.consume_contextual("on")?;
10510 let signal = self.consume_any_ident_or_kw()?;
10511 self.consume_contextual("as")?;
10513 let binder = self.consume_any_ident_or_kw()?;
10514 self.consume_contextual("resumable")?;
10516 let handler = self.parse_session_step_block()?;
10517 Ok(SessionStep {
10518 op: "interrupt".into(),
10519 message_type: signal.value,
10520 branches: vec![
10521 SessionBranch { label: "body".into(), steps: body, loc: loc.clone() },
10522 SessionBranch { label: "handler".into(), steps: handler, loc: loc.clone() },
10523 ],
10524 binder: binder.value,
10525 resumable: true,
10526 loc,
10527 })
10528 }
10529
10530 fn parse_session_choice(&mut self, op: &str, loc: Loc) -> Result<SessionStep, ParseError> {
10533 self.advance(); self.consume(TokenType::LBrace)?;
10535 let mut branches = Vec::new();
10536 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10537 let label_tok = self.consume_any_ident_or_kw()?;
10538 self.consume(TokenType::Colon)?;
10539 let steps = self.parse_session_steps()?;
10540 branches.push(SessionBranch {
10541 label: label_tok.value,
10542 steps,
10543 loc: Loc { line: label_tok.line, column: label_tok.column },
10544 });
10545 if self.check(TokenType::Comma) {
10546 self.advance();
10547 }
10548 }
10549 self.consume(TokenType::RBrace)?;
10550 Ok(SessionStep { op: op.to_string(), branches, loc, ..Default::default() })
10551 }
10552
10553 fn parse_topology(&mut self) -> Result<TopologyDefinition, ParseError> {
10555 let tok = self.consume(TokenType::Topology)?;
10556 let name = self.consume(TokenType::Identifier)?.value;
10557 let mut node = TopologyDefinition {
10558 name,
10559 nodes: Vec::new(),
10560 edges: Vec::new(),
10561 loc: Loc {
10562 line: tok.line,
10563 column: tok.column,
10564 },
10565 leading_trivia: Vec::new(),
10566 trailing_trivia: Vec::new(),
10567 };
10568 self.consume(TokenType::LBrace)?;
10569 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10570 let field_name = self.current().value.clone();
10571 self.advance();
10572 if !self.check(TokenType::Colon) {
10573 if self.check(TokenType::LBrace) {
10574 self.skip_braced_block()?;
10575 }
10576 continue;
10577 }
10578 self.advance();
10579 match field_name.as_str() {
10580 "nodes" => node.nodes = self.parse_bracketed_identifiers()?,
10581 "edges" => node.edges = self.parse_topology_edges()?,
10582 _ => self.skip_value(),
10583 }
10584 }
10585 self.consume(TokenType::RBrace)?;
10586 Ok(node)
10587 }
10588
10589 fn parse_topology_edges(&mut self) -> Result<Vec<TopologyEdge>, ParseError> {
10590 self.consume(TokenType::LBracket)?;
10591 let mut edges = Vec::new();
10592 while !self.check(TokenType::RBracket) && !self.check(TokenType::Eof) {
10593 edges.push(self.parse_topology_edge()?);
10594 if self.check(TokenType::Comma) {
10595 self.advance();
10596 }
10597 }
10598 self.consume(TokenType::RBracket)?;
10599 Ok(edges)
10600 }
10601
10602 fn parse_topology_edge(&mut self) -> Result<TopologyEdge, ParseError> {
10603 let src_tok = self.consume_any_ident_or_kw()?;
10604 self.consume(TokenType::Arrow)?;
10605 let tgt_tok = self.consume_any_ident_or_kw()?;
10606 self.consume(TokenType::Colon)?;
10607 let sess_tok = self.consume_any_ident_or_kw()?;
10608 Ok(TopologyEdge {
10609 source: src_tok.value,
10610 target: tgt_tok.value,
10611 session_ref: sess_tok.value,
10612 loc: Loc {
10613 line: src_tok.line,
10614 column: src_tok.column,
10615 },
10616 })
10617 }
10618
10619 fn parse_immune(&mut self) -> Result<ImmuneDefinition, ParseError> {
10623 let tok = self.consume(TokenType::Immune)?;
10624 let name = self.consume(TokenType::Identifier)?.value;
10625 let mut node = ImmuneDefinition {
10626 name,
10627 watch: Vec::new(),
10628 sensitivity: None,
10629 baseline: "learned".to_string(),
10630 window: 100,
10631 scope: String::new(),
10632 tau: String::new(),
10633 decay: "exponential".to_string(),
10634 loc: Loc {
10635 line: tok.line,
10636 column: tok.column,
10637 },
10638 leading_trivia: Vec::new(),
10639 trailing_trivia: Vec::new(),
10640 };
10641 self.consume(TokenType::LBrace)?;
10642 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10643 let field_name = self.current().value.clone();
10644 self.advance();
10645 if !self.check(TokenType::Colon) {
10646 if self.check(TokenType::LBrace) {
10647 self.skip_braced_block()?;
10648 }
10649 continue;
10650 }
10651 self.advance();
10652 match field_name.as_str() {
10653 "watch" => node.watch = self.parse_bracketed_identifiers()?,
10654 "sensitivity" => node.sensitivity = self.parse_optional_float(),
10655 "baseline" => node.baseline = self.consume_any_ident_or_kw()?.value,
10656 "window" => {
10657 if let Some(v) = self.parse_optional_int() {
10658 node.window = v;
10659 }
10660 }
10661 "scope" => {
10662 let s_tok = self.consume_any_ident_or_kw()?;
10663 let s = s_tok.value;
10664 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
10665 return Err(ParseError {
10666 message: format!(
10667 "Invalid scope '{s}' in immune '{}' — \
10668 expected tenant | flow | global",
10669 node.name
10670 ),
10671 line: s_tok.line,
10672 column: s_tok.column,
10673 ..Default::default()
10674 });
10675 }
10676 node.scope = s;
10677 }
10678 "tau" => {
10679 let t = self.current().clone();
10680 match t.ttype {
10681 TokenType::Duration | TokenType::StringLit => {
10682 self.advance();
10683 node.tau = t.value;
10684 }
10685 _ => node.tau = self.consume_any_ident_or_kw()?.value,
10686 }
10687 }
10688 "decay" => {
10689 let d_tok = self.consume_any_ident_or_kw()?;
10690 let d = d_tok.value;
10691 if !matches!(d.as_str(), "exponential" | "linear" | "none") {
10692 return Err(ParseError {
10693 message: format!(
10694 "Invalid decay '{d}' in immune '{}' — \
10695 expected exponential | linear | none",
10696 node.name
10697 ),
10698 line: d_tok.line,
10699 column: d_tok.column,
10700 ..Default::default()
10701 });
10702 }
10703 node.decay = d;
10704 }
10705 _ => self.skip_value(),
10706 }
10707 }
10708 self.consume(TokenType::RBrace)?;
10709 Ok(node)
10710 }
10711
10712 fn parse_reflex(&mut self) -> Result<ReflexDefinition, ParseError> {
10714 let tok = self.consume(TokenType::Reflex)?;
10715 let name = self.consume(TokenType::Identifier)?.value;
10716 let mut node = ReflexDefinition {
10717 name,
10718 trigger: String::new(),
10719 on_level: "doubt".to_string(),
10720 action: String::new(),
10721 scope: String::new(),
10722 sla: String::new(),
10723 loc: Loc {
10724 line: tok.line,
10725 column: tok.column,
10726 },
10727 leading_trivia: Vec::new(),
10728 trailing_trivia: Vec::new(),
10729 };
10730 self.consume(TokenType::LBrace)?;
10731 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10732 let field_name = self.current().value.clone();
10733 self.advance();
10734 if !self.check(TokenType::Colon) {
10735 if self.check(TokenType::LBrace) {
10736 self.skip_braced_block()?;
10737 }
10738 continue;
10739 }
10740 self.advance();
10741 match field_name.as_str() {
10742 "trigger" => node.trigger = self.consume_any_ident_or_kw()?.value,
10743 "on_level" => {
10744 let l_tok = self.consume_any_ident_or_kw()?;
10745 let l = l_tok.value;
10746 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
10747 return Err(ParseError {
10748 message: format!(
10749 "Invalid on_level '{l}' in reflex '{}' — \
10750 expected know | believe | speculate | doubt",
10751 node.name
10752 ),
10753 line: l_tok.line,
10754 column: l_tok.column,
10755 ..Default::default()
10756 });
10757 }
10758 node.on_level = l;
10759 }
10760 "action" => {
10761 let a_tok = self.consume_any_ident_or_kw()?;
10762 let a = a_tok.value;
10763 if !matches!(
10764 a.as_str(),
10765 "drop"
10766 | "revoke"
10767 | "emit"
10768 | "redact"
10769 | "quarantine"
10770 | "terminate"
10771 | "alert"
10772 ) {
10773 return Err(ParseError {
10774 message: format!(
10775 "Invalid action '{a}' in reflex '{}' — \
10776 expected drop | revoke | emit | redact | \
10777 quarantine | terminate | alert",
10778 node.name
10779 ),
10780 line: a_tok.line,
10781 column: a_tok.column,
10782 ..Default::default()
10783 });
10784 }
10785 node.action = a;
10786 }
10787 "scope" => {
10788 let s_tok = self.consume_any_ident_or_kw()?;
10789 let s = s_tok.value;
10790 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
10791 return Err(ParseError {
10792 message: format!(
10793 "Invalid scope '{s}' in reflex '{}' — \
10794 expected tenant | flow | global",
10795 node.name
10796 ),
10797 line: s_tok.line,
10798 column: s_tok.column,
10799 ..Default::default()
10800 });
10801 }
10802 node.scope = s;
10803 }
10804 "sla" => {
10805 let t = self.current().clone();
10806 match t.ttype {
10807 TokenType::Duration | TokenType::StringLit => {
10808 self.advance();
10809 node.sla = t.value;
10810 }
10811 _ => node.sla = self.consume_any_ident_or_kw()?.value,
10812 }
10813 }
10814 _ => self.skip_value(),
10815 }
10816 }
10817 self.consume(TokenType::RBrace)?;
10818 Ok(node)
10819 }
10820
10821 fn parse_heal(&mut self) -> Result<HealDefinition, ParseError> {
10823 let tok = self.consume(TokenType::Heal)?;
10824 let name = self.consume(TokenType::Identifier)?.value;
10825 let mut node = HealDefinition {
10826 name,
10827 source: String::new(),
10828 on_level: "doubt".to_string(),
10829 mode: "human_in_loop".to_string(),
10830 scope: String::new(),
10831 review_sla: String::new(),
10832 shield_ref: String::new(),
10833 max_patches: 3,
10834 loc: Loc {
10835 line: tok.line,
10836 column: tok.column,
10837 },
10838 leading_trivia: Vec::new(),
10839 trailing_trivia: Vec::new(),
10840 };
10841 self.consume(TokenType::LBrace)?;
10842 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10843 let field_name = self.current().value.clone();
10844 self.advance();
10845 if !self.check(TokenType::Colon) {
10846 if self.check(TokenType::LBrace) {
10847 self.skip_braced_block()?;
10848 }
10849 continue;
10850 }
10851 self.advance();
10852 match field_name.as_str() {
10853 "source" => node.source = self.consume_any_ident_or_kw()?.value,
10854 "on_level" => {
10855 let l_tok = self.consume_any_ident_or_kw()?;
10856 let l = l_tok.value;
10857 if !matches!(l.as_str(), "know" | "believe" | "speculate" | "doubt") {
10858 return Err(ParseError {
10859 message: format!(
10860 "Invalid on_level '{l}' in heal '{}' — \
10861 expected know | believe | speculate | doubt",
10862 node.name
10863 ),
10864 line: l_tok.line,
10865 column: l_tok.column,
10866 ..Default::default()
10867 });
10868 }
10869 node.on_level = l;
10870 }
10871 "mode" => {
10872 let m_tok = self.consume_any_ident_or_kw()?;
10873 let m = m_tok.value;
10874 if !matches!(m.as_str(), "audit_only" | "human_in_loop" | "adversarial") {
10875 return Err(ParseError {
10876 message: format!(
10877 "Invalid mode '{m}' in heal '{}' — \
10878 expected audit_only | human_in_loop | adversarial",
10879 node.name
10880 ),
10881 line: m_tok.line,
10882 column: m_tok.column,
10883 ..Default::default()
10884 });
10885 }
10886 node.mode = m;
10887 }
10888 "scope" => {
10889 let s_tok = self.consume_any_ident_or_kw()?;
10890 let s = s_tok.value;
10891 if !matches!(s.as_str(), "tenant" | "flow" | "global") {
10892 return Err(ParseError {
10893 message: format!(
10894 "Invalid scope '{s}' in heal '{}' — \
10895 expected tenant | flow | global",
10896 node.name
10897 ),
10898 line: s_tok.line,
10899 column: s_tok.column,
10900 ..Default::default()
10901 });
10902 }
10903 node.scope = s;
10904 }
10905 "review_sla" => {
10906 let t = self.current().clone();
10907 match t.ttype {
10908 TokenType::Duration | TokenType::StringLit => {
10909 self.advance();
10910 node.review_sla = t.value;
10911 }
10912 _ => node.review_sla = self.consume_any_ident_or_kw()?.value,
10913 }
10914 }
10915 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
10916 "max_patches" => {
10917 if let Some(v) = self.parse_optional_int() {
10918 node.max_patches = v;
10919 }
10920 }
10921 _ => self.skip_value(),
10922 }
10923 }
10924 self.consume(TokenType::RBrace)?;
10925 Ok(node)
10926 }
10927
10928 fn parse_component(&mut self) -> Result<ComponentDefinition, ParseError> {
10932 let tok = self.consume(TokenType::Component)?;
10933 let name = self.consume(TokenType::Identifier)?.value;
10934 let mut node = ComponentDefinition {
10935 name,
10936 renders: String::new(),
10937 via_shield: String::new(),
10938 on_interact: String::new(),
10939 render_hint: "custom".to_string(),
10940 loc: Loc {
10941 line: tok.line,
10942 column: tok.column,
10943 },
10944 leading_trivia: Vec::new(),
10945 trailing_trivia: Vec::new(),
10946 };
10947 self.consume(TokenType::LBrace)?;
10948 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
10949 let field_name = self.current().value.clone();
10950 self.advance();
10951 if !self.check(TokenType::Colon) {
10952 if self.check(TokenType::LBrace) {
10953 self.skip_braced_block()?;
10954 }
10955 continue;
10956 }
10957 self.advance();
10958 match field_name.as_str() {
10959 "renders" => node.renders = self.consume_any_ident_or_kw()?.value,
10960 "via_shield" => node.via_shield = self.consume_any_ident_or_kw()?.value,
10961 "on_interact" => node.on_interact = self.consume_any_ident_or_kw()?.value,
10962 "render_hint" => {
10963 let h_tok = self.consume_any_ident_or_kw()?;
10964 let h = h_tok.value;
10965 if !matches!(h.as_str(), "card" | "list" | "form" | "chart" | "custom") {
10966 return Err(ParseError {
10967 message: format!(
10968 "Invalid render_hint '{h}' in component '{}' — \
10969 expected card | list | form | chart | custom",
10970 node.name
10971 ),
10972 line: h_tok.line,
10973 column: h_tok.column,
10974 ..Default::default()
10975 });
10976 }
10977 node.render_hint = h;
10978 }
10979 _ => self.skip_value(),
10980 }
10981 }
10982 self.consume(TokenType::RBrace)?;
10983 Ok(node)
10984 }
10985
10986 fn parse_view(&mut self) -> Result<ViewDefinition, ParseError> {
10988 let tok = self.consume(TokenType::View)?;
10989 let name = self.consume(TokenType::Identifier)?.value;
10990 let mut node = ViewDefinition {
10991 name,
10992 title: String::new(),
10993 components: Vec::new(),
10994 route: String::new(),
10995 loc: Loc {
10996 line: tok.line,
10997 column: tok.column,
10998 },
10999 leading_trivia: Vec::new(),
11000 trailing_trivia: Vec::new(),
11001 };
11002 self.consume(TokenType::LBrace)?;
11003 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
11004 let field_name = self.current().value.clone();
11005 self.advance();
11006 if !self.check(TokenType::Colon) {
11007 if self.check(TokenType::LBrace) {
11008 self.skip_braced_block()?;
11009 }
11010 continue;
11011 }
11012 self.advance();
11013 match field_name.as_str() {
11014 "title" => node.title = self.consume(TokenType::StringLit)?.value,
11015 "components" => node.components = self.parse_bracketed_identifiers()?,
11016 "route" => node.route = self.consume(TokenType::StringLit)?.value,
11017 _ => self.skip_value(),
11018 }
11019 }
11020 self.consume(TokenType::RBrace)?;
11021 Ok(node)
11022 }
11023
11024 fn parse_axonendpoint(&mut self) -> Result<AxonEndpointDefinition, ParseError> {
11025 let tok = self.consume(TokenType::AxonEndpoint)?;
11026 let name = self.consume(TokenType::Identifier)?.value;
11027 let mut node = AxonEndpointDefinition {
11028 name,
11029 method: String::new(),
11030 path: String::new(),
11031 body_type: String::new(),
11032 execute_flow: String::new(),
11033 output_type: String::new(),
11034 shield_ref: String::new(),
11035 cors_ref: String::new(),
11038 retries: None,
11039 timeout: String::new(),
11040 compliance: Vec::new(),
11041 transport: "json".to_string(),
11043 keepalive: String::new(),
11044 transport_explicit: false,
11050 implicit_transport: String::new(),
11051 requires_capabilities: Vec::new(),
11053 public: false,
11056 replay_explicit: false,
11060 replay: false,
11061 transport_dialect: String::new(),
11066 has_algebraic_stream_effect: false,
11072 backend: String::new(),
11077 path_params: Vec::new(),
11082 query_params: Vec::new(),
11087 loc: Loc {
11088 line: tok.line,
11089 column: tok.column,
11090 },
11091 leading_trivia: Vec::new(),
11092 trailing_trivia: Vec::new(),
11093 };
11094 self.consume(TokenType::LBrace)?;
11095 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
11096 let field_name = self.current().value.clone();
11097 self.advance();
11098 if self.check(TokenType::Colon) {
11099 self.advance();
11100 match field_name.as_str() {
11101 "method" => {
11102 let value_tok = self.consume_any_ident_or_kw()?;
11109 let value_upper = value_tok.value.to_uppercase();
11110 if !axonendpoint_is_valid_method(&value_upper) {
11111 let hint = crate::smart_suggest::suggest_for(
11112 &value_upper,
11113 AXONENDPOINT_METHOD_VALUES,
11114 );
11115 let base = format!(
11116 "Invalid method '{}' in axonendpoint '{}'.",
11117 value_tok.value, node.name
11118 );
11119 let message = if hint.is_empty() {
11120 format!(
11121 "{base} expected GET | POST | PUT | DELETE | PATCH, found {}",
11122 value_tok.value
11123 )
11124 } else {
11125 format!(
11126 "{base} {hint} (expected GET | POST | PUT | DELETE | PATCH, found {})",
11127 value_tok.value
11128 )
11129 };
11130 return Err(ParseError {
11131 message,
11132 line: value_tok.line,
11133 column: value_tok.column,
11134 ..Default::default()
11135 });
11136 }
11137 node.method = value_upper;
11138 }
11139 "path" => {
11140 node.path = self.consume(TokenType::StringLit)?.value.clone();
11141 match extract_path_param_names(&node.path) {
11149 Ok(names) => node.path_params = names,
11150 Err(dup) => {
11151 let cur = self.current().clone();
11152 return Err(ParseError {
11153 message: format!(
11154 "axonendpoint '{}' declares path '{}' \
11155 containing duplicate placeholder '{{{}}}'. \
11156 Each `{{name}}` in a `path:` must be \
11157 unique — the runtime cannot bind two \
11158 path segments to the same name (Fase 37.y D1).",
11159 node.name, node.path, dup,
11160 ),
11161 line: cur.line,
11162 column: cur.column,
11163 ..Default::default()
11164 });
11165 }
11166 }
11167 },
11168 "body" => node.body_type = self.consume_any_ident_or_kw()?.value.clone(),
11169 "query" => {
11170 let lbrace_tok = self.consume(TokenType::LBrace)?;
11189 let block_line = lbrace_tok.line;
11190 if !node.query_params.is_empty() {
11191 return Err(ParseError {
11192 message: format!(
11193 "axonendpoint '{}' declares `query: {{ … }}` \
11194 more than once. The query-parameter block \
11195 is unique per endpoint; combine all params \
11196 into a single block (Fase 37.y D2).",
11197 node.name,
11198 ),
11199 line: lbrace_tok.line,
11200 column: lbrace_tok.column,
11201 ..Default::default()
11202 });
11203 }
11204 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
11205 let name_tok = self.consume(TokenType::Identifier)?;
11206 let field_name = name_tok.value.clone();
11207 if node
11209 .query_params
11210 .iter()
11211 .any(|f| f.name == field_name)
11212 {
11213 return Err(ParseError {
11214 message: format!(
11215 "axonendpoint '{}' declares duplicate \
11216 query param '{}' inside `query: {{ … }}`. \
11217 Each name must appear at most once \
11218 (Fase 37.y D2).",
11219 node.name, field_name,
11220 ),
11221 line: name_tok.line,
11222 column: name_tok.column,
11223 ..Default::default()
11224 });
11225 }
11226 self.consume(TokenType::Colon)?;
11227 let type_expr = self.parse_type_expr()?;
11228 if !type_expr.generic_param.is_empty() {
11240 let canonical_hint = if type_expr.name == "Optional" {
11241 format!(
11242 " Use `{}?` (the `?` suffix) for an \
11243 optional query param instead of \
11244 `Optional<{}>`.",
11245 type_expr.generic_param,
11246 type_expr.generic_param,
11247 )
11248 } else if type_expr.name == "List" {
11249 " Multi-value query params (e.g. `?tag=a&tag=b`) \
11250 are honest-deferred from v1.38.5; bind a \
11251 single-value `Text` query param and parse \
11252 the value inside the flow."
11253 .to_string()
11254 } else {
11255 String::new()
11256 };
11257 return Err(ParseError {
11258 message: format!(
11259 "axonendpoint '{}' query param '{}' uses \
11260 a generic type `{}<{}>`. Query params \
11261 take a primitive type from the closed \
11262 catalog ({}); the `?` suffix marks \
11263 optional.{} (Fase 37.y D2).",
11264 node.name,
11265 field_name,
11266 type_expr.name,
11267 type_expr.generic_param,
11268 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | "),
11269 canonical_hint,
11270 ),
11271 line: type_expr.loc.line,
11272 column: type_expr.loc.column,
11273 ..Default::default()
11274 });
11275 }
11276 if !axonendpoint_is_valid_query_param_type(&type_expr.name) {
11280 let hint = crate::smart_suggest::suggest_for(
11287 &type_expr.name,
11288 AXONENDPOINT_QUERY_PARAM_TYPES,
11289 );
11290 let hint_text = if hint.is_empty() {
11291 format!(
11292 " Expected one of: {}.",
11293 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
11294 )
11295 } else {
11296 format!(
11297 " {} Expected one of: {}.",
11298 hint,
11299 AXONENDPOINT_QUERY_PARAM_TYPES.join(" | ")
11300 )
11301 };
11302 return Err(ParseError {
11303 message: format!(
11304 "axonendpoint '{}' query param '{}' has \
11305 unsupported type '{}'.{} (Fase 37.y D2).",
11306 node.name, field_name, type_expr.name,
11307 hint_text,
11308 ),
11309 line: type_expr.loc.line,
11310 column: type_expr.loc.column,
11311 ..Default::default()
11312 });
11313 }
11314 node.query_params.push(TypeField {
11315 name: field_name,
11316 type_expr,
11317 loc: Loc {
11318 line: name_tok.line,
11319 column: name_tok.column,
11320 },
11321 });
11322 if self.check(TokenType::Comma) {
11328 self.advance();
11329 }
11330 let _ = block_line; }
11332 self.consume(TokenType::RBrace)?;
11333 },
11334 "execute" => node.execute_flow = self.consume_any_ident_or_kw()?.value.clone(),
11335 "output" => {
11336 node.output_type = self.parse_output_type_string()?;
11354 }
11355 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value.clone(),
11356 "cors" => node.cors_ref = self.consume_any_ident_or_kw()?.value.clone(),
11358 "retries" => node.retries = self.parse_optional_int(),
11359 "timeout" => {
11360 let t = self.current().clone();
11361 self.advance();
11362 node.timeout = t.value.clone();
11363 }
11364 "compliance" => node.compliance = self.parse_bracketed_identifiers()?,
11365 "replay" => {
11366 let value_tok = self.consume(TokenType::Bool)?;
11373 node.replay = value_tok.value.eq_ignore_ascii_case("true");
11374 node.replay_explicit = true;
11375 }
11376 "public" => {
11382 let value_tok = self.consume(TokenType::Bool)?;
11383 node.public = value_tok.value.eq_ignore_ascii_case("true");
11384 }
11385 "requires" => {
11386 let bracket_tok = self.current().clone();
11393 let items = self.parse_bracketed_dot_identifiers()?;
11394 for slug in &items {
11395 if !is_valid_capability_slug(slug) {
11396 return Err(ParseError {
11397 message: format!(
11398 "Invalid capability slug '{slug}' in axonendpoint '{}' \
11399 `requires:`. Capability slugs must match \
11400 ^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$ — dot-separated \
11401 lowercase identifiers starting with a letter. Examples: \
11402 `admin`, `legal.read`, `hipaa.phi.read`.",
11403 node.name
11404 ),
11405 line: bracket_tok.line,
11406 column: bracket_tok.column,
11407 ..Default::default()
11408 });
11409 }
11410 }
11411 node.requires_capabilities = items;
11412 }
11413 "transport" => {
11417 let value_tok = self.consume_any_ident_or_kw()?;
11418 let value = &value_tok.value;
11419 if !axonendpoint_is_valid_transport(value) {
11420 let hint = crate::smart_suggest::suggest_for(
11421 value,
11422 AXONENDPOINT_TRANSPORT_VALUES,
11423 );
11424 let base = format!(
11425 "Invalid transport '{}' in axonendpoint '{}'.",
11426 value, node.name
11427 );
11428 let message = if hint.is_empty() {
11429 format!("{base} expected json | sse | ndjson, found {value}")
11430 } else {
11431 format!(
11432 "{base} {hint} (expected json | sse | ndjson, found {value})"
11433 )
11434 };
11435 return Err(ParseError {
11436 message,
11437 line: value_tok.line,
11438 column: value_tok.column,
11439 ..Default::default()
11440 });
11441 }
11442 node.transport = value.clone();
11443 node.transport_explicit = true;
11448 if self.check(TokenType::LParen) {
11455 if value != "sse" {
11456 let tok = self.current().clone();
11457 return Err(ParseError {
11458 message: format!(
11459 "Dialect parametrization \
11460 `transport: {value}(<dialect>)` is \
11461 only valid for `sse`; got \
11462 `{value}` in axonendpoint '{}'.",
11463 node.name
11464 ),
11465 line: tok.line,
11466 column: tok.column,
11467 ..Default::default()
11468 });
11469 }
11470 self.advance(); let dialect_tok = self.consume_any_ident_or_kw()?;
11472 let dialect = dialect_tok.value.clone();
11473 if !AXONENDPOINT_TRANSPORT_DIALECTS
11474 .iter()
11475 .any(|&d| d == dialect)
11476 {
11477 let hint = crate::smart_suggest::suggest_for(
11478 &dialect,
11479 AXONENDPOINT_TRANSPORT_DIALECTS,
11480 );
11481 let base = format!(
11482 "Invalid SSE dialect '{dialect}' in axonendpoint '{}'.",
11483 node.name
11484 );
11485 let message = if hint.is_empty() {
11486 format!(
11487 "{base} expected axon | openai | kimi | glm | anthropic, found {dialect}"
11488 )
11489 } else {
11490 format!(
11491 "{base} {hint} (expected axon | openai | kimi | glm | anthropic, found {dialect})"
11492 )
11493 };
11494 return Err(ParseError {
11495 message,
11496 line: dialect_tok.line,
11497 column: dialect_tok.column,
11498 ..Default::default()
11499 });
11500 }
11501 let rparen_tok = self.current().clone();
11503 if !self.check(TokenType::RParen) {
11504 return Err(ParseError {
11505 message: format!(
11506 "Expected `)` after dialect name \
11507 in axonendpoint '{}' \
11508 (transport: sse(<dialect>) grammar).",
11509 node.name
11510 ),
11511 line: rparen_tok.line,
11512 column: rparen_tok.column,
11513 ..Default::default()
11514 });
11515 }
11516 self.advance(); node.transport_dialect = dialect;
11518 }
11519 }
11520 "keepalive" => {
11521 let value_tok = self.current().clone();
11525 self.advance();
11526 let value = &value_tok.value;
11527 if !axonendpoint_is_valid_keepalive(value) {
11528 let hint = crate::smart_suggest::suggest_for(
11529 value,
11530 AXONENDPOINT_KEEPALIVE_VALUES,
11531 );
11532 let base = format!(
11533 "Invalid keepalive '{}' in axonendpoint '{}'.",
11534 value, node.name
11535 );
11536 let message = if hint.is_empty() {
11537 format!("{base} expected 5s | 15s | 30s | 60s, found {value}")
11538 } else {
11539 format!(
11540 "{base} {hint} (expected 5s | 15s | 30s | 60s, found {value})"
11541 )
11542 };
11543 return Err(ParseError {
11544 message,
11545 line: value_tok.line,
11546 column: value_tok.column,
11547 ..Default::default()
11548 });
11549 }
11550 node.keepalive = value.clone();
11551 }
11552 "backend" => {
11553 let value_tok = self.consume_any_ident_or_kw()?;
11561 let value = &value_tok.value;
11562 if !axonendpoint_is_valid_backend(value) {
11563 let hint = crate::smart_suggest::suggest_for(
11564 value,
11565 AXONENDPOINT_BACKEND_VALUES,
11566 );
11567 let expected = AXONENDPOINT_BACKEND_VALUES.join(" | ");
11568 let base = format!(
11569 "Invalid backend '{}' in axonendpoint '{}'.",
11570 value, node.name
11571 );
11572 let message = if hint.is_empty() {
11573 format!("{base} expected {expected}, found {value}")
11574 } else {
11575 format!(
11576 "{base} {hint} (expected {expected}, found {value})"
11577 )
11578 };
11579 return Err(ParseError {
11580 message,
11581 line: value_tok.line,
11582 column: value_tok.column,
11583 ..Default::default()
11584 });
11585 }
11586 node.backend = value.clone();
11587 }
11588 _ => self.skip_value(),
11589 }
11590 } else if self.check(TokenType::LBrace) {
11591 self.skip_braced_block()?;
11592 }
11593 }
11594 self.consume(TokenType::RBrace)?;
11595 Ok(node)
11596 }
11597
11598 fn parse_optional_int(&mut self) -> Option<i64> {
11601 let tok = self.current().clone();
11602 match tok.ttype {
11603 TokenType::Integer => {
11604 self.advance();
11605 tok.value.parse::<i64>().ok()
11606 }
11607 _ => {
11608 self.advance();
11609 None
11610 }
11611 }
11612 }
11613
11614 fn parse_optional_float(&mut self) -> Option<f64> {
11615 let tok = self.current().clone();
11616 match tok.ttype {
11617 TokenType::Float | TokenType::Integer => {
11618 self.advance();
11619 tok.value.parse::<f64>().ok()
11620 }
11621 _ => {
11622 self.advance();
11623 None
11624 }
11625 }
11626 }
11627
11628 fn parse_lambda_data(&mut self) -> Result<LambdaDataDefinition, ParseError> {
11631 let tok = self.consume(TokenType::Lambda)?;
11632 let name = self.consume(TokenType::Identifier)?;
11633 self.consume(TokenType::LBrace)?;
11634
11635 let mut node = LambdaDataDefinition {
11636 name: name.value.clone(),
11637 ontology: String::new(),
11638 certainty: 1.0,
11639 temporal_frame_start: String::new(),
11640 temporal_frame_end: String::new(),
11641 provenance: String::new(),
11642 derivation: String::new(),
11643 loc: Loc {
11644 line: tok.line,
11645 column: tok.column,
11646 },
11647 leading_trivia: Vec::new(),
11648 trailing_trivia: Vec::new(),
11649 };
11650
11651 while !self.check(TokenType::RBrace) {
11652 let field = self.current().clone();
11653 match field.ttype {
11654 TokenType::Ontology => {
11655 self.advance();
11656 self.consume(TokenType::Colon)?;
11657 node.ontology = self.consume(TokenType::StringLit)?.value.clone();
11658 }
11659 TokenType::Certainty => {
11660 self.advance();
11661 self.consume(TokenType::Colon)?;
11662 let val = self.current().clone();
11663 match val.ttype {
11664 TokenType::Float => {
11665 self.advance();
11666 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
11667 }
11668 TokenType::Integer => {
11669 self.advance();
11670 node.certainty = val.value.parse::<f64>().unwrap_or(1.0);
11671 }
11672 _ => {
11673 return Err(ParseError {
11674 message: format!(
11675 "Expected number for certainty, got '{}'",
11676 val.value
11677 ),
11678 line: val.line,
11679 column: val.column,
11680 ..Default::default()
11681 });
11682 }
11683 }
11684 }
11685 TokenType::TemporalFrame => {
11686 self.advance();
11687 self.consume(TokenType::Colon)?;
11688 node.temporal_frame_start = self.consume(TokenType::StringLit)?.value.clone();
11689 if self.check(TokenType::StringLit) {
11691 node.temporal_frame_end = self.consume(TokenType::StringLit)?.value.clone();
11692 }
11693 }
11694 TokenType::Provenance => {
11695 self.advance();
11696 self.consume(TokenType::Colon)?;
11697 node.provenance = self.consume(TokenType::StringLit)?.value.clone();
11698 }
11699 TokenType::Derivation => {
11700 self.advance();
11701 self.consume(TokenType::Colon)?;
11702 let d = self.current().clone();
11703 self.advance();
11704 node.derivation = d.value.clone();
11705 }
11706 _ => {
11707 self.advance();
11709 if self.check(TokenType::Colon) {
11710 self.advance();
11711 self.skip_value();
11712 }
11713 }
11714 }
11715 }
11716
11717 self.consume(TokenType::RBrace)?;
11718 Ok(node)
11719 }
11720
11721 fn parse_lambda_data_apply(&mut self) -> Result<LambdaDataApplyNode, ParseError> {
11722 let tok = self.consume(TokenType::Lambda)?;
11723 let lambda_name = self.consume(TokenType::Identifier)?;
11724
11725 let on_tok = self.current().clone();
11727 self.advance();
11728 if on_tok.value != "on" {
11729 return Err(ParseError {
11730 message: format!(
11731 "Expected 'on' after lambda data name in flow step, got '{}'",
11732 on_tok.value
11733 ),
11734 line: on_tok.line,
11735 column: on_tok.column,
11736 ..Default::default()
11737 });
11738 }
11739
11740 let target = self.current().clone();
11741 self.advance();
11742
11743 let mut output_type = String::new();
11744 if self.check(TokenType::Arrow) {
11745 self.advance();
11746 output_type = self.consume(TokenType::Identifier)?.value.clone();
11747 }
11748
11749 Ok(LambdaDataApplyNode {
11750 lambda_data_name: lambda_name.value.clone(),
11751 target: target.value.clone(),
11752 output_type,
11753 loc: Loc {
11754 line: tok.line,
11755 column: tok.column,
11756 },
11757 })
11758 }
11759
11760 fn parse_generic_declaration(&mut self) -> Result<Declaration, ParseError> {
11763 let kw_tok = self.current().clone();
11764 self.advance(); let name = if self.current().ttype == TokenType::Identifier {
11768 let n = self.current().value.clone();
11769 self.advance();
11770 n
11771 } else if !self.check(TokenType::LBrace)
11772 && !self.check(TokenType::LParen)
11773 && !self.check(TokenType::Eof)
11774 && self
11775 .current()
11776 .value
11777 .chars()
11778 .all(|c| c.is_alphanumeric() || c == '_')
11779 {
11780 let n = self.current().value.clone();
11781 self.advance();
11782 n
11783 } else {
11784 String::new()
11785 };
11786
11787 if self.check(TokenType::LParen) {
11789 self.advance();
11790 let mut depth = 1u32;
11791 while depth > 0 && !self.check(TokenType::Eof) {
11792 if self.check(TokenType::LParen) {
11793 depth += 1;
11794 } else if self.check(TokenType::RParen) {
11795 depth -= 1;
11796 }
11797 self.advance();
11798 }
11799 }
11800
11801 while !self.check(TokenType::LBrace) && !self.at_declaration_start() {
11803 if self.check(TokenType::Eof) {
11804 break;
11805 }
11806 self.advance();
11807 }
11808
11809 if self.check(TokenType::LBrace) {
11811 self.skip_braced_block()?;
11812 }
11813
11814 Ok(Declaration::Generic(GenericDeclaration {
11815 keyword: kw_tok.value,
11816 name,
11817 loc: Loc {
11818 line: kw_tok.line,
11819 column: kw_tok.column,
11820 },
11821 leading_trivia: Vec::new(),
11822 trailing_trivia: Vec::new(),
11823 }))
11824 }
11825
11826 fn parse_channel(&mut self) -> Result<ChannelDefinition, ParseError> {
11834 let tok = self.consume(TokenType::Channel)?;
11835 let name = self.consume(TokenType::Identifier)?.value;
11836 let mut node = ChannelDefinition {
11837 name: name.clone(),
11838 message: String::new(),
11839 qos: "at_least_once".to_string(),
11840 lifetime: "affine".to_string(),
11841 persistence: "ephemeral".to_string(),
11842 shield_ref: String::new(),
11843 loc: Loc {
11844 line: tok.line,
11845 column: tok.column,
11846 },
11847 leading_trivia: Vec::new(),
11848 trailing_trivia: Vec::new(),
11849 };
11850 self.consume(TokenType::LBrace)?;
11851 while !self.check(TokenType::RBrace) && !self.check(TokenType::Eof) {
11852 let field_tok = self.current().clone();
11853 let field_name = field_tok.value.clone();
11854 self.advance();
11855 if !self.check(TokenType::Colon) {
11856 if self.check(TokenType::LBrace) {
11857 self.skip_braced_block()?;
11858 }
11859 continue;
11860 }
11861 self.advance();
11862 match field_name.as_str() {
11863 "message" => node.message = self.parse_channel_message_type()?,
11864 "qos" => {
11865 let q_tok = self.consume_any_ident_or_kw()?;
11866 if !matches!(
11867 q_tok.value.as_str(),
11868 "at_most_once" | "at_least_once" | "exactly_once" | "broadcast" | "queue"
11869 ) {
11870 return Err(ParseError {
11871 message: format!(
11872 "Invalid qos '{}' in channel '{}' — \
11873 expected at_most_once | at_least_once | \
11874 exactly_once | broadcast | queue",
11875 q_tok.value, name
11876 ),
11877 line: q_tok.line,
11878 column: q_tok.column,
11879 ..Default::default()
11880 });
11881 }
11882 node.qos = q_tok.value;
11883 }
11884 "lifetime" => {
11885 let lt_tok = self.consume_any_ident_or_kw()?;
11886 if !matches!(lt_tok.value.as_str(), "linear" | "affine" | "persistent") {
11887 return Err(ParseError {
11888 message: format!(
11889 "Invalid lifetime '{}' in channel '{}' — \
11890 expected linear | affine | persistent",
11891 lt_tok.value, name
11892 ),
11893 line: lt_tok.line,
11894 column: lt_tok.column,
11895 ..Default::default()
11896 });
11897 }
11898 node.lifetime = lt_tok.value;
11899 }
11900 "persistence" => {
11901 let p_tok = self.consume_any_ident_or_kw()?;
11902 if !matches!(p_tok.value.as_str(), "ephemeral" | "persistent_axonstore") {
11903 return Err(ParseError {
11904 message: format!(
11905 "Invalid persistence '{}' in channel '{}' — \
11906 expected ephemeral | persistent_axonstore",
11907 p_tok.value, name
11908 ),
11909 line: p_tok.line,
11910 column: p_tok.column,
11911 ..Default::default()
11912 });
11913 }
11914 node.persistence = p_tok.value;
11915 }
11916 "shield" => node.shield_ref = self.consume_any_ident_or_kw()?.value,
11917 _ => self.skip_value(),
11918 }
11919 }
11920 self.consume(TokenType::RBrace)?;
11921 Ok(node)
11922 }
11923
11924 fn parse_channel_message_type(&mut self) -> Result<String, ParseError> {
11927 let head = self.consume(TokenType::Identifier)?;
11928 let mut spelling = head.value;
11929 if self.check(TokenType::Lt) {
11930 self.advance();
11931 let inner = self.parse_channel_message_type()?;
11932 self.consume(TokenType::Gt)?;
11933 spelling = format!("{}<{}>", spelling, inner);
11934 }
11935 Ok(spelling)
11936 }
11937
11938 fn parse_emit_step(&mut self) -> Result<FlowStep, ParseError> {
11944 let tok = self.consume(TokenType::Emit)?;
11945 let channel = self.consume(TokenType::Identifier)?.value;
11946 self.consume(TokenType::LParen)?;
11947 let value = self.parse_emit_value_ref()?;
11948 self.consume(TokenType::RParen)?;
11949 Ok(FlowStep::Emit(EmitStatement {
11950 channel_ref: channel,
11951 value_ref: value,
11952 loc: Loc {
11953 line: tok.line,
11954 column: tok.column,
11955 },
11956 }))
11957 }
11958
11959 fn parse_mint_step(&mut self) -> Result<FlowStep, ParseError> {
11965 let tok = self.consume(TokenType::Mint)?;
11966 let credential_ref = self.consume(TokenType::Identifier)?.value;
11967 self.consume(TokenType::As)?;
11968 let binding = self.consume(TokenType::Identifier)?.value;
11969 Ok(FlowStep::Mint(MintStep {
11970 credential_ref,
11971 binding,
11972 loc: Loc {
11973 line: tok.line,
11974 column: tok.column,
11975 },
11976 }))
11977 }
11978
11979 fn parse_rotate_step(&mut self) -> Result<FlowStep, ParseError> {
11994 let tok = self.consume(TokenType::Rotate)?;
11995 let store_ref = self.consume(TokenType::Identifier)?.value;
11996 let mut where_expr = String::new();
11997 if self.check(TokenType::Where) {
11998 self.advance();
11999 where_expr = self.consume(TokenType::StringLit)?.value.clone();
12000 }
12001 let with_tok = self.current().clone();
12002 if with_tok.value != "with" {
12003 return Err(ParseError {
12004 message: format!(
12005 "Expected `with <Tool>` after `rotate {store_ref}{}`, found '{}'. \
12006 A rotation names the tool that performs the renewal exchange: \
12007 `rotate {store_ref} [where \"<filter>\"] with <Tool> as <binding>`.",
12008 if where_expr.is_empty() { "" } else { " where …" },
12009 with_tok.value
12010 ),
12011 line: with_tok.line,
12012 column: with_tok.column,
12013 ..Default::default()
12014 });
12015 }
12016 self.advance();
12017 let tool_ref = self.consume(TokenType::Identifier)?.value;
12018 self.consume(TokenType::As)?;
12019 let binding = self.consume(TokenType::Identifier)?.value;
12020 Ok(FlowStep::Rotate(RotateStep {
12021 store_ref,
12022 where_expr,
12023 tool_ref,
12024 binding,
12025 loc: Loc {
12026 line: tok.line,
12027 column: tok.column,
12028 },
12029 }))
12030 }
12031
12032 fn parse_emit_value_ref(&mut self) -> Result<String, ParseError> {
12048 let head = self.consume(TokenType::Identifier)?.value;
12049 let mut parts = vec![head];
12050 while self.check(TokenType::Dot) {
12051 self.advance(); let next_tok = self.current().clone();
12053 let valid = !next_tok.value.is_empty()
12054 && next_tok.value.as_bytes()[0].is_ascii_alphabetic()
12055 || next_tok.value.starts_with('_');
12056 if !valid {
12057 return Err(ParseError {
12058 message: format!(
12059 "Expected identifier or keyword after '.' in dotted \
12060 access, found {:?}",
12061 next_tok.value
12062 ),
12063 line: next_tok.line,
12064 column: next_tok.column,
12065 ..Default::default()
12066 });
12067 }
12068 self.advance();
12069 parts.push(next_tok.value);
12070 }
12071 Ok(parts.join("."))
12072 }
12073
12074 fn parse_publish_step(&mut self) -> Result<FlowStep, ParseError> {
12076 let tok = self.consume(TokenType::Publish)?;
12077 let channel = self.consume(TokenType::Identifier)?.value;
12078 self.consume(TokenType::Within)?;
12079 let shield = self.consume(TokenType::Identifier)?.value;
12080 Ok(FlowStep::Publish(PublishStatement {
12081 channel_ref: channel,
12082 shield_ref: shield,
12083 loc: Loc {
12084 line: tok.line,
12085 column: tok.column,
12086 },
12087 }))
12088 }
12089
12090 fn parse_discover_step(&mut self) -> Result<FlowStep, ParseError> {
12092 let tok = self.consume(TokenType::Discover)?;
12093 let cap = self.consume(TokenType::Identifier)?.value;
12094 self.consume(TokenType::As)?;
12095 let alias = self.consume(TokenType::Identifier)?.value;
12096 Ok(FlowStep::Discover(DiscoverStatement {
12097 capability_ref: cap,
12098 alias,
12099 loc: Loc {
12100 line: tok.line,
12101 column: tok.column,
12102 },
12103 }))
12104 }
12105}
12106
12107#[cfg(test)]
12110mod fase13_parser_tests {
12111 use super::*;
12112 use crate::lexer::Lexer;
12113
12114 fn parse(src: &str) -> Result<Program, ParseError> {
12115 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
12116 Parser::new(tokens).parse()
12117 }
12118
12119 #[test]
12120 fn channel_full_parses() {
12121 let src = r#"channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }"#;
12122 let prog = parse(src).expect("parse");
12123 match &prog.declarations[0] {
12124 Declaration::Channel(c) => {
12125 assert_eq!(c.name, "C");
12126 assert_eq!(c.message, "Order");
12127 assert_eq!(c.qos, "at_least_once");
12128 assert_eq!(c.lifetime, "affine");
12129 assert_eq!(c.persistence, "ephemeral");
12130 assert_eq!(c.shield_ref, "Gate");
12131 }
12132 _ => panic!("expected ChannelDefinition"),
12133 }
12134 }
12135
12136 #[test]
12137 fn channel_defaults_match_paper_d1() {
12138 let prog = parse("channel C { message: Order }").expect("parse");
12139 if let Declaration::Channel(c) = &prog.declarations[0] {
12140 assert_eq!(c.qos, "at_least_once"); assert_eq!(c.lifetime, "affine"); assert_eq!(c.persistence, "ephemeral");
12143 assert_eq!(c.shield_ref, "");
12144 } else {
12145 panic!("expected ChannelDefinition");
12146 }
12147 }
12148
12149 #[test]
12150 fn channel_second_order_message_type_parses() {
12151 let prog = parse("channel C { message: Channel<Order> }").expect("parse");
12152 if let Declaration::Channel(c) = &prog.declarations[0] {
12153 assert_eq!(c.message, "Channel<Order>");
12154 } else {
12155 panic!("expected ChannelDefinition");
12156 }
12157 }
12158
12159 #[test]
12160 fn channel_nested_channel_message_type_parses() {
12161 let prog = parse("channel C { message: Channel<Channel<Order>> }").expect("parse");
12162 if let Declaration::Channel(c) = &prog.declarations[0] {
12163 assert_eq!(c.message, "Channel<Channel<Order>>");
12164 } else {
12165 panic!("expected ChannelDefinition");
12166 }
12167 }
12168
12169 #[test]
12170 fn channel_invalid_qos_rejected() {
12171 let err = parse("channel C { message: T qos: bogus }").unwrap_err();
12172 assert!(err.message.contains("Invalid qos"), "got {}", err.message);
12173 }
12174
12175 #[test]
12176 fn channel_invalid_lifetime_rejected() {
12177 let err = parse("channel C { message: T lifetime: eternal }").unwrap_err();
12178 assert!(
12179 err.message.contains("Invalid lifetime"),
12180 "got {}",
12181 err.message
12182 );
12183 }
12184
12185 #[test]
12186 fn channel_invalid_persistence_rejected() {
12187 let err = parse("channel C { message: T persistence: forever }").unwrap_err();
12188 assert!(
12189 err.message.contains("Invalid persistence"),
12190 "got {}",
12191 err.message
12192 );
12193 }
12194
12195 #[test]
12196 fn emit_value_parses() {
12197 let src = "flow f() -> Out { emit C(payload) }";
12198 let prog = parse(src).expect("parse");
12199 if let Declaration::Flow(f) = &prog.declarations[0] {
12200 match &f.body[0] {
12201 FlowStep::Emit(e) => {
12202 assert_eq!(e.channel_ref, "C");
12203 assert_eq!(e.value_ref, "payload");
12204 }
12205 other => panic!("expected Emit, got {:?}", other),
12206 }
12207 } else {
12208 panic!("expected Flow");
12209 }
12210 }
12211
12212 #[test]
12213 fn publish_within_shield_parses() {
12214 let src = "flow f() -> Cap { publish C within Gate }";
12215 let prog = parse(src).expect("parse");
12216 if let Declaration::Flow(f) = &prog.declarations[0] {
12217 match &f.body[0] {
12218 FlowStep::Publish(p) => {
12219 assert_eq!(p.channel_ref, "C");
12220 assert_eq!(p.shield_ref, "Gate");
12221 }
12222 other => panic!("expected Publish, got {:?}", other),
12223 }
12224 } else {
12225 panic!("expected Flow");
12226 }
12227 }
12228
12229 #[test]
12230 fn discover_with_alias_parses() {
12231 let src = "flow f() -> Out { discover C as ch }";
12232 let prog = parse(src).expect("parse");
12233 if let Declaration::Flow(f) = &prog.declarations[0] {
12234 match &f.body[0] {
12235 FlowStep::Discover(d) => {
12236 assert_eq!(d.capability_ref, "C");
12237 assert_eq!(d.alias, "ch");
12238 }
12239 other => panic!("expected Discover, got {:?}", other),
12240 }
12241 } else {
12242 panic!("expected Flow");
12243 }
12244 }
12245
12246 #[test]
12247 fn listen_typed_ref_sets_flag_true() {
12248 let src = "daemon D() { goal: \"x\" listen C as ev { } }";
12249 let prog = parse(src).expect("parse");
12250 if let Declaration::Daemon(d) = &prog.declarations[0] {
12251 assert_eq!(d.listeners.len(), 1);
12252 assert_eq!(d.listeners[0].channel, "C");
12253 assert!(d.listeners[0].channel_is_ref, "typed ref ⇒ true");
12254 } else {
12255 panic!("expected Daemon");
12256 }
12257 }
12258
12259 #[test]
12260 fn listen_string_topic_legacy_flag_false() {
12261 let src = "daemon D() { goal: \"x\" listen \"orders\" as ev { } }";
12262 let prog = parse(src).expect("parse");
12263 if let Declaration::Daemon(d) = &prog.declarations[0] {
12264 assert_eq!(d.listeners.len(), 1);
12265 assert_eq!(d.listeners[0].channel, "orders");
12266 assert!(!d.listeners[0].channel_is_ref, "string topic ⇒ false");
12267 } else {
12268 panic!("expected Daemon");
12269 }
12270 }
12271
12272 fn extract_first_emit(prog: &Program) -> &EmitStatement {
12275 if let Declaration::Flow(f) = &prog.declarations[0] {
12276 if let FlowStep::Emit(e) = &f.body[0] {
12277 return e;
12278 }
12279 }
12280 panic!("expected emit statement at flow body[0]");
12281 }
12282
12283 #[test]
12284 fn emit_accepts_bare_identifier_value_ref() {
12285 let prog = parse("flow f() -> Out { emit Hello(payload) }").expect("parse");
12287 let emit = extract_first_emit(&prog);
12288 assert_eq!(emit.channel_ref, "Hello");
12289 assert_eq!(emit.value_ref, "payload");
12290 }
12291
12292 #[test]
12293 fn emit_accepts_two_segment_dotted_value_ref() {
12294 let prog = parse("flow f() -> Out { emit Hello(Build.output) }").expect("parse");
12296 let emit = extract_first_emit(&prog);
12297 assert_eq!(emit.value_ref, "Build.output");
12298 }
12299
12300 #[test]
12301 fn emit_accepts_three_segment_nested_dotted_value_ref() {
12302 let prog = parse("flow f() -> Out { emit Score(Analyze.result.score) }").expect("parse");
12303 let emit = extract_first_emit(&prog);
12304 assert_eq!(emit.value_ref, "Analyze.result.score");
12305 }
12306
12307 #[test]
12308 fn emit_dotted_with_trailing_dot_fails() {
12309 let result = parse("flow f() -> Out { emit Hello(Build.) }");
12311 assert!(result.is_err(), "expected parse error for trailing dot");
12312 }
12313}
12314
12315#[cfg(test)]
12318mod fase14a_declaration_trivia_tests {
12319 use super::*;
12320 use crate::lexer::Lexer;
12321 use crate::tokens::TriviaKind;
12322
12323 fn parse(src: &str) -> Program {
12324 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
12325 Parser::new(toks).parse().expect("parse")
12326 }
12327
12328 #[test]
12329 fn no_comments_means_empty_trivia_per_decl() {
12330 let prog = parse("flow F() -> Out { }");
12331 assert_eq!(prog.declarations.len(), 1);
12332 assert_eq!(prog.declaration_trivia.len(), 1);
12333 assert!(prog.declaration_trivia[0].leading.is_empty());
12334 assert!(prog.declaration_trivia[0].trailing.is_empty());
12335 }
12336
12337 #[test]
12338 fn doc_line_comment_attaches_as_leading() {
12339 let prog = parse("/// Documents F\nflow F() -> Out { }");
12340 let triv = &prog.declaration_trivia[0];
12341 assert_eq!(triv.leading.len(), 1);
12342 assert_eq!(triv.leading[0].kind, TriviaKind::DocLine);
12343 assert!(triv.leading[0].is_doc());
12344 assert_eq!(triv.leading[0].text, "/// Documents F");
12345 }
12346
12347 #[test]
12348 fn regular_line_comment_attaches_as_leading() {
12349 let prog = parse("// header\nflow F() -> Out { }");
12350 let triv = &prog.declaration_trivia[0];
12351 assert_eq!(triv.leading.len(), 1);
12352 assert_eq!(triv.leading[0].kind, TriviaKind::Line);
12353 assert!(!triv.leading[0].is_doc());
12354 }
12355
12356 #[test]
12357 fn block_doc_comment_attaches_as_leading() {
12358 let prog = parse("/** Doc block */\nflow F() -> Out { }");
12359 let triv = &prog.declaration_trivia[0];
12360 assert_eq!(triv.leading[0].kind, TriviaKind::DocBlock);
12361 assert!(triv.leading[0].is_doc());
12362 }
12363
12364 #[test]
12365 fn multiple_comments_collected_in_source_order() {
12366 let src = "/// First\n/// Second\nflow F() -> Out { }";
12367 let prog = parse(src);
12368 let triv = &prog.declaration_trivia[0];
12369 assert_eq!(triv.leading.len(), 2);
12370 assert_eq!(triv.leading[0].text, "/// First");
12371 assert_eq!(triv.leading[1].text, "/// Second");
12372 }
12373
12374 #[test]
12375 fn three_decls_each_get_own_leading() {
12376 let src = "/// for A\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }\n/// for C\nflow C() -> Out { }";
12377 let prog = parse(src);
12378 assert_eq!(prog.declarations.len(), 3);
12379 assert_eq!(prog.declaration_trivia.len(), 3);
12380 for (idx, name) in ["A", "B", "C"].iter().enumerate() {
12381 let triv = &prog.declaration_trivia[idx];
12382 assert_eq!(triv.leading.len(), 1);
12383 assert_eq!(triv.leading[0].text, format!("/// for {name}"));
12384 }
12385 }
12386
12387 #[test]
12388 fn trailing_comment_attaches_to_last_token_of_decl() {
12389 let prog = parse("flow F() -> Out { } // tail");
12391 let triv = &prog.declaration_trivia[0];
12392 assert_eq!(triv.trailing.len(), 1);
12393 assert_eq!(triv.trailing[0].text, "// tail");
12394 }
12395
12396 #[test]
12397 fn mixed_doc_and_regular_preserve_order_between_decls() {
12398 let src = "/// doc for A\nflow A() -> Out { }\n\n// header line\n/// doc for B\nflow B() -> Out { }";
12399 let prog = parse(src);
12400 assert_eq!(prog.declarations.len(), 2);
12401 assert_eq!(prog.declaration_trivia[0].leading.len(), 1);
12403 assert_eq!(prog.declaration_trivia[1].leading.len(), 2);
12405 assert_eq!(prog.declaration_trivia[1].leading[0].text, "// header line");
12406 assert_eq!(prog.declaration_trivia[1].leading[1].text, "/// doc for B");
12407 }
12408
12409 #[test]
12410 fn parser_unaffected_by_comments_in_grammar_path() {
12411 let src =
12416 "// before flow\nflow /* between flow and name */ F() -> Out {\n // body comment\n}";
12417 let prog = parse(src);
12418 assert_eq!(prog.declarations.len(), 1);
12419 if let Declaration::Flow(f) = &prog.declarations[0] {
12420 assert_eq!(f.name, "F");
12421 } else {
12422 panic!("expected Flow declaration");
12423 }
12424 }
12425}
12426
12427#[cfg(test)]
12436mod fase14b_per_struct_trivia_tests {
12437 use super::*;
12438 use crate::lexer::Lexer;
12439 use crate::tokens::TriviaKind;
12440
12441 fn parse(src: &str) -> Program {
12442 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
12443 Parser::new(toks).parse().expect("parse")
12444 }
12445
12446 #[test]
12447 fn flow_definition_carries_leading_trivia_directly() {
12448 let prog = parse("/// documents F\nflow F() -> Out { }");
12449 if let Declaration::Flow(f) = &prog.declarations[0] {
12450 assert_eq!(f.leading_trivia.len(), 1);
12451 assert_eq!(f.leading_trivia[0].kind, TriviaKind::DocLine);
12452 assert_eq!(f.leading_trivia[0].text, "/// documents F");
12453 assert!(f.trailing_trivia.is_empty());
12454 } else {
12455 panic!("expected Flow declaration");
12456 }
12457 }
12458
12459 #[test]
12460 fn flow_definition_carries_trailing_trivia_directly() {
12461 let prog = parse("flow F() -> Out { } // tail comment");
12462 if let Declaration::Flow(f) = &prog.declarations[0] {
12463 assert_eq!(f.trailing_trivia.len(), 1);
12464 assert_eq!(f.trailing_trivia[0].text, "// tail comment");
12465 } else {
12466 panic!("expected Flow declaration");
12467 }
12468 }
12469
12470 #[test]
12471 fn channel_definition_carries_trivia_directly() {
12472 let src = concat!(
12475 "/// inbound order events\n",
12476 "channel Orders {\n",
12477 " message: Order\n",
12478 " qos: at_least_once\n",
12479 " lifetime: affine\n",
12480 " persistence: ephemeral\n",
12481 " shield: Broker\n",
12482 "}",
12483 );
12484 let prog = parse(src);
12485 if let Declaration::Channel(ch) = &prog.declarations[0] {
12486 assert_eq!(ch.leading_trivia.len(), 1);
12487 assert!(ch.leading_trivia[0].is_doc());
12488 assert_eq!(ch.leading_trivia[0].text, "/// inbound order events");
12489 } else {
12490 panic!("expected Channel declaration");
12491 }
12492 }
12493
12494 #[test]
12495 fn per_struct_fields_match_side_channel() {
12496 let src = "/// for A\n// header for B\nflow A() -> Out { }\n/// for B\nflow B() -> Out { }";
12499 let prog = parse(src);
12500 for (idx, decl) in prog.declarations.iter().enumerate() {
12501 let side = &prog.declaration_trivia[idx];
12502 let (per_lead, per_trail) = match decl {
12503 Declaration::Flow(f) => (&f.leading_trivia, &f.trailing_trivia),
12504 _ => panic!("unexpected variant"),
12505 };
12506 assert_eq!(per_lead.len(), side.leading.len());
12507 assert_eq!(per_trail.len(), side.trailing.len());
12508 for (a, b) in per_lead.iter().zip(side.leading.iter()) {
12509 assert_eq!(a.text, b.text);
12510 assert_eq!(a.kind, b.kind);
12511 }
12512 }
12513 }
12514
12515 #[test]
12516 fn comment_free_program_yields_empty_per_struct_fields() {
12517 let prog = parse("flow F() -> Out { }");
12518 if let Declaration::Flow(f) = &prog.declarations[0] {
12519 assert!(f.leading_trivia.is_empty());
12520 assert!(f.trailing_trivia.is_empty());
12521 } else {
12522 panic!("expected Flow declaration");
12523 }
12524 }
12525}
12526
12527#[cfg(test)]
12536mod fase14c_inner_doc_tests {
12537 use super::*;
12538 use crate::lexer::Lexer;
12539 use crate::tokens::TriviaKind;
12540
12541 fn parse(src: &str) -> Program {
12542 let toks = Lexer::new(src, "<test>").tokenize().expect("lex");
12543 Parser::new(toks).parse().expect("parse")
12544 }
12545
12546 #[test]
12547 fn inner_doc_line_reaches_leading_trivia() {
12548 let src = "//! file-level docs\nflow F() -> Out { }";
12549 let prog = parse(src);
12550 let triv = &prog.declaration_trivia[0];
12551 assert_eq!(triv.leading.len(), 1);
12552 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocLine);
12553 assert!(triv.leading[0].is_doc());
12554 assert!(triv.leading[0].is_inner_doc());
12555 assert_eq!(triv.leading[0].text, "//! file-level docs");
12556 assert_eq!(triv.leading[0].stripped_text(), " file-level docs");
12557 }
12558
12559 #[test]
12560 fn inner_doc_block_reaches_leading_trivia() {
12561 let src = "/*! module-level docs */\nflow F() -> Out { }";
12562 let prog = parse(src);
12563 let triv = &prog.declaration_trivia[0];
12564 assert_eq!(triv.leading.len(), 1);
12565 assert_eq!(triv.leading[0].kind, TriviaKind::InnerDocBlock);
12566 assert!(triv.leading[0].is_inner_doc());
12567 assert_eq!(triv.leading[0].stripped_text(), " module-level docs ");
12568 }
12569
12570 #[test]
12571 fn outer_and_inner_doc_can_coexist() {
12572 let src = "//! file docs\n/// docs F\nflow F() -> Out { }";
12576 let prog = parse(src);
12577 let triv = &prog.declaration_trivia[0];
12578 assert_eq!(triv.leading.len(), 2);
12579 assert!(triv.leading[0].is_inner_doc());
12580 assert!(triv.leading[1].is_doc());
12581 assert!(!triv.leading[1].is_inner_doc());
12582 }
12583
12584 #[test]
12585 fn inner_doc_reaches_per_struct_fields() {
12586 let src = "//! intro\nflow F() -> Out { }";
12588 let prog = parse(src);
12589 if let Declaration::Flow(f) = &prog.declarations[0] {
12590 assert_eq!(f.leading_trivia.len(), 1);
12591 assert!(f.leading_trivia[0].is_inner_doc());
12592 } else {
12593 panic!("expected Flow declaration");
12594 }
12595 }
12596}
12597
12598#[cfg(test)]
12616mod fase28_recovery_tests {
12617 use super::*;
12618 use crate::lexer::Lexer;
12619
12620 fn lex(src: &str) -> Vec<Token> {
12623 Lexer::new(src, "<test>").tokenize().expect("lex")
12624 }
12625
12626 fn recover(src: &str) -> ParseResult {
12629 Parser::new(lex(src)).parse_with_recovery()
12630 }
12631
12632 fn strict(src: &str) -> Result<Program, ParseError> {
12634 Parser::new(lex(src)).parse()
12635 }
12636
12637 #[test]
12640 fn strict_parse_unchanged_for_clean_source() {
12641 let src = "intent I {}";
12644 let prog = strict(src).expect("clean parse");
12645 assert_eq!(prog.declarations.len(), 1);
12646 }
12647
12648 #[test]
12649 fn strict_parse_still_raises_on_first_error() {
12650 let src = "flow F() { } not_a_keyword flow G() { }";
12655 let _ = strict(src).expect_err("must error fast in strict mode");
12656 }
12657
12658 #[test]
12659 fn recovery_clean_source_yields_no_errors() {
12660 let src = "flow F() { } flow G() { }";
12661 let pr = recover(src);
12662 assert!(pr.is_clean(), "errors: {:?}", pr.errors);
12663 assert_eq!(pr.program.declarations.len(), 2);
12664 }
12665
12666 #[test]
12669 fn single_unknown_top_level_token_recovers() {
12670 let src = "garbage_token flow F() { } flow G() { }";
12672 let pr = recover(src);
12673 assert_eq!(pr.errors.len(), 1, "errors: {:?}", pr.errors);
12674 assert_eq!(pr.program.declarations.len(), 2);
12675 }
12676
12677 #[test]
12678 fn error_in_first_decl_does_not_block_second() {
12679 let src = "flow F() { not_a_step nope } flow G() { }";
12682 let pr = recover(src);
12683 assert!(pr.has_errors(), "expected at least one error");
12684 let names: Vec<&str> = pr
12686 .program
12687 .declarations
12688 .iter()
12689 .filter_map(|d| match d {
12690 Declaration::Flow(f) => Some(f.name.as_str()),
12691 _ => None,
12692 })
12693 .collect();
12694 assert!(names.contains(&"G"), "G not found among {names:?}");
12695 }
12696
12697 #[test]
12698 fn malformed_declaration_then_clean_intent_recovers() {
12699 let src = "flow @ () { } intent I {}";
12700 let pr = recover(src);
12701 assert!(pr.has_errors());
12702 let kinds: Vec<&str> = pr
12703 .program
12704 .declarations
12705 .iter()
12706 .map(|d| match d {
12707 Declaration::Intent(_) => "intent",
12708 Declaration::Flow(_) => "flow",
12709 _ => "other",
12710 })
12711 .collect();
12712 assert!(kinds.contains(&"intent"), "kinds: {kinds:?}");
12713 }
12714
12715 #[test]
12716 fn recovery_does_not_double_count_a_single_error() {
12717 let src = "flow F() { not_a_step }";
12725 let pr = recover(src);
12726 let outer_ghosts = pr
12727 .errors
12728 .iter()
12729 .filter(|e| e.message.contains("at top level"))
12730 .count();
12731 assert_eq!(outer_ghosts, 0, "ghost errors: {:?}", pr.errors);
12732 }
12733
12734 #[test]
12737 fn three_independent_errors_yield_three_entries() {
12738 let src =
12739 "garbage1 flow F() { } garbage2 flow G() { } garbage3 flow H() { }";
12740 let pr = recover(src);
12741 assert_eq!(pr.errors.len(), 3, "errors: {:?}", pr.errors);
12742 assert_eq!(pr.program.declarations.len(), 3);
12743 }
12744
12745 #[test]
12746 fn all_errors_no_valid_declarations() {
12747 let src = "foo bar baz qux";
12748 let pr = recover(src);
12749 assert!(pr.has_errors());
12750 assert!(pr.program.declarations.is_empty());
12751 }
12752
12753 #[test]
12754 fn errors_recorded_in_source_order() {
12755 let src = "x flow A() { } y flow B() { } z flow C() { }";
12756 let pr = recover(src);
12757 assert_eq!(pr.errors.len(), 3);
12758 let lines: Vec<u32> = pr.errors.iter().map(|e| e.line).collect();
12759 assert!(
12762 lines.windows(2).all(|w| w[0] <= w[1]),
12763 "errors out of order: {lines:?}"
12764 );
12765 }
12766
12767 #[test]
12770 fn sync_to_flow_keyword() {
12771 let src = "garbage flow F() { }";
12772 let pr = recover(src);
12773 assert_eq!(pr.program.declarations.len(), 1);
12774 }
12775
12776 #[test]
12777 fn sync_to_intent_keyword() {
12778 let src = "garbage intent I {}";
12779 let pr = recover(src);
12780 assert_eq!(pr.program.declarations.len(), 1);
12781 }
12782
12783 #[test]
12784 fn sync_to_persona_keyword() {
12785 let src = "garbage persona P { name: \"P\" role: \"R\" }";
12786 let pr = recover(src);
12787 assert!(
12788 pr.program
12789 .declarations
12790 .iter()
12791 .any(|d| matches!(d, Declaration::Persona(_))),
12792 "persona not recovered: decls = {:?}",
12793 pr.program.declarations.len()
12794 );
12795 }
12796
12797 #[test]
12798 fn sync_to_run_keyword() {
12799 let src = "garbage run R { input: { user_message: \"hi\" } }";
12800 let pr = recover(src);
12801 assert!(pr.has_errors());
12803 }
12804
12805 #[test]
12808 fn parse_result_has_errors_and_is_clean_invert() {
12809 let pr_clean = recover("flow F() { }");
12810 assert!(pr_clean.is_clean());
12811 assert!(!pr_clean.has_errors());
12812
12813 let pr_err = recover("garbage");
12814 assert!(!pr_err.is_clean());
12815 assert!(pr_err.has_errors());
12816 }
12817
12818 #[test]
12819 fn parse_result_program_field_holds_partial_program() {
12820 let pr = recover("garbage flow F() { }");
12821 assert!(!pr.program.declarations.is_empty());
12822 }
12823
12824 #[test]
12825 fn parse_result_errors_carry_line_and_column() {
12826 let pr = recover("garbage");
12827 assert!(!pr.errors.is_empty());
12828 let e = &pr.errors[0];
12829 assert!(e.line >= 1);
12830 let _ = e.column;
12833 assert!(!e.message.is_empty());
12834 }
12835
12836 #[test]
12837 fn parse_result_debug_renders() {
12838 let pr = recover("flow F() { }");
12839 let s = format!("{pr:?}");
12840 assert!(s.contains("ParseResult"));
12841 }
12842
12843 #[test]
12846 fn empty_source_is_clean() {
12847 let pr = recover("");
12848 assert!(pr.is_clean());
12849 assert!(pr.program.declarations.is_empty());
12850 }
12851
12852 #[test]
12853 fn whitespace_only_source_is_clean() {
12854 let pr = recover(" \n\n\t \n");
12855 assert!(pr.is_clean());
12856 assert!(pr.program.declarations.is_empty());
12857 }
12858
12859 #[test]
12860 fn only_garbage_does_not_crash() {
12861 let pr = recover("foo bar baz { qux quux } corge { grault }");
12863 assert!(pr.has_errors());
12864 }
12865
12866 #[test]
12867 fn unbalanced_close_brace_does_not_crash() {
12868 let pr = recover("} flow F() { }");
12869 let names: Vec<&str> = pr
12871 .program
12872 .declarations
12873 .iter()
12874 .filter_map(|d| match d {
12875 Declaration::Flow(f) => Some(f.name.as_str()),
12876 _ => None,
12877 })
12878 .collect();
12879 assert!(names.contains(&"F"), "F not recovered: {names:?}");
12880 }
12881
12882 #[test]
12883 fn error_at_eof_does_not_loop() {
12884 let pr = recover("flow F() { ");
12886 let _ = pr.errors.len();
12888 }
12889
12890 #[test]
12891 fn nested_braces_inside_error_still_balance() {
12892 let src = "flow F() { not_a_step { inner } } flow G() { }";
12895 let pr = recover(src);
12896 let names: Vec<&str> = pr
12897 .program
12898 .declarations
12899 .iter()
12900 .filter_map(|d| match d {
12901 Declaration::Flow(f) => Some(f.name.as_str()),
12902 _ => None,
12903 })
12904 .collect();
12905 assert!(names.contains(&"G"), "G not recovered: {names:?}");
12906 }
12907
12908 #[derive(Clone, Copy)]
12917 struct Xorshift(u64);
12918 impl Xorshift {
12919 fn next(&mut self) -> u64 {
12920 let mut x = self.0;
12921 x ^= x << 13;
12922 x ^= x >> 7;
12923 x ^= x << 17;
12924 self.0 = x;
12925 x
12926 }
12927 fn pick<T: Copy>(&mut self, slice: &[T]) -> T {
12928 slice[(self.next() as usize) % slice.len()]
12929 }
12930 }
12931
12932 fn mutate(src: &str, rng: &mut Xorshift) -> String {
12933 let mut bytes: Vec<u8> = src.bytes().collect();
12934 if bytes.is_empty() {
12935 return src.to_string();
12936 }
12937 let op = rng.next() % 4;
12938 let pos = (rng.next() as usize) % bytes.len();
12939 let safe: &[u8] = b"abcdefghijklmnopqrstuvwxyz {}();:,_0123456789";
12943 match op {
12944 0 => {
12945 bytes.remove(pos);
12946 }
12947 1 => {
12948 let b = rng.pick(safe);
12949 bytes.insert(pos, b);
12950 }
12951 2 if pos + 1 < bytes.len() => {
12952 bytes.swap(pos, pos + 1);
12953 }
12954 _ => {
12955 let b = rng.pick(safe);
12956 bytes[pos] = b;
12957 }
12958 }
12959 bytes.retain(|b| b.is_ascii());
12962 String::from_utf8_lossy(&bytes).into_owned()
12963 }
12964
12965 #[test]
12966 fn fuzz_recovery_never_crashes() {
12967 let seed_bases = [
12968 "flow F() { }",
12969 "intent I { }",
12970 "persona P { name: \"P\" role: \"R\" }",
12971 "intent J { ask: \"a\" }",
12972 "type T = String",
12973 ];
12974 for (bucket, base) in (0..100u64).zip(seed_bases.iter().cycle()) {
12976 let mut rng = Xorshift(0x1234_5678_9abc_def0_u64.wrapping_add(bucket));
12977 let mut current = (*base).to_string();
12978 for _ in 0..10 {
12979 current = mutate(¤t, &mut rng);
12980 let toks = match Lexer::new(¤t, "<fuzz>").tokenize() {
12983 Ok(t) => t,
12984 Err(_) => continue,
12985 };
12986 let _pr = Parser::new(toks).parse_with_recovery();
12988 }
12989 }
12990 }
12991
12992 #[test]
12995 fn missing_colon_hint_preserved_under_recovery() {
12996 let src = "flow F() { run R { input { user_message: \"hi\" } } }";
13000 let pr = recover(src);
13001 if !pr.errors.is_empty() {
13005 let any_msg = pr.errors.iter().any(|e| !e.message.is_empty());
13006 assert!(any_msg);
13007 }
13008 }
13009
13010 #[test]
13013 fn recovered_declarations_appear_in_source_order() {
13014 let src = "flow A() { } garbage flow B() { } garbage flow C() { }";
13015 let pr = recover(src);
13016 let names: Vec<&str> = pr
13017 .program
13018 .declarations
13019 .iter()
13020 .filter_map(|d| match d {
13021 Declaration::Flow(f) => Some(f.name.as_str()),
13022 _ => None,
13023 })
13024 .collect();
13025 assert_eq!(names, vec!["A", "B", "C"]);
13026 }
13027}
13028
13029#[cfg(test)]
13037mod fase28_source_context_tests {
13038 use super::*;
13039 use crate::lexer::Lexer;
13040
13041 fn snippet(source: &str, line: u32, column: u32, filename: &str) -> String {
13042 SourceSnippet::new(
13043 source.to_string(),
13044 line,
13045 column,
13046 filename.to_string(),
13047 )
13048 .render()
13049 }
13050
13051 #[test]
13054 fn rustc_style_block_for_middle_line() {
13055 let src = "line one\nline two\nline three\nline four\nline five";
13056 let out = snippet(src, 3, 6, "x.axon");
13057 assert!(out.contains("--> x.axon:3:6"));
13058 assert!(out.contains("1 | line one"));
13059 assert!(out.contains("2 | line two"));
13060 assert!(out.contains("3 | line three"));
13061 assert!(out.contains("4 | line four"));
13062 assert!(out.contains("5 | line five"));
13063 assert!(out.contains("\n | ^"), "out:\n{out}");
13065 }
13066
13067 #[test]
13068 fn caret_column_one_renders_correctly() {
13069 let out = snippet("abc\n", 1, 1, "<source>");
13070 assert!(out.contains("\n | ^"));
13071 }
13072
13073 #[test]
13074 fn first_line_clamps_context_before_to_zero() {
13075 let src = "first\nsecond\nthird\nfourth\nfifth";
13076 let out = snippet(src, 1, 1, "<source>");
13077 assert!(out.contains("1 | first"));
13078 assert!(out.contains("2 | second"));
13079 assert!(out.contains("3 | third"));
13080 assert!(!out.contains("4 | fourth"));
13081 }
13082
13083 #[test]
13084 fn last_line_clamps_context_after_to_eof() {
13085 let src = "first\nsecond\nthird\nfourth\nfifth";
13086 let out = snippet(src, 5, 2, "<source>");
13087 assert!(out.contains("5 | fifth"));
13088 assert!(out.contains("3 | third"));
13089 assert!(out.contains("4 | fourth"));
13090 assert!(!out.contains("2 | second"));
13091 }
13092
13093 #[test]
13094 fn gutter_width_grows_with_line_count() {
13095 let src: String = (1..=12).map(|i| format!("line{i}")).collect::<Vec<_>>().join("\n");
13096 let out = snippet(&src, 12, 1, "<source>");
13097 assert!(out.contains("12 | line12"));
13098 assert!(out.contains("10 | line10"));
13099 }
13100
13101 #[test]
13104 fn empty_source_returns_empty() {
13105 assert_eq!(snippet("", 1, 1, "<source>"), "");
13106 }
13107
13108 #[test]
13109 fn zero_line_returns_empty() {
13110 assert_eq!(snippet("hi", 0, 1, "<source>"), "");
13111 }
13112
13113 #[test]
13114 fn out_of_range_line_returns_empty() {
13115 assert_eq!(snippet("hi", 99, 1, "<source>"), "");
13116 }
13117
13118 #[test]
13119 fn caret_clamps_past_eol() {
13120 let out = snippet("hello", 1, 50, "<source>");
13121 assert!(out.contains("\n | ^"), "out:\n{out}");
13122 }
13123
13124 #[test]
13125 fn unicode_codepoint_count_for_caret_clamp() {
13126 let out = snippet("héllo", 1, 99, "<source>");
13128 assert!(out.contains("\n | ^"), "out:\n{out}");
13129 }
13130
13131 #[test]
13132 fn trailing_newline_does_not_create_phantom_last_line() {
13133 let out = snippet("first\nsecond\n", 2, 1, "<source>");
13134 assert!(!out.contains("3 |"));
13135 assert!(out.contains("2 | second"));
13136 }
13137
13138 fn lex(src: &str) -> Vec<Token> {
13141 Lexer::new(src, "<test>").tokenize().expect("lex")
13142 }
13143
13144 #[test]
13145 fn strict_parse_attaches_snippet_when_source_given() {
13146 let src = "garbage_token\nflow F() { }";
13147 let err = Parser::new(lex(src))
13148 .with_source(src, "x.axon")
13149 .parse()
13150 .expect_err("must error");
13151 assert!(err.source_snippet.is_some());
13152 let display = format!("{err}");
13153 assert!(display.contains("--> x.axon:"), "display: {display}");
13154 }
13155
13156 #[test]
13157 fn strict_parse_no_snippet_when_no_source() {
13158 let src = "garbage_token";
13159 let err = Parser::new(lex(src)).parse().expect_err("must error");
13160 assert!(err.source_snippet.is_none());
13161 let display = format!("{err}");
13162 assert!(!display.contains("\n -->"));
13163 }
13164
13165 #[test]
13166 fn every_recovered_error_has_snippet() {
13167 let src = "garbage1\nflow F() { }\ngarbage2\nflow G() { }";
13168 let result = Parser::new(lex(src))
13169 .with_source(src, "multi.axon")
13170 .parse_with_recovery();
13171 assert!(!result.errors.is_empty());
13172 for err in &result.errors {
13173 assert!(err.source_snippet.is_some());
13174 let display = format!("{err}");
13175 assert!(
13176 display.contains("--> multi.axon:"),
13177 "display: {display}"
13178 );
13179 }
13180 }
13181
13182 #[test]
13183 fn recovery_no_snippet_when_no_source() {
13184 let src = "garbage1 garbage2";
13185 let result = Parser::new(lex(src)).parse_with_recovery();
13186 for err in &result.errors {
13187 assert!(err.source_snippet.is_none());
13188 }
13189 }
13190
13191 #[test]
13192 fn snippet_points_at_correct_line_for_each_error() {
13193 let src = "garbage_a\nflow F() { }\ngarbage_b\nflow G() { }";
13194 let result = Parser::new(lex(src))
13195 .with_source(src, "x")
13196 .parse_with_recovery();
13197 for err in &result.errors {
13198 let sn = err.source_snippet.as_ref().expect("snippet");
13199 assert_eq!(sn.line, err.line);
13200 }
13201 }
13202
13203 #[test]
13206 fn legacy_constructor_still_works() {
13207 let src = "flow F() { }";
13208 let prog = Parser::new(lex(src)).parse().expect("clean");
13209 assert_eq!(prog.declarations.len(), 1);
13210 }
13211
13212 #[test]
13213 fn attach_source_idempotent() {
13214 let err = ParseError {
13215 message: "bad".to_string(),
13216 line: 2,
13217 column: 3,
13218 ..Default::default()
13219 };
13220 let err2 = err.clone().attach_source("a\nb\nc\n", "f.axon");
13221 let first = format!("{err2}");
13222 let err3 = err.attach_source("a\nb\nc\n", "f.axon");
13223 let second = format!("{err3}");
13224 assert_eq!(first, second);
13225 }
13226
13227 #[test]
13228 fn attach_source_noop_when_line_zero() {
13229 let err = ParseError {
13230 message: "bad".to_string(),
13231 line: 0,
13232 column: 0,
13233 ..Default::default()
13234 };
13235 let err = err.attach_source("a\nb\nc\n", "f.axon");
13236 assert!(err.source_snippet.is_none());
13237 }
13238
13239 #[test]
13245 fn golden_simple_three_line_block() {
13246 let src = "alpha\nbeta\ngamma";
13247 let out = snippet(src, 2, 3, "g.axon");
13248 let expected = concat!(
13252 " --> g.axon:2:3\n",
13253 " |\n",
13254 "1 | alpha\n",
13255 "2 | beta\n",
13256 " | ^\n",
13257 "3 | gamma",
13258 );
13259 assert_eq!(out, expected);
13260 }
13261
13262 #[test]
13263 fn golden_first_line_caret() {
13264 let src = "abc\ndef\n";
13265 let out = snippet(src, 1, 1, "x");
13266 let expected = concat!(
13267 " --> x:1:1\n",
13268 " |\n",
13269 "1 | abc\n",
13270 " | ^\n",
13271 "2 | def",
13272 );
13273 assert_eq!(out, expected);
13274 }
13275
13276 #[test]
13277 fn golden_two_digit_gutter() {
13278 let src: String = (1..=11)
13279 .map(|i| format!("L{i}"))
13280 .collect::<Vec<_>>()
13281 .join("\n");
13282 let out = snippet(&src, 10, 2, "big");
13283 let expected = concat!(
13284 " --> big:10:2\n",
13285 " |\n",
13286 " 8 | L8\n",
13287 " 9 | L9\n",
13288 "10 | L10\n",
13289 " | ^\n",
13290 "11 | L11",
13291 );
13292 assert_eq!(out, expected);
13293 }
13294}
13295
13296#[cfg(test)]
13303mod fase28_smart_suggest_parser_tests {
13304 use super::*;
13305 use crate::lexer::Lexer;
13306
13307 fn lex(src: &str) -> Vec<Token> {
13308 Lexer::new(src, "<test>").tokenize().expect("lex")
13309 }
13310
13311 #[test]
13312 fn top_level_typo_suggests_flow() {
13313 let src = "flwo F() { }";
13314 let err = Parser::new(lex(src)).parse().expect_err("must error");
13315 assert!(
13316 err.message.contains("Did you mean `flow`?"),
13317 "msg: {}",
13318 err.message
13319 );
13320 }
13321
13322 #[test]
13323 fn top_level_unknown_far_no_suggestion() {
13324 let src = "qwerty F() { }";
13325 let err = Parser::new(lex(src)).parse().expect_err("must error");
13326 assert!(
13327 !err.message.contains("Did you mean"),
13328 "msg: {}",
13329 err.message
13330 );
13331 }
13332
13333 #[test]
13334 fn flow_body_typo_suggests_step() {
13335 let src = "flow F() { stepp S {} }";
13336 let err = Parser::new(lex(src)).parse().expect_err("must error");
13337 assert!(
13338 err.message.contains("Did you mean `step`"),
13339 "msg: {}",
13340 err.message
13341 );
13342 }
13343
13344 #[test]
13345 fn flow_body_typo_suggests_reason() {
13346 let src = "flow F() { reasn R {} }";
13347 let err = Parser::new(lex(src)).parse().expect_err("must error");
13348 assert!(
13349 err.message.contains("Did you mean `reason`?"),
13350 "msg: {}",
13351 err.message
13352 );
13353 }
13354
13355 #[test]
13356 fn recovery_mode_carries_hint() {
13357 let src = "flwo F() { }";
13358 let result = Parser::new(lex(src)).parse_with_recovery();
13359 assert!(
13360 result
13361 .errors
13362 .iter()
13363 .any(|e| e.message.contains("Did you mean `flow`?")),
13364 "errors: {:?}",
13365 result.errors
13366 );
13367 }
13368}
13369
13370#[cfg(test)]
13373mod fase35m_mutate_purge_where_tests {
13374 use super::*;
13375
13376 fn parse(src: &str) -> Program {
13377 let tokens = crate::lexer::Lexer::new(src, "<test>")
13378 .tokenize()
13379 .expect("lex");
13380 Parser::new(tokens).parse().expect("parse")
13381 }
13382
13383 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
13384 for d in &prog.declarations {
13385 if let Declaration::Flow(f) = d {
13386 if f.name == flow {
13387 return f.body.first().expect("flow has at least one step");
13388 }
13389 }
13390 }
13391 panic!("flow `{flow}` not found");
13392 }
13393
13394 #[test]
13395 fn mutate_captures_its_where_clause() {
13396 let prog =
13399 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
13400 match first_step(&prog, "F") {
13401 FlowStep::Mutate(m) => {
13402 assert_eq!(m.store_name, "accounts");
13403 assert_eq!(m.where_expr, "id = 1");
13404 }
13405 other => panic!("expected Mutate, got {other:?}"),
13406 }
13407 }
13408
13409 #[test]
13410 fn purge_captures_its_where_clause() {
13411 let prog =
13412 parse("flow F() -> Unit { purge logs { where: \"ts < 100\" } }");
13413 match first_step(&prog, "F") {
13414 FlowStep::Purge(p) => {
13415 assert_eq!(p.store_name, "logs");
13416 assert_eq!(p.where_expr, "ts < 100");
13417 }
13418 other => panic!("expected Purge, got {other:?}"),
13419 }
13420 }
13421
13422 #[test]
13423 fn mutate_without_a_where_block_is_a_whole_store_op() {
13424 let prog = parse("flow F() -> Unit { mutate accounts }");
13427 match first_step(&prog, "F") {
13428 FlowStep::Mutate(m) => {
13429 assert_eq!(m.store_name, "accounts");
13430 assert_eq!(m.where_expr, "");
13431 }
13432 other => panic!("expected Mutate, got {other:?}"),
13433 }
13434 }
13435}
13436
13437#[cfg(test)]
13440mod fase35o_persist_fields_tests {
13441 use super::*;
13442
13443 fn parse(src: &str) -> Program {
13444 let tokens = crate::lexer::Lexer::new(src, "<test>")
13445 .tokenize()
13446 .expect("lex");
13447 Parser::new(tokens).parse().expect("parse")
13448 }
13449
13450 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
13451 for d in &prog.declarations {
13452 if let Declaration::Flow(f) = d {
13453 if f.name == flow {
13454 return f.body.first().expect("flow has at least one step");
13455 }
13456 }
13457 }
13458 panic!("flow `{flow}` not found");
13459 }
13460
13461 #[test]
13462 fn persist_captures_its_field_block() {
13463 let prog = parse(
13467 "flow F() -> Unit { persist into chat_history { \
13468 session_id: \"${session_id}\" sender: \"user\" \
13469 content: \"${message}\" } }",
13470 );
13471 match first_step(&prog, "F") {
13472 FlowStep::Persist(p) => {
13473 assert_eq!(p.store_name, "chat_history");
13474 assert_eq!(
13475 p.fields,
13476 vec![
13477 ("session_id".to_string(), "${session_id}".to_string()),
13478 ("sender".to_string(), "user".to_string()),
13479 ("content".to_string(), "${message}".to_string()),
13480 ]
13481 );
13482 }
13483 other => panic!("expected Persist, got {other:?}"),
13484 }
13485 }
13486
13487 #[test]
13488 fn persist_without_a_block_keeps_the_user_bindings_fallback() {
13489 let prog = parse("flow F() -> Unit { persist events }");
13492 match first_step(&prog, "F") {
13493 FlowStep::Persist(p) => {
13494 assert_eq!(p.store_name, "events");
13495 assert!(p.fields.is_empty());
13496 }
13497 other => panic!("expected Persist, got {other:?}"),
13498 }
13499 }
13500
13501 #[test]
13502 fn persist_accepts_the_optional_into_connector() {
13503 let with =
13506 parse("flow F() -> Unit { persist into accounts { id: \"1\" } }");
13507 let without =
13508 parse("flow F() -> Unit { persist accounts { id: \"1\" } }");
13509 for prog in [&with, &without] {
13510 match first_step(prog, "F") {
13511 FlowStep::Persist(p) => assert_eq!(p.store_name, "accounts"),
13512 other => panic!("expected Persist, got {other:?}"),
13513 }
13514 }
13515 }
13516
13517 #[test]
13518 fn persist_into_without_a_block_resolves_the_store_name() {
13519 let prog = parse("flow F() -> Unit { persist into events }");
13522 match first_step(&prog, "F") {
13523 FlowStep::Persist(p) => {
13524 assert_eq!(p.store_name, "events");
13525 assert!(p.fields.is_empty());
13526 }
13527 other => panic!("expected Persist, got {other:?}"),
13528 }
13529 }
13530
13531 #[test]
13532 fn persist_fields_lower_into_the_ir() {
13533 let prog = parse(
13536 "flow F() -> Unit { persist into chat { content: \"${msg}\" } }",
13537 );
13538 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
13539 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
13540 match flow.steps.first().expect("one step") {
13541 crate::ir_nodes::IRFlowNode::Persist(p) => {
13542 assert_eq!(p.store_name, "chat");
13543 assert_eq!(
13544 p.fields,
13545 vec![("content".to_string(), "${msg}".to_string())]
13546 );
13547 }
13548 other => panic!("expected IRFlowNode::Persist, got {other:?}"),
13549 }
13550 }
13551}
13552
13553#[cfg(test)]
13556mod fase35p_mutate_fields_tests {
13557 use super::*;
13558
13559 fn parse(src: &str) -> Program {
13560 let tokens = crate::lexer::Lexer::new(src, "<test>")
13561 .tokenize()
13562 .expect("lex");
13563 Parser::new(tokens).parse().expect("parse")
13564 }
13565
13566 fn first_step<'a>(prog: &'a Program, flow: &str) -> &'a FlowStep {
13567 for d in &prog.declarations {
13568 if let Declaration::Flow(f) = d {
13569 if f.name == flow {
13570 return f.body.first().expect("flow has at least one step");
13571 }
13572 }
13573 }
13574 panic!("flow `{flow}` not found");
13575 }
13576
13577 #[test]
13578 fn mutate_captures_its_set_field_block() {
13579 let prog = parse(
13583 "flow F() -> Unit { mutate accounts { where: \"id = ${id}\" \
13584 balance: \"${new_balance}\" status: \"active\" } }",
13585 );
13586 match first_step(&prog, "F") {
13587 FlowStep::Mutate(m) => {
13588 assert_eq!(m.store_name, "accounts");
13589 assert_eq!(m.where_expr, "id = ${id}");
13590 assert_eq!(
13591 m.fields,
13592 vec![
13593 ("balance".to_string(), "${new_balance}".to_string()),
13594 ("status".to_string(), "active".to_string()),
13595 ]
13596 );
13597 }
13598 other => panic!("expected Mutate, got {other:?}"),
13599 }
13600 }
13601
13602 #[test]
13603 fn mutate_where_only_block_has_no_set_fields() {
13604 let prog =
13607 parse("flow F() -> Unit { mutate accounts { where: \"id = 1\" } }");
13608 match first_step(&prog, "F") {
13609 FlowStep::Mutate(m) => {
13610 assert_eq!(m.where_expr, "id = 1");
13611 assert!(m.fields.is_empty());
13612 }
13613 other => panic!("expected Mutate, got {other:?}"),
13614 }
13615 }
13616
13617 #[test]
13618 fn mutate_with_no_block_is_a_whole_store_op() {
13619 let prog = parse("flow F() -> Unit { mutate accounts }");
13622 match first_step(&prog, "F") {
13623 FlowStep::Mutate(m) => {
13624 assert_eq!(m.store_name, "accounts");
13625 assert_eq!(m.where_expr, "");
13626 assert!(m.fields.is_empty());
13627 }
13628 other => panic!("expected Mutate, got {other:?}"),
13629 }
13630 }
13631
13632 #[test]
13633 fn mutate_fields_lower_into_the_ir() {
13634 let prog = parse(
13635 "flow F() -> Unit { mutate t { where: \"id = 1\" v: \"${x}\" } }",
13636 );
13637 let ir = crate::ir_generator::IRGenerator::new().generate(&prog);
13638 let flow = ir.flows.iter().find(|f| f.name == "F").expect("flow F");
13639 match flow.steps.first().expect("one step") {
13640 crate::ir_nodes::IRFlowNode::Mutate(m) => {
13641 assert_eq!(m.where_expr, "id = 1");
13642 assert_eq!(
13643 m.fields,
13644 vec![("v".to_string(), "${x}".to_string())]
13645 );
13646 }
13647 other => panic!("expected IRFlowNode::Mutate, got {other:?}"),
13648 }
13649 }
13650}
13651