Skip to content

Commit e0de366

Browse files
authored
Use error getters and constructors (#62)
1 parent 96c86fe commit e0de366

File tree

3 files changed

+251
-223
lines changed

3 files changed

+251
-223
lines changed

crates/cellang/src/error.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,57 @@ use thiserror::Error;
1010
#[diagnostic(code(cellang::syntax_error))]
1111
pub struct SyntaxError {
1212
#[source_code]
13-
pub source_code: String,
13+
source_code: String,
1414

1515
#[label("this")]
16-
pub span: SourceSpan,
16+
span: SourceSpan,
1717

18-
pub message: String,
18+
message: String,
1919

2020
#[help]
21-
pub help: Option<String>,
21+
help: Option<String>,
22+
}
23+
24+
impl SyntaxError {
25+
/// Creates a new syntax error capturing the offending source span.
26+
pub fn new(
27+
source_code: impl Into<String>,
28+
span: SourceSpan,
29+
message: impl Into<String>,
30+
) -> Self {
31+
Self {
32+
source_code: source_code.into(),
33+
span,
34+
message: message.into(),
35+
help: None,
36+
}
37+
}
38+
39+
/// Provides an optional help hint for diagnostics.
40+
pub fn with_help(mut self, help: impl Into<String>) -> Self {
41+
self.help = Some(help.into());
42+
self
43+
}
44+
45+
/// Full source text used to produce the diagnostic.
46+
pub fn source_text(&self) -> &str {
47+
&self.source_code
48+
}
49+
50+
/// Span pointing at the offending location.
51+
pub fn span(&self) -> &SourceSpan {
52+
&self.span
53+
}
54+
55+
/// Human readable error message.
56+
pub fn message(&self) -> &str {
57+
&self.message
58+
}
59+
60+
/// Optional help hint associated with the diagnostic.
61+
pub fn help_text(&self) -> Option<&str> {
62+
self.help.as_deref()
63+
}
2264
}
2365

2466
/// Describes semantic/environment validation failures (duplicate identifiers,

crates/cellang/src/lexer.rs

Lines changed: 100 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -314,21 +314,17 @@ impl<'src> Lexer<'src> {
314314
) -> Result<Token<'src>, SyntaxError> {
315315
match self.next() {
316316
Some(Ok(token)) if check(&token) => Ok(token),
317-
Some(Ok(token)) => Err(SyntaxError {
318-
source_code: self.whole.to_string(),
319-
message: unexpected.to_string(),
320-
help: None,
321-
span: SourceSpan::new(token.offset.into(), token.span_len),
322-
}),
317+
Some(Ok(token)) => Err(SyntaxError::new(
318+
self.whole.to_string(),
319+
SourceSpan::new(token.offset.into(), token.span_len),
320+
unexpected.to_string(),
321+
)),
323322
Some(Err(e)) => Err(e),
324-
None => Err(SyntaxError {
325-
source_code: self.whole.to_string(),
326-
message: format!(
327-
"Unexpected end of file, expected {unexpected}"
328-
),
329-
help: None,
330-
span: SourceSpan::new(self.byte.into(), 0),
331-
}),
323+
None => Err(SyntaxError::new(
324+
self.whole.to_string(),
325+
SourceSpan::new(self.byte.into(), 0),
326+
format!("Unexpected end of file, expected {unexpected}"),
327+
)),
332328
}
333329
}
334330

