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