forked from NomicFoundation/slang
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
232 additions
and
30 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
crates/solidity/outputs/cargo/tests/src/doc_examples/fixtures/ERC721.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
pragma solidity ^0.4.20; | ||
|
||
interface ERC165 { | ||
function supportsInterface(bytes4 interfaceID) external view returns (bool); | ||
} | ||
|
||
interface ERC721 { | ||
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); | ||
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); | ||
event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); | ||
|
||
function balanceOf(address _owner) external view returns (uint256); | ||
|
||
function ownerOf(uint256 _tokenId) external view returns (address); | ||
|
||
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; | ||
|
||
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; | ||
|
||
function transferFrom(address _from, address _to, uint256 _tokenId) external payable; | ||
|
||
function approve(address _approved, uint256 _tokenId) external payable; | ||
|
||
function setApprovalForAll(address _operator, bool _approved) external; | ||
|
||
function getApproved(uint256 _tokenId) external view returns (address); | ||
|
||
function isApprovedForAll(address _owner, address _operator) external view returns (bool); | ||
} |
152 changes: 152 additions & 0 deletions
152
crates/solidity/outputs/cargo/tests/src/doc_examples/parsing_erc721.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
use anyhow::Result; | ||
use semver::Version; | ||
|
||
use slang_solidity::{ | ||
cst::Node, | ||
kinds::{ProductionKind, RuleKind, TokenKind}, | ||
language::Language, | ||
}; | ||
|
||
const ERC721: &str = include!("../fixtures/erc721.sol"); | ||
|
||
#[test] | ||
fn cursor_api() -> Result<()> { | ||
let language = Language::new(Version::new(0, 8, 0))?; | ||
let parse_output = language.parse(ProductionKind::ContractDefinition, "contract Foo {}"); | ||
|
||
let mut contract_names = Vec::new(); | ||
let mut cursor = parse_output.create_tree_cursor(); | ||
while let Some(_rule_node) = cursor.find_rule_with_kind(&[RuleKind::ContractDefinition]) { | ||
// You have to make sure you return the cursor to original position | ||
if cursor.go_to_first_child() { | ||
while let Some(token_node) = cursor.find_token_with_kind(&[TokenKind::Identifier]) { | ||
contract_names.push(token_node.text.clone()); | ||
cursor.go_to_next_non_descendent(); | ||
} | ||
cursor.go_to_parent(); | ||
} | ||
|
||
cursor.go_to_next_non_descendent(); | ||
// if you wanted to continue to recurse into the | ||
// children of the contract definition | ||
// you would call `cursor.go_to_next()` | ||
} | ||
assert!(matches!(&contract_names[..], [single] if single == "Foo")); | ||
|
||
return Ok(()); | ||
} | ||
|
||
#[test] | ||
fn cursor_api_using_spawn() -> Result<()> { | ||
let language = Language::new(Version::parse("0.8.0")?)?; | ||
let parse_output = language.parse(ProductionKind::ContractDefinition, "contract Foo {}"); | ||
|
||
let mut contract_names = Vec::new(); | ||
let mut cursor = parse_output.create_tree_cursor(); | ||
while let Some(_rule_node) = cursor.find_rule_with_kind(&[RuleKind::ContractDefinition]) { | ||
// `.spawn()` creates a new cursor without the path history, which is cheaper | ||
// than `.clone()`, which copies the path history. | ||
// Do this when you don't want to worry about restoring the position of the | ||
// existing cursor. | ||
let mut child_cursor = cursor.spawn(); | ||
if child_cursor.go_to_first_child() { | ||
// We don't recurse because we just want to check the immediate children | ||
while let Some(token_node) = child_cursor.find_token_with_kind(&[TokenKind::Identifier]) | ||
{ | ||
contract_names.push(token_node.text.clone()); | ||
child_cursor.go_to_next_non_descendent(); | ||
} | ||
} | ||
|
||
cursor.go_to_next_non_descendent(); | ||
// if you wanted to continue to recurse into the | ||
// children of the contract definition | ||
// you would call `cursor.go_to_next()` | ||
} | ||
assert!(matches!(&contract_names[..], [single] if single == "Foo")); | ||
|
||
return Ok(()); | ||
} | ||
|
||
#[test] | ||
fn cursor_api_using_iter() -> Result<()> { | ||
let language = Language::new(Version::parse("0.8.0")?)?; | ||
let parse_output = language.parse(ProductionKind::ContractDefinition, "contract Foo {}"); | ||
|
||
let mut contract_names = Vec::new(); | ||
let mut cursor = parse_output.create_tree_cursor(); | ||
while let Some(_rule_node) = cursor.find_rule_with_kind(&[RuleKind::ContractDefinition]) { | ||
if let Some(token_node) = _rule_node | ||
.children | ||
.iter() | ||
.find_map(|node| node.as_token_with_kind(&[TokenKind::Identifier])) | ||
{ | ||
contract_names.push(token_node.text.clone()); | ||
} | ||
|
||
cursor.go_to_next_non_descendent(); | ||
// if you wanted to continue to recurse into the | ||
// children of the contract definition | ||
// you would call `cursor.go_to_next()` | ||
} | ||
assert!(matches!(&contract_names[..], [single] if single == "Foo")); | ||
|
||
return Ok(()); | ||
} | ||
|
||
#[test] | ||
fn cursor_api_using_iter_combinators() -> Result<()> { | ||
let language = Language::new(Version::parse("0.8.0")?)?; | ||
let parse_output = language.parse(ProductionKind::ContractDefinition, "contract Foo {}"); | ||
|
||
let cursor = parse_output.create_tree_cursor(); | ||
|
||
let contract_names: Vec<_> = cursor | ||
.filter_map(|node| { | ||
let node = node.as_rule_with_kind(&[RuleKind::ContractDefinition])?; | ||
let name = node | ||
.children | ||
.iter() | ||
.find_map(|node| node.as_token_with_kind(&[TokenKind::Identifier]))?; | ||
|
||
Some(name.text.clone()) | ||
}) | ||
.collect(); | ||
|
||
assert_eq!(contract_names, &["Foo"]); | ||
|
||
return Ok(()); | ||
} | ||
|
||
#[test] | ||
fn cursor_as_iter() -> Result<()> { | ||
let language = Language::new(Version::parse("0.8.0")?)?; | ||
let parse_output = language.parse(ProductionKind::ContractDefinition, "contract Foo {}"); | ||
|
||
let mut cursor = parse_output.create_tree_cursor(); | ||
assert_eq!( | ||
cursor.node().as_rule().unwrap().kind, | ||
RuleKind::ContractDefinition | ||
); | ||
|
||
macro_rules! assert_next_is { | ||
($pattern:pat $(if $guard:expr)? $(,)?) => { | ||
assert!(matches!(cursor.next(), $pattern $(if $guard)?)); | ||
}; | ||
} | ||
|
||
assert_next_is!(Some(Node::Rule(rule)) if rule.kind == RuleKind::ContractDefinition); | ||
{ | ||
assert_next_is!(Some(Node::Token(token)) if token.kind == TokenKind::ContractKeyword); | ||
assert_next_is!(Some(Node::Rule(rule)) if rule.kind == RuleKind::LeadingTrivia); | ||
assert_next_is!(Some(Node::Token(token)) if token.kind == TokenKind::Whitespace); | ||
assert_next_is!(Some(Node::Token(token)) if token.kind == TokenKind::Identifier && token.text == "Foo"); | ||
assert_next_is!(Some(Node::Rule(rule)) if rule.kind == RuleKind::LeadingTrivia); | ||
assert_next_is!(Some(Node::Token(token)) if token.kind == TokenKind::Whitespace); | ||
assert_next_is!(Some(Node::Token(token)) if token.kind == TokenKind::OpenBrace); | ||
assert_next_is!(Some(Node::Token(token)) if token.kind == TokenKind::CloseBrace); | ||
} | ||
assert_next_is!(None); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
- [Index](./index.md) | ||
- [Overview](./overview.md) | ||
- [Parsing](./parsing) | ||
- [Parsing](./parsing/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
- [Index](./index.md) | ||
- [HowTo](./howto.md) | ||
- [Parsing](./index.md) | ||
- [Use-case: Parsing ERC721](./parsing-erc721.md) |
Empty file.
26 changes: 25 additions & 1 deletion
26
documentation/public/user-guide/cargo-crate/parsing/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,25 @@ | ||
Parsing hello | ||
## Parser API | ||
|
||
Similar to the CLI, the API also requires passing a language version to create a `Language` object. | ||
You can then use it to parse different inputs belonging to that version. | ||
Each `parse()` operation accepts the input source code, and a `ProductionKind` variant. | ||
This allows callers to parse entire source files (`ProductionKind::SourceUnit`), individual contracts (`ProductionKind::ContractDefinition`), | ||
methods (`ProductionKind::FunctionDefinition`), or any other syntax nodes. | ||
|
||
The resulting `ParseOutput` object will contain syntax errors (if any), and the parse tree corresponding to the input source code. | ||
You can then iterate over the resulting children, and assert that they match the expected syntax nodes: | ||
|
||
```{ .rust } | ||
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/simple_contract.rs" | ||
``` | ||
|
||
## Cursor API | ||
|
||
For many code analysis tasks, it is useful to traverse the parse tree and visit each node. | ||
The `Cursor` object allows callers to traverse the parse tree in a pre-order depth-first manner. | ||
|
||
The below example uses a cursor to collect the names of all contracts in a source file, and returns them as a `Vec<String>`: | ||
|
||
```{ .rust } | ||
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/cursor_api.rs" | ||
``` |
23 changes: 23 additions & 0 deletions
23
documentation/public/user-guide/cargo-crate/parsing/parsing-erc721.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Use-case: Parsing ERC721 | ||
|
||
Let's say we want to parse the interface of the [ERC-721](https://eips.ethereum.org/EIPS/eip-721) standard. | ||
Here it is: | ||
|
||
```{ .solidity } | ||
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/fixtures/erc721.sol" | ||
``` | ||
|
||
We want to parse the entire file, so we use the `ProductionKind::SourceUnit` as the target production. | ||
Let's say that we're using Solidity `v0.8.0` and we mean to parse it using it: | ||
|
||
# How to parse a Solidity file and navigate the Concrete Syntax Tree (CST) | ||
|
||
First, we need to specify the version of the language that we want to parse. | ||
Different Solidity versions can include | ||
|
||
- per version | ||
- per production kind | ||
|
||
To parse a file, we need to first specify what kind of grammar production | ||
Slang exposes a `Language::parse()` | ||
We use `Language::parse()` |