1pub mod acp_ambient_globals;
2pub mod analysis;
3mod ast;
4pub mod ast_json;
5pub mod builtin_signatures;
6pub mod const_eval;
7pub mod diagnostic;
8pub mod diagnostic_codes;
9pub mod harness_methods;
10mod parser;
11pub mod stdlib_metadata;
12pub mod typechecker;
13pub mod visit;
14
15pub use ast::*;
16pub use diagnostic_codes::{
17 Category as DiagnosticCodeCategory, Code as DiagnosticCode, ParseRepairSafetyError, Repair,
18 RepairId, RepairSafety, RepairTemplate, REPAIR_REGISTRY,
19};
20pub use parser::*;
21pub use stdlib_metadata::{
22 parse_for_span as parse_stdlib_metadata, synthesize_example, StdlibMetadata,
23};
24pub use typechecker::{
25 block_definitely_exits, format_type, stmt_definitely_exits, DiagnosticDetails,
26 DiagnosticSeverity, InlayHintInfo, TypeChecker, TypeDiagnostic,
27};
28
29pub use builtin_signatures::install_builtin_signatures;
30
31pub fn is_known_builtin(name: &str) -> bool {
33 builtin_signatures::is_builtin(name)
34}
35
36pub fn known_builtin_names() -> impl Iterator<Item = &'static str> {
39 builtin_signatures::iter_builtin_names()
40}
41
42pub fn known_builtin_metadata() -> impl Iterator<Item = builtin_signatures::BuiltinMetadata> {
43 builtin_signatures::iter_builtin_metadata()
44}
45
46pub fn static_signature_names() -> impl Iterator<Item = &'static str> {
51 builtin_signatures::static_signature_names()
52}
53
54#[derive(Debug)]
57pub enum PipelineError {
58 Lex(harn_lexer::LexerError),
59 Parse(ParserError),
60 TypeCheck(Box<TypeDiagnostic>),
63}
64
65impl std::fmt::Display for PipelineError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 PipelineError::Lex(e) => e.fmt(f),
69 PipelineError::Parse(e) => e.fmt(f),
70 PipelineError::TypeCheck(diag) => write!(f, "type error: {}", diag.message),
71 }
72 }
73}
74
75impl std::error::Error for PipelineError {}
76
77impl From<harn_lexer::LexerError> for PipelineError {
78 fn from(e: harn_lexer::LexerError) -> Self {
79 PipelineError::Lex(e)
80 }
81}
82
83impl From<ParserError> for PipelineError {
84 fn from(e: ParserError) -> Self {
85 PipelineError::Parse(e)
86 }
87}
88
89impl PipelineError {
90 pub fn span(&self) -> Option<&harn_lexer::Span> {
92 match self {
93 PipelineError::Lex(e) => match e {
94 harn_lexer::LexerError::UnexpectedCharacter(_, span)
95 | harn_lexer::LexerError::UnterminatedString(span)
96 | harn_lexer::LexerError::IntegerLiteralOutOfRange(_, span)
97 | harn_lexer::LexerError::UnterminatedBlockComment(span) => Some(span),
98 },
99 PipelineError::Parse(e) => match e {
100 ParserError::Unexpected { span, .. } => Some(span),
101 ParserError::UnexpectedEof { span, .. } => Some(span),
102 },
103 PipelineError::TypeCheck(diag) => diag.span.as_ref(),
104 }
105 }
106}
107
108pub fn parse_source(source: &str) -> Result<Vec<SNode>, PipelineError> {
110 let mut lexer = harn_lexer::Lexer::new(source);
111 let tokens = lexer.tokenize()?;
112 let mut parser = Parser::new(tokens);
113 Ok(parser.parse()?)
114}
115
116pub fn check_source(source: &str) -> Result<(Vec<SNode>, Vec<TypeDiagnostic>), PipelineError> {
119 let program = parse_source(source)?;
120 let diagnostics = TypeChecker::new().check_with_source(&program, source);
121 Ok((program, diagnostics))
122}
123
124pub fn check_source_strict(source: &str) -> Result<Vec<SNode>, PipelineError> {
126 let (program, diagnostics) = check_source(source)?;
127 for diag in &diagnostics {
128 if diag.severity == DiagnosticSeverity::Error {
129 return Err(PipelineError::TypeCheck(Box::new(diag.clone())));
130 }
131 }
132 Ok(program)
133}
134
135#[cfg(test)]
136mod pipeline_tests {
137 use super::*;
138
139 #[test]
140 fn parse_source_valid() {
141 let program = parse_source("const x = 1").unwrap();
142 assert!(!program.is_empty());
143 }
144
145 #[test]
146 fn parse_source_lex_error() {
147 let err = parse_source("let x = `").unwrap_err();
148 assert!(matches!(err, PipelineError::Lex(_)));
149 assert!(err.span().is_some());
150 assert!(err.to_string().contains("Unexpected character"));
151 }
152
153 #[test]
154 fn parse_source_parse_error() {
155 let err = parse_source("let = 1").unwrap_err();
156 assert!(matches!(err, PipelineError::Parse(_)));
157 assert!(err.span().is_some());
158 }
159
160 #[test]
161 fn check_source_returns_diagnostics() {
162 let (program, _diagnostics) = check_source("const x = 1").unwrap();
163 assert!(!program.is_empty());
164 }
165
166 #[test]
167 fn check_source_strict_passes_valid_code() {
168 let program = check_source_strict("const x = 1\nlog(x)").unwrap();
169 assert!(!program.is_empty());
170 }
171
172 #[test]
173 fn check_source_strict_catches_lex_error() {
174 let err = check_source_strict("`").unwrap_err();
175 assert!(matches!(err, PipelineError::Lex(_)));
176 }
177
178 #[test]
179 fn pipeline_error_display_is_informative() {
180 let err = parse_source("`").unwrap_err();
181 let msg = err.to_string();
182 assert!(!msg.is_empty());
183 assert!(msg.contains('`') || msg.contains("Unexpected"));
184 }
185
186 #[test]
187 fn pipeline_error_size_is_bounded() {
188 assert!(
190 std::mem::size_of::<PipelineError>() <= 96,
191 "PipelineError grew to {} bytes — consider boxing large variants",
192 std::mem::size_of::<PipelineError>()
193 );
194 }
195}