Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] [zero-copy] Static analysis #295

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions examples/zero-copy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use chumsky::prelude::*;
use chumsky::problems::Problems;

#[derive(PartialEq, Debug)]
enum Token<'a> {
Expand Down Expand Up @@ -27,6 +28,17 @@ fn parser<'a>() -> impl Parser<'a, &'a str, [(SimpleSpan<usize>, Token<'a>); 6]>
.collect_exactly()
}

fn bad_parser<'a>() -> impl Parser<'a, &'a str, Vec<()>, extra::Err<Rich<'a, char>>> + Problems {
just("").or(just("b"))
.or_not()
.separated_by(just(""))
.ignored()
.then(just("").ignored())
.ignored()
.repeated()
.collect::<_>()
}

fn main() {
assert_eq!(
parser()
Expand All @@ -41,4 +53,9 @@ fn main() {
((31..37).into(), Token::Ident("tokens")),
]),
);

let p = bad_parser();
for p in p.find_problems() {
println!("{p}");
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub mod input;
pub mod label;
pub mod primitive;
mod private;
pub mod problems;
pub mod recovery;
pub mod recursive;
#[cfg(feature = "regex")]
Expand All @@ -69,6 +70,7 @@ pub mod span;
mod stream;
pub mod text;
pub mod util;
pub(crate) mod visit;

/// Commonly used functions, traits and types.
///
Expand Down
4 changes: 2 additions & 2 deletions src/primitive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl<T> Default for JustCfg<T> {

/// See [`just`].
pub struct Just<T, I, E = EmptyErr> {
seq: T,
pub(crate) seq: T,
#[allow(dead_code)]
phantom: EmptyPhantom<(E, I)>,
}
Expand Down Expand Up @@ -838,7 +838,7 @@ where
/// See [`choice`].
#[derive(Copy, Clone)]
pub struct Choice<T> {
parsers: T,
pub(crate) parsers: T,
}

/// Parse using a tuple of many parsers, producing the output of the first to successfully parse.
Expand Down
90 changes: 90 additions & 0 deletions src/problems.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! TODO

use crate::visit::{ParserInfo, ParserVisitor, Visitable};

#[derive(Clone, PartialEq)]
enum CheckState {
None,
Check(String),
Defer(String),
}

struct ProblemVisitor {
problems: Vec<String>,

first: bool,
check_nonempty: CheckState,
}

impl ProblemVisitor {
fn new() -> Self {
ProblemVisitor {
problems: Vec::new(),

first: true,
check_nonempty: CheckState::None,
}
}

fn into_problems(self) -> Vec<String> {
self.problems
}
}

impl ParserVisitor for ProblemVisitor {
fn visit<P>(&mut self, info: &ParserInfo<'_, P>)
where
P: ?Sized + Visitable,
{
if self.first {
self.first = false;
if info.size_hint.lower() == 0 {
self.problems.push(format!(
"The top-level `{}` parser can potentially consume no input, meaning that it cannot fail.\nThis is probably a bug.\nConsider adding `.then_ignore(end())` to the parser.", info.name
))
}
}

let state = core::mem::replace(&mut self.check_nonempty, CheckState::None);
if let CheckState::Check(name) = state {
if info.size_hint.lower() == 0 {
self.problems.push(format!(
"`{}` parser has an inner parser that can potentially consume no input, meaning that it cannot fail.\nThis is probably a bug, because it could lead to an infinite loop.\nConsider using `.at_least(1)` to force the inner parser to consume some input.",
name,
))
}
self.check_nonempty = CheckState::Defer(name);
}

match info.name {
"repeated" | "separated_by" => {
let old = core::mem::replace(
&mut self.check_nonempty,
CheckState::Check(info.name.to_string())
);
info.visit(self);
self.check_nonempty = old;
}
_ => info.visit(self),
}

let state = core::mem::replace(&mut self.check_nonempty, CheckState::None);
if let CheckState::Defer(name) = state {
self.check_nonempty = CheckState::Check(name);
}
}
}

/// TODO
pub trait Problems: Visitable {
/// TODO
fn find_problems(&self) -> Vec<String>;
}

impl<P: Visitable> Problems for P {
fn find_problems(&self) -> Vec<String> {
let mut visitor = ProblemVisitor::new();
self.visit(&mut visitor);
visitor.into_problems()
}
}
Loading