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