Skip to content

Commit c50d94f

Browse files
committed
release 2.0.0 candidate: Python API overhaul
1 parent ca9b1a2 commit c50d94f

21 files changed

+666
-670
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ defines:
22
smoke-test: &smoke-test
33
name: Smoke test
44
command: |
5-
wget https://raw.githubusercontent.com/Certora/natspecTools/master/tests/basic_tests/definition_test.spec
5+
wget https://raw.githubusercontent.com/Certora/cvldocTool/master/tests/basic_tests/definition_test.spec
66
77
python -c "import cvldoc_parser
8-
parsed = cvldoc_parser.parse([\"definition_test.spec\"])
9-
assert len(parsed[0]) == 3, \"should parse to 3 elements\""
8+
parsed = cvldoc_parser.parse(\"definition_test.spec\")
9+
assert len(parsed) == 3, \"should parse to 3 elements\""
1010
1111
store-and-persist:
1212
- store_artifacts: &store-wheel

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
**/Cargo.lock
33
**/.DS_Store
44
BUGS
5-
**/python_wrapper/.env
5+
**/.env

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
# Changelog
2+
## [2.0.0] - 2023-09-13
3+
### Added
4+
- Support parsing of `hook`, `use`, `using`, `import` statements.
5+
- Python module API has been simplified.
6+
### Changed
7+
- CVL2 syntax is now enforced. Parsing of code which is not compliant with CVL2 has been removed.
8+
### Fixed
9+
- Some issues with `Invariant` parsing have been fixed.
10+
211
## [1.0.3] - 2023-05-27
312
### Fixed
413
- Fix issue that could cause infinite recursion on certain input

Cargo.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
[package]
22
name = "cvldoc_parser_core"
3-
version = "1.0.3"
3+
version = "2.0.0"
44
edition = "2021"
55

66
[dependencies]
77
assert_matches = "1.5.0"
88
chumsky = "0.9.2"
99
color-eyre = "0.6.2"
10-
indoc = "2.0.1"
11-
itertools = "0.10.5"
10+
indoc = "2"
11+
itertools = "0.11"
1212
lsp-types = "0.94.0"
1313
once_cell = "1.17.1"
14-
regex = "1.8.2"
14+
regex = "1"
1515
ropey = "1.6.0"
16-
serde = { version = "1.0.163", features = ["derive"] }
17-
tap = "1.0.1"
16+
serde = { version = "1", features = ["derive"] }
17+
tap = "1"

src/diagnostics.rs

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ impl Ast {
1010
Ast::Invariant { .. } => &[Title, Notice, Dev, Param],
1111
Ast::Function { .. } => &[Notice, Dev, Param, Return],
1212
Ast::Definition { .. } => &[Notice, Dev, Param, Return],
13-
Ast::Ghost { .. } | Ast::GhostMapping { .. } => &[Notice, Dev, Param, Return],
13+
Ast::GhostFunction { .. } | Ast::GhostMapping { .. } => &[Notice, Dev, Param, Return],
1414
Ast::Methods { .. } => &[Notice, Dev],
1515
Ast::FreeFormComment { .. } => &[Notice, Dev],
16-
Ast::Import(..)
16+
Ast::Import { .. }
1717
| Ast::Using { .. }
1818
| Ast::UseRule { .. }
1919
| Ast::UseBuiltinRule { .. }
@@ -42,6 +42,7 @@ const WARNING: DiagnosticSeverity = DiagnosticSeverity::WARNING;
4242
const ERROR: DiagnosticSeverity = DiagnosticSeverity::ERROR;
4343

4444
enum DiagSpan<'a> {
45+
#[allow(unused)]
4546
EntireDoc,
4647
SingleTag(&'a DocumentationTag),
4748
}
@@ -50,58 +51,60 @@ impl CvlElement {
5051
pub fn enumerate_diagnostics(&self, converter: RangeConverter) -> Vec<Diagnostic> {
5152
let mut diagnostics = Vec::new();
5253

53-
if let Some(doc) = &self.doc {
54-
let mut add = |message, diag_span, severity| {
55-
let span = match diag_span {
56-
DiagSpan::EntireDoc => self.element_span.clone(),
57-
DiagSpan::SingleTag(tag) => tag.span.clone(),
58-
};
54+
let mut add = |message, diag_span, severity| {
55+
let span = match diag_span {
56+
DiagSpan::EntireDoc => self.element_span.clone(),
57+
DiagSpan::SingleTag(tag) => tag.span.clone(),
58+
};
5959

60-
let diag = Diagnostic {
61-
range: converter.to_range(span),
62-
severity: Some(severity),
63-
message,
64-
..Default::default()
65-
};
66-
diagnostics.push(diag);
60+
let diag = Diagnostic {
61+
range: converter.to_range(span),
62+
severity: Some(severity),
63+
message,
64+
..Default::default()
6765
};
66+
diagnostics.push(diag);
67+
};
6868

69-
if doc.iter().all(|tag| tag.kind != TagKind::Notice) {
70-
//Any applicable item is missing a notice
71-
let message = "associated element is undocumented".to_string();
72-
add(message, DiagSpan::EntireDoc, WARNING);
73-
}
69+
// disabled for now. this diagnostic is overly broad.
70+
// if self.doc.iter().all(|tag| tag.kind != TagKind::Notice) {
71+
// //Any applicable item is missing a notice
72+
// let message = "associated element is undocumented".to_string();
73+
// add(message, DiagSpan::EntireDoc, WARNING);
74+
// }
7475

75-
let tags_with_params = doc.iter().filter_map(|tag| {
76-
let param = tag.param_name()?;
77-
Some((tag, param))
78-
});
76+
let tags_with_params = self.doc.iter().filter_map(|tag| {
77+
let param = tag.param_name()?;
78+
Some((tag, param))
79+
});
7980

80-
for (i, (tag, param)) in tags_with_params.enumerate() {
81-
if !self.ast.defines_param(param) {
82-
//A @param is provided for a non-existent parameter
83-
let message = format!("no such parameter: {param}");
84-
add(message, DiagSpan::SingleTag(tag), ERROR);
85-
} else if doc[..i].iter().any(|tag| tag.param_name() == Some(param)) {
86-
//Each parameter must be documented at most once
87-
let message = "parameter is already documented".to_string();
88-
add(message, DiagSpan::SingleTag(tag), ERROR);
89-
}
81+
for (i, (tag, param)) in tags_with_params.enumerate() {
82+
if !self.ast.defines_param(param) {
83+
//A @param is provided for a non-existent parameter
84+
let message = format!("no such parameter: {param}");
85+
add(message, DiagSpan::SingleTag(tag), ERROR);
86+
} else if self.doc[..i]
87+
.iter()
88+
.any(|tag| tag.param_name() == Some(param))
89+
{
90+
//Each parameter must be documented at most once
91+
let message = "parameter is already documented".to_string();
92+
add(message, DiagSpan::SingleTag(tag), ERROR);
9093
}
94+
}
9195

92-
for tag in doc {
93-
if !self.ast.supports(&tag.kind) {
94-
let message = format!("this tag is unsupported for {} blocks", self.ast);
95-
add(message, DiagSpan::SingleTag(tag), ERROR);
96-
}
96+
for tag in &self.doc {
97+
if !self.ast.supports(&tag.kind) {
98+
let message = format!("this tag is unsupported for {} blocks", self.ast);
99+
add(message, DiagSpan::SingleTag(tag), ERROR);
97100
}
101+
}
98102

99-
for tag in doc {
100-
if let TagKind::Unexpected(unexpected_tag) = &tag.kind {
101-
//Unrecognized tags appear anywhere
102-
let message = format!("@{unexpected_tag} is unrecognized");
103-
add(message, DiagSpan::SingleTag(tag), WARNING);
104-
}
103+
for tag in &self.doc {
104+
if let TagKind::Unexpected(unexpected_tag) = &tag.kind {
105+
//Unrecognized tags appear anywhere
106+
let message = format!("@{unexpected_tag} is unrecognized");
107+
add(message, DiagSpan::SingleTag(tag), WARNING);
105108
}
106109
}
107110

src/lib.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ pub mod diagnostics;
22
pub mod parse;
33
pub mod util;
44

5+
use serde::Serialize;
56
use std::fmt::{Debug, Display};
67
use std::sync::Arc;
78
use util::{ByteSpan, Span};
89

9-
#[derive(Clone, PartialEq, Eq)]
10+
#[derive(Clone, PartialEq, Eq, Serialize)]
1011
pub struct CvlElement {
11-
pub doc: Option<Vec<DocumentationTag>>,
12+
pub doc: Vec<DocumentationTag>,
1213
pub ast: Ast,
1314
pub element_span: Span,
1415
pub doc_span: Option<Span>,
16+
#[serde(skip)]
1517
pub src: Arc<str>,
1618
}
1719

@@ -26,7 +28,7 @@ impl Debug for CvlElement {
2628
}
2729
}
2830

29-
#[derive(Clone, Debug, PartialEq, Eq)]
31+
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
3032
pub struct Param {
3133
pub ty: String,
3234
pub name: String,
@@ -40,7 +42,8 @@ impl Param {
4042
}
4143
}
4244

43-
#[derive(Debug, Clone, PartialEq, Eq)]
45+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
46+
#[serde(tag = "type")]
4447
pub enum Ast {
4548
FreeFormComment {
4649
text: String,
@@ -70,7 +73,7 @@ pub enum Ast {
7073
returns: String,
7174
definition: String,
7275
},
73-
Ghost {
76+
GhostFunction {
7477
name: String,
7578
ty_list: Vec<String>,
7679
returns: String,
@@ -84,7 +87,9 @@ pub enum Ast {
8487
Methods {
8588
block: String,
8689
},
87-
Import(String),
90+
Import {
91+
imported: String,
92+
},
8893
Using {
8994
contract_name: String,
9095
spec_name: String,
@@ -125,14 +130,14 @@ pub enum Ast {
125130

126131
impl CvlElement {
127132
pub fn title(&self) -> Option<String> {
128-
let from_title_tag = self.doc.iter().flatten().find_map(|tag| {
129-
if tag.kind == TagKind::Title {
133+
let from_title_tag = self.doc.iter().find_map(|tag| {
134+
if matches!(tag.kind, TagKind::Title) {
130135
Some(tag.description.clone())
131136
} else {
132137
None
133138
}
134139
});
135-
let from_name = || self.ast.name().map(String::from);
140+
let from_name = || self.ast.name().map(ToOwned::to_owned);
136141

137142
from_title_tag.or_else(from_name)
138143
}
@@ -154,7 +159,7 @@ impl CvlElement {
154159
}
155160
}
156161

157-
#[derive(Debug, Clone, PartialEq, Eq)]
162+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
158163
pub struct DocumentationTag {
159164
pub kind: TagKind,
160165
pub description: String,
@@ -192,7 +197,7 @@ impl DocumentationTag {
192197
}
193198
}
194199

195-
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
200+
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default, Serialize)]
196201
pub enum TagKind {
197202
Title,
198203
#[default]
@@ -275,10 +280,10 @@ impl Display for Ast {
275280
Ast::Invariant { .. } => "invariant",
276281
Ast::Function { .. } => "function",
277282
Ast::Definition { .. } => "definition",
278-
Ast::Ghost { .. } | Ast::GhostMapping { .. } => "ghost",
283+
Ast::GhostFunction { .. } | Ast::GhostMapping { .. } => "ghost",
279284
Ast::Methods { .. } => "methods",
280285
Ast::FreeFormComment { .. } => "freeform comment",
281-
Ast::Import(..) => "import",
286+
Ast::Import { .. } => "import",
282287
Ast::Using { .. } => "using",
283288
Ast::UseRule { .. } | Ast::UseBuiltinRule { .. } | Ast::UseInvariant { .. } => "use",
284289
Ast::HookSload { .. }
@@ -298,7 +303,7 @@ impl Ast {
298303
| Ast::Invariant { name, .. }
299304
| Ast::Function { name, .. }
300305
| Ast::Definition { name, .. }
301-
| Ast::Ghost { name, .. }
306+
| Ast::GhostFunction { name, .. }
302307
| Ast::GhostMapping { name, .. } => Some(name.as_str()),
303308
_ => None,
304309
}
@@ -325,7 +330,7 @@ impl Ast {
325330
| Ast::HookOpcode { block, .. } => Some(block.as_str()),
326331

327332
Ast::Invariant { proof: block, .. }
328-
| Ast::Ghost { axioms: block, .. }
333+
| Ast::GhostFunction { axioms: block, .. }
329334
| Ast::GhostMapping { axioms: block, .. } => block.as_ref().map(String::as_str),
330335

331336
_ => None,
@@ -334,15 +339,17 @@ impl Ast {
334339

335340
pub fn returns(&self) -> Option<&str> {
336341
match self {
337-
Ast::Function { returns, .. } => returns.as_ref().map(String::as_str),
338-
Ast::Definition { returns, .. } | Ast::Ghost { returns, .. } => Some(returns.as_str()),
342+
Ast::Function { returns, .. } => returns.as_deref(),
343+
Ast::Definition { returns, .. } | Ast::GhostFunction { returns, .. } => {
344+
Some(returns.as_str())
345+
}
339346
_ => None,
340347
}
341348
}
342349

343350
pub fn ty_list(&self) -> Option<&[String]> {
344351
match self {
345-
Ast::Ghost { ty_list, .. } => Some(ty_list),
352+
Ast::GhostFunction { ty_list, .. } => Some(ty_list),
346353
_ => None,
347354
}
348355
}

0 commit comments

Comments
 (0)