1use crate::graph::Graph;
2use pest_derive::Parser;
3use std::path::PathBuf;
4
5#[derive(Parser)]
6#[grammar = "grammar/sea.pest"]
7pub struct SeaParser;
8
9pub mod ast;
10pub mod error;
11pub mod lint;
12pub mod printer;
13pub mod profiles;
14pub mod string_utils;
15
16pub use ast::parse_expression_from_str;
17pub use ast::parse_source;
18pub use ast::Ast;
19pub use ast::AstNode;
20pub use error::{ParseError, ParseResult};
21pub use lint::*;
22pub use printer::PrettyPrinter;
23pub use profiles::{Profile, ProfileRegistry};
24pub use string_utils::unescape_string;
25
26#[derive(Debug, Clone, Default)]
31pub struct ParseOptions {
32 pub default_namespace: Option<String>,
35 pub namespace_registry: Option<crate::registry::NamespaceRegistry>,
37 pub entry_path: Option<PathBuf>,
39 pub active_profile: Option<String>,
41 pub tolerate_profile_warnings: bool,
43}
44
45pub fn parse(source: &str) -> ParseResult<Ast> {
47 ast::parse_source(source)
48}
49
50pub fn parse_to_graph(source: &str) -> ParseResult<Graph> {
52 let ast = parse(source)?;
53 ast::ast_to_graph_with_options(ast, &ParseOptions::default())
54}
55
56pub fn parse_to_graph_with_options(source: &str, options: &ParseOptions) -> ParseResult<Graph> {
79 match (&options.namespace_registry, &options.entry_path) {
80 (Some(registry), Some(path)) => {
81 let mut resolver = crate::module::resolver::ModuleResolver::new(registry)?;
82 let ast = resolver.validate_entry(path, source)?;
83 resolver.validate_dependencies(path, &ast)?;
84 ast::ast_to_graph_with_options(ast, options)
85 }
86 (Some(_), None) => Err(ParseError::Validation(
87 "Namespace registry provided without entry path".to_string(),
88 )),
89 _ => {
90 if let Some(path) = &options.entry_path {
91 log::warn!(
92 "Entry path '{}' provided without namespace registry; module resolution skipped",
93 path.display()
94 );
95 }
96 let ast = parse(source)?;
97 ast::ast_to_graph_with_options(ast, options)
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_entity_declaration_syntax() {
108 let source = r#"
109 Entity "Warehouse A" in logistics
110 "#;
111
112 let result = parse(source);
113 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
114 }
115
116 #[test]
117 fn test_resource_declaration_syntax() {
118 let source = r#"
119 Resource "Camera Units" units in inventory
120 "#;
121
122 let result = parse(source);
123 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
124 }
125
126 #[test]
127 fn test_flow_declaration_syntax() {
128 let source = r#"
129 Flow "Camera Units" from "Warehouse" to "Factory" quantity 100
130 "#;
131
132 let result = parse(source);
133 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
134 }
135
136 #[test]
137 fn test_policy_simple_syntax() {
138 let source = r#"
139 Policy check_quantity as: Flow.quantity > 0
140 "#;
141
142 let result = parse(source);
143 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
144 }
145
146 #[test]
147 fn test_complex_policy_syntax() {
148 let source = r#"
149 Policy flow_constraints as:
150 (Flow.quantity > 0) and (Entity.name != "")
151 "#;
152
153 let result = parse(source);
154 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
155 }
156
157 #[test]
158 fn test_nested_expressions() {
159 let source = r#"
160 Policy multi_condition as:
161 (A or B) and (C or (D and E))
162 "#;
163
164 let result = parse(source);
165 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
166 }
167
168 #[test]
169 fn test_comments_ignored() {
170 let source = r#"
171 // This is a comment
172 Entity "Test" in domain
173 // Another comment
174 "#;
175
176 let result = parse(source);
177 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
178 }
179
180 #[test]
181 fn test_multiple_declarations() {
182 let source = r#"
183 Entity "Warehouse" in logistics
184 Resource "Cameras" units
185 Flow "Cameras" from "Warehouse" to "Factory" quantity 50
186 "#;
187
188 let result = parse(source);
189 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
190 }
191}