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