@@ -431,17 +427,14 @@ impl<'src> Iterator for Lexer<'src> {
431427
self.rest = &self.rest[1..];
432428
return just(TokenType::And);
433429
} else {
434-
return Some(Err(SyntaxError {
435-
source_code: self.whole.to_string(),
436-
span: SourceSpan::new(
437-
c_at.into(),
438-
self.byte - c_at,
439-
),
440-
message: format!(
430+
return Some(Err(SyntaxError::new(
431+
self.whole.to_string(),
432+
SourceSpan::new(c_at.into(), self.byte - c_at),
433+
format!(
441434
"[line {line}] Error: Unexpected character: {c}"
442435
),
443-
help: Some("expected `&&`".to_string()),
444-
}));
436+
)
437+
.with_help("expected `&&`")));
445438
}
446439
}
447440
'|' => {
@@ -450,17 +443,14 @@ impl<'src> Iterator for Lexer<'src> {
450443
self.rest = &self.rest[1..];
451444
return just(TokenType::Or);
452445
} else {
453-
return Some(Err(SyntaxError {
454-
source_code: self.whole.to_string(),
455-
span: SourceSpan::new(
456-
c_at.into(),
457-
self.byte - c_at,
458-
),
459-
message: format!(
446+
return Some(Err(SyntaxError::new(
447+
self.whole.to_string(),
448+
SourceSpan::new(c_at.into(), self.byte - c_at),
449+
format!(
460450
"[line {line}] Error: Unexpected character: {c}"
461451
),
462-
help: Some("expected `||`".to_string()),
463-
}));
452+
)
453+
.with_help("expected `||`")));
464454
}
465455
}
466456
'0'..='9' => {
@@ -509,14 +499,13 @@ impl<'src> Iterator for Lexer<'src> {
509499
c => {
510500
let line = self.line;
511501

512-
return Some(Err(SyntaxError {
513-
source_code: self.whole.to_string(),
514-
span: SourceSpan::new(c_at.into(), self.byte - c_at),
515-
message: format!(
502+
return Some(Err(SyntaxError::new(
503+
self.whole.to_string(),
504+
SourceSpan::new(c_at.into(), self.byte - c_at),
505+
format!(
516506
"[line {line}] Error: Unexpected character: {c}"
517507
),
518-
help: None,
519-
}));
508+
)));
520509
}
521510
};
522511

@@ -695,17 +684,19 @@ impl<'src> Iterator for Lexer<'src> {
695684
State::Nil => {
696685
let line = self.line;
697686

698-
return Some(Err(SyntaxError {
699-
source_code: self.whole.to_string(),
700-
span: SourceSpan::new(
701-
c_at.into(),
702-
self.byte - c_at,
703-
),
704-
message: format!(
705-
"[line {line}] Error: Unexpected character: {c}"
706-
),
707-
help: Some("Expected a number".to_string()),
708-
}));
687+
return Some(Err(
688+
SyntaxError::new(
689+
self.whole.to_string(),
690+
SourceSpan::new(
691+
c_at.into(),
692+
self.byte - c_at,
693+
),
694+
format!(
695+
"[line {line}] Error: Unexpected character: {c}"
696+
),
697+
)
698+
.with_help("Expected a number"),
699+
));
709700
}
710701
State::Dot | State::Exp => {
711702
// unread the dot or exp
@@ -782,17 +773,14 @@ impl<'src> Iterator for Lexer<'src> {
782773
let rest = &c_onwards[2..]; // ignore 0x
783774
if rest.is_empty() {
784775
let line = self.line;
785-
return Some(Err(SyntaxError {
786-
source_code: self.whole.to_string(),
787-
span: SourceSpan::new(
788-
c_at.into(),
789-
self.byte - c_at,
790-
),
791-
message: format!(
776+
return Some(Err(SyntaxError::new(
777+
self.whole.to_string(),
778+
SourceSpan::new(c_at.into(), self.byte - c_at),
779+
format!(
792780
"[line {line}] Error: Unexpected character: {c}"
793781
),
794-
help: Some("Expected a number".to_string()),
795-
}));
782+
)
783+
.with_help("Expected a number")));
796784
}
797785

798786
let mut end = 0;
@@ -865,12 +853,12 @@ fn scan_str(s: &str) -> Result<(usize, &str), SyntaxError> {
865853
assert!(s.starts_with(['"', '\'']));
866854

867855
if s.len() < 2 {
868-
return Err(SyntaxError {
869-
source_code: s.to_string(),
870-
span: SourceSpan::new(0.into(), s.len()),
871-
message: "Unexpected end of file".to_string(),
872-
help: Some("Expected a closing quote".to_string()),
873-
});
856+
return Err(SyntaxError::new(
857+
s.to_string(),
858+
SourceSpan::new(0.into(), s.len()),
859+
"Unexpected end of file",
860+
)
861+
.with_help("Expected a closing quote"));
874862
}
875863

876864
let mut chars = s.chars();
@@ -890,24 +878,24 @@ fn scan_str(s: &str) -> Result<(usize, &str), SyntaxError> {
890878
let c = match chars.next() {
891879
Some(c) => c,
892880
None => {
893-
return Err(SyntaxError {
894-
source_code: s.to_string(),
895-
span: SourceSpan::new(0.into(), end),
896-
message: "Unexpected end of file".to_string(),
897-
help: Some("Expected a closing quote".to_string()),
898-
});
881+
return Err(SyntaxError::new(
882+
s.to_string(),
883+
SourceSpan::new(0.into(), end),
884+
"Unexpected end of file",
885+
)
886+
.with_help("Expected a closing quote"));
899887
}
900888
};
901889

902890
end += c.len_utf8();
903891

904892
if delim_n == 1 && matches!(c, '\r' | '\n') {
905-
return Err(SyntaxError {
906-
source_code: s[..end].to_string(),
907-
span: SourceSpan::new((end - 1).into(), 1),
908-
message: "Unexpected newline in string".to_string(),
909-
help: Some("Unterminated string".to_string()),
910-
});
893+
return Err(SyntaxError::new(
894+
s[..end].to_string(),
895+
SourceSpan::new((end - 1).into(), 1),
896+
"Unexpected newline in string",
897+
)
898+
.with_help("Unterminated string"));
911899
}
912900

913901
// if it is a delimiter, check if it is the closing delimiter
@@ -929,12 +917,12 @@ fn scan_str(s: &str) -> Result<(usize, &str), SyntaxError> {
929917
let c = match chars.next() {
930918
Some(c) => c,
931919
None => {
932-
return Err(SyntaxError {
933-
source_code: s.to_string(),
934-
span: SourceSpan::new(0.into(), end),
935-
message: "Unexpected end of file".to_string(),
936-
help: Some("Expected char after escape".to_string()),
937-
});
920+
return Err(SyntaxError::new(
921+
s.to_string(),
922+
SourceSpan::new(0.into(), end),
923+
"Unexpected end of file",
924+
)
925+
.with_help("Expected char after escape"));
938926
}
939927
};
940928
end += c.len_utf8();
@@ -981,25 +969,25 @@ fn scan_str(s: &str) -> Result<(usize, &str), SyntaxError> {
981969
end += 2;
982970
}
983971
_ => {
984-
return Err(SyntaxError {
985-
source_code: s.to_string(),
986-
span: SourceSpan::new(0.into(), end),
987-
message: format!("Unexpected character: {c}"),
988-
help: Some("Expected a hex digit".to_string()),
989-
});
972+
return Err(SyntaxError::new(
973+
s.to_string(),
974+
SourceSpan::new(0.into(), end),
975+
format!("Unexpected character: {c}"),
976+
)
977+
.with_help("Expected a hex digit"));
990978
}
991979
}
992980
}
993981
}
994982

995983
fn read_str_raw(s: &str) -> Result<(usize, &str), SyntaxError> {
996984
if s.len() < 2 {
997-
return Err(SyntaxError {
998-
source_code: s.to_string(),
999-
span: SourceSpan::new(0.into(), s.len()),
1000-
message: "Unexpected end of file".to_string(),
1001-
help: Some("Expected a closing quote".to_string()),
1002-
});
985+
return Err(SyntaxError::new(
986+
s.to_string(),
987+
SourceSpan::new(0.into(), s.len()),
988+
"Unexpected end of file",
989+
)
990+
.with_help("Expected a closing quote"));
1003991
}
1004992

1005993
let take = if s.starts_with(r#"""""#) || s.starts_with("'''") {
@@ -1013,12 +1001,12 @@ fn read_str_raw(s: &str) -> Result<(usize, &str), SyntaxError> {
10131001
let end = match rest.find(delim) {
10141002
Some(end) => end,
10151003
None => {
1016-
return Err(SyntaxError {
1017-
source_code: s.to_string(),
1018-
span: SourceSpan::new(0.into(), s.len()),
1019-
message: "Unexpected end of file".to_string(),
1020-
help: Some("Expected a closing quote".to_string()),
1021-
});
1004+
return Err(SyntaxError::new(
1005+
s.to_string(),
1006+
SourceSpan::new(0.into(), s.len()),
1007+
"Unexpected end of file",
1008+
)
1009+
.with_help("Expected a closing quote"));
10221010
}
10231011
};
10241012

@@ -1044,22 +1032,22 @@ fn read_chars_tested(
10441032
let c = match chars.next() {
10451033
Some(c) => c,
10461034
None => {
1047-
return Err(SyntaxError {
1048-
source_code: buf.clone(),
1049-
span: SourceSpan::new(0.into(), buf.len()),
1050-
message: "Unexpected end of file".to_string(),
1051-
help: Some(err_msg.to_string()),
1052-
});
1035+
return Err(SyntaxError::new(
1036+
buf.clone(),
1037+
SourceSpan::new(0.into(), buf.len()),
1038+
"Unexpected end of file",
1039+
)
1040+
.with_help(err_msg.to_string()));
10531041
}
10541042
};
10551043

10561044
if !test_fn(c) {
1057-
return Err(SyntaxError {
1058-
source_code: buf.clone(),
1059-
span: SourceSpan::new(0.into(), buf.len()),
1060-
message: format!("Unexpected character: {c}"),
1061-
help: Some(err_msg.to_string()),
1062-
});
1045+
return Err(SyntaxError::new(
1046+
buf.clone(),
1047+
SourceSpan::new(0.into(), buf.len()),
1048+
format!("Unexpected character: {c}"),
1049+
)
1050+
.with_help(err_msg.to_string()));
10631051
}
10641052

10651053
buf.push(c);

0 commit comments

Comments
 (0)