Skip to content

Commit

Permalink
WIP2
Browse files Browse the repository at this point in the history
  • Loading branch information
Xanewok committed Nov 24, 2023
1 parent 3117f27 commit d5c8a5b
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 30 deletions.
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 crates/solidity/outputs/cargo/tests/src/doc_examples/parsing_erc721.rs
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(())
}
2 changes: 1 addition & 1 deletion documentation/public/user-guide/cargo-crate/NAV.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
- [Index](./index.md)
- [Overview](./overview.md)
- [Parsing](./parsing)
- [Parsing](./parsing/)
26 changes: 0 additions & 26 deletions documentation/public/user-guide/cargo-crate/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,3 @@ You can also print the CST serialized as JSON by passing the `--json` flag:
```bash
slang_solidity parse "path/to/input.sol" --version "0.8.0" [--json]
```

## 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"
```
4 changes: 2 additions & 2 deletions documentation/public/user-guide/cargo-crate/parsing/NAV.md
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 documentation/public/user-guide/cargo-crate/parsing/index.md
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"
```
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()`

0 comments on commit d5c8a5b

Please sign in to comment.