Fork of daniellemaywood.uk/gleam — Wasm codegen work
23 kB
489 lines
1use crate::ast::{SrcSpan, TypeAst};
2use crate::error::wrap;
3use crate::parse::Token;
4use ecow::EcoString;
5
6#[derive(Debug, PartialEq, Eq, Clone, Copy)]
7pub struct LexicalError {
8 pub error: LexicalErrorType,
9 pub location: SrcSpan,
10}
11
12#[derive(Debug, PartialEq, Eq, Clone, Copy)]
13pub enum InvalidUnicodeEscapeError {
14 MissingOpeningBrace, // Expected '{'
15 ExpectedHexDigitOrCloseBrace, // Expected hex digit or '}'
16 InvalidNumberOfHexDigits, // Expected between 1 and 6 hex digits
17 InvalidCodepoint, // Invalid Unicode codepoint
18}
19
20#[derive(Debug, PartialEq, Eq, Clone, Copy)]
21pub enum LexicalErrorType {
22 BadStringEscape, // string contains an unescaped slash
23 InvalidUnicodeEscape(InvalidUnicodeEscapeError), // \u{...} escape sequence is invalid
24 DigitOutOfRadix, // 0x012 , 2 is out of radix
25 NumTrailingUnderscore, // 1_000_ is not allowed
26 RadixIntNoValue, // 0x, 0b, 0o without a value
27 MissingExponent, // 1.0e, for example, where there is no exponent
28 UnexpectedStringEnd, // Unterminated string literal
29 UnrecognizedToken { tok: char },
30 InvalidTripleEqual,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct ParseError {
35 pub error: ParseErrorType,
36 pub location: SrcSpan,
37}
38
39impl ParseError {
40 pub fn details(&self) -> (&'static str, Vec<String>) {
41 match &self.error {
42 ParseErrorType::ExpectedEqual => ("I was expecting a '=' after this", vec![]),
43 ParseErrorType::ExpectedExpr => ("I was expecting an expression after this", vec![]),
44 ParseErrorType::ExpectedName => ("I was expecting a name here", vec![]),
45 ParseErrorType::ExpectedPattern => ("I was expecting a pattern after this", vec![]),
46 ParseErrorType::ExpectedType => (
47 "I was expecting a type after this",
48 vec!["See: https://tour.gleam.run/basics/assignments/".into()],
49 ),
50 ParseErrorType::ExpectedUpName => ("I was expecting a type name here", vec![]),
51 ParseErrorType::ExpectedValue => ("I was expecting a value after this", vec![]),
52 ParseErrorType::ExpectedStatement => ("I was expecting a statement after this", vec![]),
53 ParseErrorType::ExpectedDefinition => {
54 ("I was expecting a definition after this", vec![])
55 }
56 ParseErrorType::ExpectedDeprecationMessage => (
57 "A deprecation attribute must have a string message.",
58 vec![],
59 ),
60 ParseErrorType::ExpectedFunctionDefinition => {
61 ("I was expecting a function definition after this", vec![])
62 }
63 ParseErrorType::ExpectedTargetName => (
64 "I was expecting a target name after this",
65 vec!["Try `erlang`, `javascript`.".into()],
66 ),
67 ParseErrorType::ExtraSeparator => (
68 "This is an extra delimiter",
69 vec!["Hint: Try removing it?".into()],
70 ),
71 ParseErrorType::ExprLparStart => (
72 "This parenthesis cannot be understood here",
73 vec![
74 "Hint: To group expressions in Gleam, use \"{\" and \"}\"; tuples are created with `#(` and `)`.".into(),
75 ]
76 ),
77 ParseErrorType::IncorrectName => (
78 "I'm expecting a lowercase name here",
79 vec![wrap(
80 "Hint: Variable and module names start with a lowercase letter, \
81and can contain a-z, 0-9, or _.",
82 )],
83 ),
84 ParseErrorType::IncorrectUpName => (
85 "I'm expecting a type name here",
86 vec![wrap(
87 "Hint: Type names start with a uppercase letter, and can \
88contain a-z, A-Z, or 0-9.",
89 )],
90 ),
91 ParseErrorType::InvalidBitArraySegment => (
92 "This is not a valid BitArray segment option",
93 vec![
94 "Hint: Valid BitArray segment options are:".into(),
95 wrap(
96 "bits, bytes, int, float, utf8, utf16, utf32, utf8_codepoint, \
97utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, unit.",
98 ),
99 "See: https://tour.gleam.run/data-types/bit-arrays/".into(),
100 ],
101 ),
102 ParseErrorType::InvalidBitArrayUnit => (
103 "This is not a valid BitArray unit value",
104 vec![
105 "Hint: unit must be an integer literal >= 1 and <= 256.".into(),
106 "See: https://tour.gleam.run/data-types/bit-arrays/".into(),
107 ],
108 ),
109 ParseErrorType::InvalidTailPattern => (
110 "This part of a list pattern can only be a name or a discard",
111 vec![],
112 ),
113 ParseErrorType::InvalidTupleAccess => (
114 "This integer is not valid for tuple access",
115 vec![
116 "Hint: Only non negative integer literals like 0, or 1_000 can be used."
117 .to_string(),
118 ],
119 ),
120 ParseErrorType::LexError { error: lex_err } => lex_err.to_parse_error_info(),
121 ParseErrorType::NestedBitArrayPattern => ("BitArray patterns cannot be nested", vec![]),
122 ParseErrorType::NotConstType => (
123 "This type is not allowed in module constants",
124 vec!["See: https://tour.gleam.run/basics/constants/".into()],
125 ),
126 ParseErrorType::NoExpression => (
127 "There must be an expression in here",
128 vec!["Hint: Put an expression in there or remove the brackets.".into()],
129 ),
130 ParseErrorType::NoLetBinding => (
131 "There must be a 'let' to bind variable to value",
132 vec![
133 "Hint: Use let for binding.".into(),
134 "See: https://tour.gleam.run/basics/assignments/".into(),
135 ],
136 ),
137 ParseErrorType::NoValueAfterEqual => (
138 "I was expecting to see a value after this equals sign",
139 vec![],
140 ),
141 ParseErrorType::OpaqueTypeAlias => (
142 "Type Aliases cannot be opaque",
143 vec!["See: https://tour.gleam.run/basics/type-aliases/".into()],
144 ),
145 ParseErrorType::OpNakedRight => (
146 "This operator has no value on its right side",
147 vec!["Hint: Remove it or put a value after it.".into()],
148 ),
149 ParseErrorType::TooManyArgHoles => (
150 "There is more than 1 argument hole in this function call",
151 vec![
152 "Hint: Function calls can have at most one argument hole.".into(),
153 "See: https://tour.gleam.run/functions/functions/".into(),
154 ],
155 ),
156 ParseErrorType::UnexpectedEof => ("The module ended unexpectedly", vec![]),
157 ParseErrorType::ListSpreadWithoutElements => (
158 "This spread does nothing",
159 vec![
160 "Hint: Try prepending some elements [1, 2, ..list].".into(),
161 "See: https://tour.gleam.run/basics/lists/".into(),
162 ],
163 ),
164 ParseErrorType::ListSpreadFollowedByElements => (
165 "I wasn't expecting elements after this",
166 vec![
167 "Lists are immutable and singly-linked, so to append items to them".into(),
168 "all the elements of a list would need to be copied into a new list.".into(),
169 "This would be slow, so there is no built-in syntax for it.".into(),
170 "".into(),
171 "Hint: prepend items to the list and then reverse it once you are done.".into(),
172 ],
173 ),
174 ParseErrorType::ListPatternSpreadFollowedByElements => (
175 "I wasn't expecting elements after this",
176 vec![
177 "Lists are immutable and singly-linked, so to match on the end".into(),
178 "of a list would require the whole list to be traversed. This".into(),
179 "would be slow, so there is no built-in syntax for it. Pattern".into(),
180 "match on the start of the list instead.".into(),
181 ],
182 ),
183 ParseErrorType::UnexpectedReservedWord => (
184 "This is a reserved word",
185 vec!["Hint: I was expecting to see a name here.".into()],
186 ),
187 ParseErrorType::LowcaseBooleanPattern => (
188 "Did you want a Bool instead of a variable?",
189 vec![
190 "Hint: In Gleam boolean literals are `True` and `False`.".into(),
191 "See: https://tour.gleam.run/basics/bools/".into(),
192 ],
193 ),
194 ParseErrorType::UnexpectedLabel => (
195 "Argument labels are not allowed for anonymous functions",
196 vec!["Please remove the argument label.".into()],
197 ),
198 ParseErrorType::UnexpectedToken {
199 token,
200 expected,
201 hint,
202 } => {
203 let found = match token {
204 Token::Int { .. } => "an Int".to_string(),
205 Token::Float { .. } => "a Float".to_string(),
206 Token::String { .. } => "a String".to_string(),
207 Token::CommentDoc { .. } => "a comment".to_string(),
208 Token::DiscardName { .. } => "a discard name".to_string(),
209 Token::Name { .. } | Token::UpName { .. } => "a name".to_string(),
210 _ if token.is_reserved_word() => format!("the keyword {token}"),
211 _ => token.to_string(),
212 };
213
214 let messages = std::iter::once(format!("Found {found}, expected one of: "))
215 .chain(expected.iter().map(|s| format!("- {s}")));
216
217 let messages = match hint {
218 Some(hint_text) => messages
219 .chain(std::iter::once(format!("Hint: {hint_text}")))
220 .collect(),
221 _ => messages.collect(),
222 };
223
224 ("I was not expecting this", messages)
225 }
226 ParseErrorType::ExpectedBoolean => ("Did you mean to negate a boolean?", vec![]),
227 ParseErrorType::ConcatPatternVariableLeftHandSide => (
228 "This must be a string literal",
229 vec![
230 "We can't tell what size this prefix should be so we don't know".into(),
231 "how to handle this pattern.".into(),
232 "".into(),
233 "If you want to match one character consider using `pop_grapheme`".into(),
234 "from the stdlib's `gleam/string` module.".into(),
235 ],
236 ),
237 ParseErrorType::UnexpectedFunction => (
238 "Functions can only be called within other functions",
239 vec![],
240 ),
241 ParseErrorType::ListSpreadWithoutTail => (
242 "I was expecting a value after this spread",
243 vec!["If a list expression has a spread then a tail must also be given.".into()],
244 ),
245 ParseErrorType::UnknownAttribute => (
246 "I don't recognise this attribute",
247 vec!["Try `deprecated`, `external` or `target` instead.".into()],
248 ),
249 ParseErrorType::DuplicateAttribute => (
250 "Duplicate attribute",
251 vec!["This attribute has already been given.".into()],
252 ),
253 ParseErrorType::UnknownTarget => (
254 "I don't recognise this target",
255 vec!["Try `erlang`, `javascript`.".into()],
256 ),
257 ParseErrorType::ExpectedFunctionBody => ("This function does not have a body", vec![]),
258 ParseErrorType::RedundantInternalAttribute => (
259 "Redundant internal attribute",
260 vec![
261 format!("Only a public definition can be annotated as internal."),
262 "Hint: remove the `@internal` annotation.".into(),
263 ],
264 ),
265 ParseErrorType::InvalidModuleTypePattern => (
266 "Invalid pattern",
267 vec![
268 "I'm expecting a pattern here".into(),
269 "Hint: A pattern can be a constructor name, a literal value".into(),
270 "or a variable to bind a value to, etc.".into(),
271 "See: https://tour.gleam.run/flow-control/case-expressions/".into(),
272 ],
273 ),
274 ParseErrorType::ExpectedRecordConstructor {
275 name,
276 public,
277 opaque,
278 field,
279 field_type,
280 } => {
281 let (accessor, opaque) = match *public {
282 true if *opaque => ("pub ", "opaque "),
283 true => ("pub ", ""),
284 false => ("", ""),
285 };
286
287 let mut annotation = EcoString::new();
288 match field_type {
289 Some(t) => t.print(&mut annotation),
290 None => annotation.push_str("Type"),
291 };
292
293 (
294 "I was not expecting this",
295 vec![
296 "Each custom type variant must have a constructor:\n".into(),
297 format!("{accessor}{opaque}type {name} {{"),
298 format!(" {name}("),
299 format!(" {field}: {annotation},"),
300 " )".into(),
301 "}".into(),
302 ],
303 )
304 }
305 ParseErrorType::CallInClauseGuard => (
306 "Unsupported expression",
307 vec!["Functions cannot be called in clause guards.".into()],
308 ),
309 ParseErrorType::IfExpression => (
310 "Gleam doesn't have if expressions",
311 vec![
312 "If you want to write a conditional expression you can use a `case`:".into(),
313 "".into(),
314 " case condition {".into(),
315 " True -> todo".into(),
316 " False -> todo".into(),
317 " }".into(),
318 "".into(),
319 "See: https://tour.gleam.run/flow-control/case-expressions/".into(),
320 ],
321 ),
322 ParseErrorType::ConstantRecordConstructorNoArguments => (
323 "I was expecting arguments here",
324 vec!["A record must be passed arguments when constructed.".into()],
325 ),
326 ParseErrorType::TypeConstructorNoArguments => (
327 "I was expecting arguments here",
328 vec!["A type constructor must be passed arguments.".into()],
329 ),
330 ParseErrorType::TypeDefinitionNoArguments => (
331 "I was expecting generic parameters here",
332 vec![
333 "A generic type must have at least a generic parameter.".into(),
334 "Hint: If a type is not generic you should omit the `()`.".into(),
335 ],
336 ),
337 ParseErrorType::UnknownAttributeRecordVariant => (
338 "This attribute cannot be used on a variant.",
339 vec!["Hint: Did you mean `@deprecated`?".into()],
340 ),
341 }
342 }
343}
344
345#[derive(Debug, Clone, PartialEq, Eq)]
346pub enum ParseErrorType {
347 ExpectedEqual, // expect "="
348 ExpectedExpr, // after "->" in a case clause
349 ExpectedName, // any token used when a Name was expected
350 ExpectedPattern, // after ':' where a pattern is expected
351 ExpectedType, // after ':' or '->' where a type annotation is expected
352 ExpectedUpName, // any token used when a UpName was expected
353 ExpectedValue, // no value after "="
354 ExpectedStatement, // no statement after "@<name>"
355 ExpectedDefinition, // after attributes
356 ExpectedDeprecationMessage, // after "deprecated"
357 ExpectedFunctionDefinition, // after function-only attributes
358 ExpectedTargetName, // after "@target("
359 ExprLparStart, // it seems "(" was used to start an expression
360 ExtraSeparator, // #(1,,) <- the 2nd comma is an extra separator
361 IncorrectName, // UpName or DiscardName used when Name was expected
362 IncorrectUpName, // Name or DiscardName used when UpName was expected
363 InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment
364 InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256
365 InvalidTailPattern, // only name and _name are allowed after ".." in list pattern
366 InvalidTupleAccess, // only positive int literals for tuple access
367 LexError {
368 error: LexicalError,
369 },
370 NestedBitArrayPattern, // <<<<1>>, 2>>, <<1>> is not allowed in there
371 NoExpression, // between "{" and "}" in expression position, there must be an expression
372 NoLetBinding, // Bindings and rebinds always require let and must always bind to a value.
373 NoValueAfterEqual, // = <something other than a value>
374 NotConstType, // :fn(), name, _ are not valid const types
375 OpNakedRight, // Operator with no value to the right
376 OpaqueTypeAlias, // Type aliases cannot be opaque
377 TooManyArgHoles, // a function call can have at most 1 arg hole
378 DuplicateAttribute, // an attribute was used more than once
379 UnknownAttribute, // an attribute was used that is not known
380 UnknownTarget, // an unknown target was used
381 ListSpreadWithoutElements, // Pointless spread: `[..xs]`
382 ListSpreadFollowedByElements, // trying to append something after the spread: `[..xs, x]`
383 LowcaseBooleanPattern, // most likely user meant True or False in patterns
384 UnexpectedLabel, // argument labels were provided, but are not supported in this context
385 UnexpectedEof,
386 UnexpectedReservedWord, // reserved word used when a name was expected
387 UnexpectedToken {
388 token: Token,
389 expected: Vec<EcoString>,
390 hint: Option<EcoString>,
391 },
392 ExpectedBoolean,
393 UnexpectedFunction, // a function was used called outside of another function
394 // A variable was assigned or discarded on the left hand side of a <> pattern
395 ConcatPatternVariableLeftHandSide,
396 ListSpreadWithoutTail, // let x = [1, ..]
397 ExpectedFunctionBody, // let x = fn()
398 RedundantInternalAttribute, // for a private definition marked as internal
399 InvalidModuleTypePattern, // for patterns that have a dot like: `name.thing`
400 ListPatternSpreadFollowedByElements, // When there is a pattern after a spread [..rest, pattern]
401 ExpectedRecordConstructor {
402 name: EcoString,
403 public: bool,
404 opaque: bool,
405 field: EcoString,
406 field_type: Option<TypeAst>,
407 },
408 CallInClauseGuard, // case x { _ if f() -> 1 }
409 IfExpression,
410 ConstantRecordConstructorNoArguments, // const x = Record()
411 TypeConstructorNoArguments, // let a : Int()
412 TypeDefinitionNoArguments, // pub type Wibble() { ... }
413 UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant
414}
415
416impl LexicalError {
417 pub fn to_parse_error_info(&self) -> (&'static str, Vec<String>) {
418 match &self.error {
419 LexicalErrorType::BadStringEscape => (
420 "I don't understand this escape code",
421 vec![
422 "Hint: Add another backslash before it.".into(),
423 "See: https://tour.gleam.run/basics/strings".into(),
424 ],
425 ),
426 LexicalErrorType::DigitOutOfRadix => {
427 ("This digit is too big for the specified radix", vec![])
428 }
429 LexicalErrorType::NumTrailingUnderscore => (
430 "Numbers cannot have a trailing underscore",
431 vec!["Hint: remove it.".into()],
432 ),
433 LexicalErrorType::RadixIntNoValue => ("This integer has no value", vec![]),
434 LexicalErrorType::MissingExponent => (
435 "This float is missing an exponent",
436 vec!["Hint: Add an exponent or remove the trailing `e`".into()],
437 ),
438 LexicalErrorType::UnexpectedStringEnd => {
439 ("The string starting here was left open", vec![])
440 }
441 LexicalErrorType::UnrecognizedToken { tok } if *tok == ';' => (
442 "Remove this semicolon",
443 vec![
444 "Hint: Semicolons used to be whitespace and did nothing.".into(),
445 "You can safely remove them without your program changing.".into(),
446 ],
447 ),
448 LexicalErrorType::UnrecognizedToken { tok } if *tok == '\'' => (
449 "Unexpected single quote",
450 vec!["Hint: Strings are written with double quotes.".into()],
451 ),
452 LexicalErrorType::UnrecognizedToken { .. } => (
453 "I can't figure out what to do with this character",
454 vec!["Hint: Is it a typo?".into()],
455 ),
456 LexicalErrorType::InvalidUnicodeEscape(
457 InvalidUnicodeEscapeError::MissingOpeningBrace,
458 ) => (
459 "Expected '{' in Unicode escape sequence",
460 vec!["Hint: Add it.".into()],
461 ),
462 LexicalErrorType::InvalidUnicodeEscape(
463 InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace,
464 ) => (
465 "Expected hex digit or '}' in Unicode escape sequence",
466 vec![
467 "Hint: Hex digits are digits from 0 to 9 and letters from a to f or A to F."
468 .into(),
469 ],
470 ),
471 LexicalErrorType::InvalidUnicodeEscape(
472 InvalidUnicodeEscapeError::InvalidNumberOfHexDigits,
473 ) => (
474 "Expected between 1 and 6 hex digits in Unicode escape sequence",
475 vec![],
476 ),
477 LexicalErrorType::InvalidUnicodeEscape(InvalidUnicodeEscapeError::InvalidCodepoint) => {
478 ("Invalid Unicode codepoint", vec![])
479 }
480 LexicalErrorType::InvalidTripleEqual => (
481 "Did you mean `==`?",
482 vec![
483 "Gleam uses `==` to check for equality between two values.".into(),
484 "See: https://tour.gleam.run/basics/equality".into(),
485 ],
486 ),
487 }
488 }
489}