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