Add fuzzing to the parser.

As part of this, todos and panics where moved outside of this module
to elsewhere when they would cause fuzzing to fail.
This commit is contained in:
Brendan Hansknecht 2020-10-30 22:04:54 -07:00
parent ad31975b97
commit d00189530a
16 changed files with 314 additions and 32 deletions

View file

@ -277,6 +277,8 @@ pub enum Def<'a> {
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
/// It lets us take a (&Def) and create a plain (Def) from it.
Nested(&'a Def<'a>),
NotYetImplemented(&'static str),
}
#[derive(Debug, Clone, PartialEq)]

View file

@ -561,7 +561,7 @@ fn annotation_or_alias<'a>(
ann: loc_ann,
},
Apply(_, _) => {
panic!("TODO gracefully handle invalid Apply in type annotation");
Def::NotYetImplemented("TODO gracefully handle invalid Apply in type annotation")
}
SpaceAfter(value, spaces_before) => Def::SpaceAfter(
arena.alloc(annotation_or_alias(arena, value, region, loc_ann)),
@ -574,19 +574,19 @@ fn annotation_or_alias<'a>(
Nested(value) => annotation_or_alias(arena, value, region, loc_ann),
PrivateTag(_) => {
panic!("TODO gracefully handle trying to use a private tag as an annotation.");
Def::NotYetImplemented("TODO gracefully handle trying to use a private tag as an annotation.")
}
QualifiedIdentifier { .. } => {
panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`");
Def::NotYetImplemented("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`")
}
NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => {
panic!("TODO gracefully handle trying to annotate a litera");
Def::NotYetImplemented("TODO gracefully handle trying to annotate a litera")
}
Underscore => {
panic!("TODO gracefully handle trying to give a type annotation to an undrscore");
Def::NotYetImplemented("TODO gracefully handle trying to give a type annotation to an undrscore")
}
Malformed(_) => {
panic!("TODO translate a malformed pattern into a malformed annotation");
Def::NotYetImplemented("TODO translate a malformed pattern into a malformed annotation")
}
Identifier(ident) => {
// This is a regular Annotation
@ -633,7 +633,13 @@ fn parse_def_expr<'a>(
))
// `<` because '=' should be same indent (or greater) as the entire def-expr
} else if equals_sign_indent < def_start_col {
todo!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col);
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col)),
},
state,
))
} else {
// Indented more beyond the original indent of the entire def-expr.
let indented_more = def_start_col + 1;
@ -720,7 +726,15 @@ fn parse_def_signature<'a>(
))
// `<` because ':' should be same indent or greater
} else if colon_indent < original_indent {
panic!("TODO the : in this declaration seems outdented");
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO the : in this declaration seems outdented".to_string(),
),
},
state,
))
} else {
// Indented more beyond the original indent.
let indented_more = original_indent + 1;
@ -1120,7 +1134,15 @@ mod when {
),
move |arena, state, (case_indent, loc_condition)| {
if case_indent < min_indent {
panic!("TODO case wasn't indented enough");
return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO case wasn't indented enough".to_string(),
),
},
state,
));
}
// Everything in the branches must be indented at least as much as the case itself.
@ -1178,9 +1200,15 @@ mod when {
if alternatives_indented_correctly(&loc_patterns, original_indent) {
Ok(((loc_patterns, loc_guard), state))
} else {
panic!(
"TODO additional branch didn't have same indentation as first branch"
);
return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO additional branch didn't have same indentation as first branch".to_string(),
),
},
state,
));
}
},
),
@ -1490,10 +1518,16 @@ fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
});
}
Err(malformed) => {
panic!(
"TODO early return malformed pattern {:?}",
malformed
);
return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
"TODO early return malformed pattern {:?}",
malformed
)),
},
state,
));
}
}
}

View file

@ -24,4 +24,5 @@ pub mod number_literal;
pub mod pattern;
pub mod problems;
pub mod string_literal;
pub mod test_helpers;
pub mod type_annotation;

View file

@ -224,6 +224,7 @@ pub enum FailReason {
BadUtf8,
ReservedKeyword(Region),
ArgumentsBeforeEquals(Region),
NotYetImplemented(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -1,8 +1,8 @@
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
use crate::expr;
use crate::parser::{
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof,
ParseResult, Parser, State,
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail,
FailReason, ParseResult, Parser, State,
};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
@ -279,7 +279,16 @@ where
// lines.push(line);
// Ok((StrLiteral::Block(lines.into_bump_slice()), state))
todo!("TODO parse this line in a block string: {:?}", line);
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
"TODO parse this line in a block string: {:?}",
line
)),
},
state,
))
}
Err(reason) => state.fail(reason),
};

View file

@ -0,0 +1,22 @@
use crate::ast::{self, Attempting};
use crate::blankspace::space0_before;
use crate::expr::expr;
use crate::parser::{loc, Fail, Parser, State};
use bumpalo::Bump;
use roc_region::all::Located;
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}

View file

@ -4,8 +4,8 @@ use crate::expr::{global_tag, private_tag};
use crate::ident::join_module_parts;
use crate::keyword;
use crate::parser::{
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either,
ParseResult, Parser, State,
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail,
FailReason, ParseResult, Parser, State,
};
use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec;
@ -239,7 +239,13 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
Ok((first, state))
} else {
// e.g. `Int,Int` without an arrow and return type
panic!("Invalid function signature")
Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()),
},
state,
))
}
}
}