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