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