Skip to content

fixes #13 fixes #14 fixes #15 #16

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

Merged
merged 1 commit into from
Sep 14, 2022
Merged
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# Changelog
## [0.3.0] - 2022-09-13
### Added
- Multi-line freeform comments with `////` are now supported. They concatenate into a single comment.
### Changed
- Merge `SingleLineFreeForm` and `MultiLineFreeForm` into a single enum.
- No longer distinguish between the header and the body of a freeform comment. Now just grab everything into a single String.
- Don't special-case `#` in the grammar.
- Don't trim `#` from headers.
- Stopped removing lines that are just whitespace.

## [0.2.2] - 2022-09-13
### Fixed
- Rules without parameters were not detected
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "natspec_parser"
version = "0.2.2"
version = "0.3.0"
edition = "2021"

[dependencies]
Expand Down
10 changes: 2 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,8 @@ use std::fmt::Display;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum NatSpec {
SingleLineFreeForm {
header: String,
range: Range,
},

MultiLineFreeForm {
header: String,
block: String,
FreeForm {
text: String,
range: Range,
},

Expand Down
10 changes: 0 additions & 10 deletions src/parse/associated_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,6 @@ use crate::{AssociatedElement, Param, Ty};
use once_cell::sync::Lazy;
use regex::Regex;

// enum Token {
// Block(String),
// Rule,
// Methods,
// Invariant,
// Function,
// Ghost,
// Definition,
// }

//grabs all text between a pair of curly brackets, including the brackets.
//it keeps going through nested brackets, until it find a closing bracket that
//matches the opening curly bracket (that is, the string up to that point is "balanced")
Expand Down
51 changes: 5 additions & 46 deletions src/parse/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ use crate::util::span_to_range::{RangeConverter, Span, Spanned};
use crate::{AssociatedElement, DocumentationTag, NatSpec, Tag};
use color_eyre::eyre::bail;
use color_eyre::Report;
use itertools::Itertools;

#[derive(Debug, Clone)]
pub enum NatSpecBuilder {
FreeFormComment {
header: String,
block: Option<String>,
text: String,
span: Span,
},
Documentation {
Expand All @@ -24,21 +22,11 @@ pub enum NatSpecBuilder {
impl NatSpecBuilder {
pub fn build_with_converter(self, converter: RangeConverter) -> Result<NatSpec, Report> {
match self {
NatSpecBuilder::FreeFormComment {
header,
block,
span,
} => {
let range = converter.to_range(span);
let free_form = match block {
Some(block) => NatSpec::MultiLineFreeForm {
header,
block,
range,
},
_ => NatSpec::SingleLineFreeForm { header, range },
NatSpecBuilder::FreeFormComment { text, span } => {
let free_form = NatSpec::FreeForm {
text,
range: converter.to_range(span),
};

Ok(free_form)
}
NatSpecBuilder::Documentation {
Expand Down Expand Up @@ -135,35 +123,6 @@ impl NatSpecBuilder {

tags
}

pub(super) fn free_form_multi_line_from_body_and_span(
body: String,
span: Span,
) -> NatSpecBuilder {
let padding: &[_] = &[' ', '\t', '*', '\n'];
let mut lines = body.lines().map(|line| line.trim_matches(padding));

let header = lines
.next()
.map(String::from)
.expect("must exist from parser definition");

let block = {
let joined = lines.filter(|line| !line.is_empty()).join("\n");

if !joined.is_empty() {
Some(joined)
} else {
None
}
};

NatSpecBuilder::FreeFormComment {
span,
header,
block,
}
}
}

pub(super) fn split_starred_doc_lines(stream: Vec<char>, span: Span) -> Vec<(String, Span)> {
Expand Down
62 changes: 33 additions & 29 deletions src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@ use crate::util::span_to_range::Spanned;
use builder::NatSpecBuilder;
use chumsky::prelude::*;
use helpers::*;
use itertools::Itertools;

fn free_form_comment<'src>() -> BoxedParser<'src, char, NatSpecBuilder, Simple<char>> {
let padding_before_header = horizontal_ws()
.then(just('#'))
.then(horizontal_ws())
.boxed();

let slashes = just('/').repeated().at_least(4);
let thick_slashed_padding = slashes.then(newline_or_end());

let slashed_header = slashes
.ignore_then(padding_before_header.clone())
let slashed_free_form_line = slashes
.ignore_then(horizontal_ws())
.ignore_then(take_to_newline_or_end())
.collect()
.map(|line: String| {
Expand All @@ -27,15 +23,22 @@ fn free_form_comment<'src>() -> BoxedParser<'src, char, NatSpecBuilder, Simple<c
})
.boxed();

let slashed_single_line_free_form = slashed_header.clone();
let slashed_thick_free_form = slashed_header.padded_by(thick_slashed_padding);
let slashed_free_form = slashed_free_form_line
.clone()
.repeated()
.at_least(1)
.map(|body| body.into_iter().join("\n"))
.boxed();
let slashed_thick_free_form = slashed_free_form_line
.padded_by(thick_slashed_padding)
.boxed();

let stars = just('*').repeated().at_least(3);
let thick_starred_padding = just('/').then(stars).then(just('/')).then(newline_or_end());

let starred_header = just('/')
.ignore_then(stars)
.ignore_then(padding_before_header)
.ignore_then(horizontal_ws())
.ignore_then(take_to_starred_terminator())
.then_ignore(newline_or_end())
.collect()
Expand All @@ -48,30 +51,31 @@ fn free_form_comment<'src>() -> BoxedParser<'src, char, NatSpecBuilder, Simple<c
let starred_single_line_free_form = starred_header.clone();
let starred_thick_free_form = starred_header.padded_by(thick_starred_padding).boxed();

let free_form_single_line = choice((
slashed_single_line_free_form,
slashed_thick_free_form,
starred_single_line_free_form,
starred_thick_free_form,
))
.map_with_span(|header, span| NatSpecBuilder::FreeFormComment {
header,
block: None,
span,
});

let multi_line_first_line = just('/').then(stars).then(newline()).boxed();
let multi_line_body = take_until(just('#'))
.ignore_then(take_to_starred_terminator())
let starred_multi_line_first_line = just("/***").then(newline()).boxed();
let starred_body = take_to_starred_terminator()
.then_ignore(newline_or_end())
.collect()
.map(|body: String| {
let padding: &[_] = &[' ', '\t', '*', '\n'];
body.trim_end()
.lines()
.map(|line| line.trim_matches(padding))
.join("\n")
})
.boxed();
let free_form_multi_line = multi_line_first_line
.ignore_then(multi_line_body)
.map_with_span(NatSpecBuilder::free_form_multi_line_from_body_and_span)
let starred_free_form = starred_multi_line_first_line
.ignore_then(starred_body)
.boxed();

choice((free_form_single_line, free_form_multi_line)).boxed()
choice([
slashed_thick_free_form,
slashed_free_form,
starred_free_form,
starred_thick_free_form,
starred_single_line_free_form,
])
.map_with_span(|text, span| NatSpecBuilder::FreeFormComment { text, span })
.boxed()
}

fn commented_out_block<'src>() -> BoxedParser<'src, char, NatSpecBuilder, Simple<char>> {
Expand Down
117 changes: 83 additions & 34 deletions src/parse/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,32 +54,44 @@ fn free_form_comments() {

let parsed = parse_src(src);

assert_eq!(parsed.len(), 5);

assert_eq!(
parsed,
vec![
NatSpec::SingleLineFreeForm {
header: "Section example".to_string(),
range: range_from((0, 0), (1, 0))
},
NatSpec::SingleLineFreeForm {
header: "Centered example".to_string(),
range: range_from((2, 0), (3, 0))
},
NatSpec::SingleLineFreeForm {
header: "Thick centered example".to_string(),
range: range_from((4, 0), (7, 0))
},
NatSpec::SingleLineFreeForm {
header: "Thick example".to_string(),
range: range_from((8, 0), (11, 0))
},
NatSpec::MultiLineFreeForm {
header: "Multiline example".to_string(),
block: "Additional detail\nand more info".to_string(),
range: range_from((12, 0), (17, 0))
},
]
)
parsed[0],
NatSpec::FreeForm {
text: "# Section example".to_string(),
range: range_from((0, 0), (1, 0))
}
);
assert_eq!(
parsed[1],
NatSpec::FreeForm {
text: "# Centered example".to_string(),
range: range_from((2, 0), (3, 0))
}
);

assert_eq!(
parsed[2],
NatSpec::FreeForm {
text: "# Thick centered example".to_string(),
range: range_from((4, 0), (7, 0))
}
);
assert_eq!(
parsed[3],
NatSpec::FreeForm {
text: "# Thick example".to_string(),
range: range_from((8, 0), (11, 0))
}
);
assert_eq!(
parsed[4],
NatSpec::FreeForm {
text: "# Multiline example\nAdditional detail\nand more info".to_string(),
range: range_from((12, 0), (17, 0))
}
);
}

fn range_from((s_line, s_character): (u32, u32), (e_line, e_character): (u32, u32)) -> Range {
Expand Down Expand Up @@ -342,12 +354,11 @@ fn invariants() {
*/
invariant validOperator(address operator)
beneficiaryOf(operator) != 0 <=> ( operator != 0 && ownerOf(operator) != 0 && authorizerOf(operator) != 0 )



/**
@title Valid state of an operator ❌.
@notice Operators with assets must have an owner, a beneficiary, and an authorizer.

(unbondedValue(o) + lockedBonds(o)) > 0 ⟹
( ownerOf(o) ≠ 0 ⋀ beneficiaryOf(o) ≠ 0 ⋀ authorizerOf(o) ≠ 0 )
*/
Expand All @@ -356,18 +367,22 @@ fn invariants() {
"#};

let parsed = parse_src(src);
assert_eq!(parsed.len(), 2);

for (i, associated) in parsed.iter().map(NatSpec::associated_element).enumerate() {
let associated = associated.unwrap();
println!("natspec {i}:");
println!("{associated:?}");
}
assert_matches!(
parsed[0].associated_element().unwrap(),
AssociatedElement::Invariant { .. }
);
assert_matches!(
parsed[1].associated_element().unwrap(),
AssociatedElement::Definition { .. }
);
}

#[test]
fn rules_without_parameters() {
let src = indoc! {r#"
// Burning a larger amount of a token must reduce that token's balance more
/// Burning a larger amount of a token must reduce that token's balance more
/// than burning a smaller amount.
/// n.b. This rule holds for `burnBatch` as well due to rules establishing
/// appropriate equivance between `burn` and `burnBatch` methods.
Expand Down Expand Up @@ -403,3 +418,37 @@ fn rules_without_parameters() {
Some(AssociatedElement::Rule { .. })
);
}

#[test]
fn multiline_slashed_freeform_concatenates_to_a_single_comment() {
let src = indoc! {r#"
//// ## Verification of ERC1155Burnable
////
//// `ERC1155Burnable` extends the `ERC1155` functionality by wrapping the internal
//// methods `_burn` and `_burnBatch` in the public methods `burn` and `burnBatch`,
//// adding a requirement that the caller of either method be the account holding
//// the tokens or approved to act on that account's behalf.
////
//// ### Assumptions and Simplifications
////
//// - No changes made using the harness
////
//// ### Properties

methods {
balanceOf(address, uint256) returns uint256 envfree
isApprovedForAll(address,address) returns bool envfree
}
"#};

let natspec = parse_src(src)
.into_iter()
.at_most_one()
.expect("parses to exactly one element");

if let Some(NatSpec::FreeForm { text, .. }) = natspec {
assert_eq!(text, "## Verification of ERC1155Burnable\n\n`ERC1155Burnable` extends the `ERC1155` functionality by wrapping the internal\nmethods `_burn` and `_burnBatch` in the public methods `burn` and `burnBatch`,\nadding a requirement that the caller of either method be the account holding\nthe tokens or approved to act on that account's behalf.\n\n### Assumptions and Simplifications\n\n- No changes made using the harness\n\n### Properties");
} else {
panic!("should have been parsed as documentation")
}
}
4 changes: 4 additions & 0 deletions src/python_wrapper/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Changelog
## [0.3.0] - 2022-09-13
### Changed
- Definition of `FreeForm` was simplified to match the definition in `natspec_parser`: Replaced `header` and `block` with `text`.
2 changes: 1 addition & 1 deletion src/python_wrapper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "python_wrapper"
version = "0.2.0"
version = "0.3.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
Loading