Skip to content

Commit 440a718

Browse files
committed
Add Rust bindings
1 parent 9ddaa72 commit 440a718

File tree

4 files changed

+288
-82
lines changed

4 files changed

+288
-82
lines changed

Cargo.toml

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,50 @@
11
[package]
2-
name = "tree-sitter-apex"
3-
description = "apex grammar for the tree-sitter parsing library"
4-
version = "0.0.1"
5-
keywords = ["incremental", "parsing", "apex"]
6-
categories = ["parsing", "text-editors"]
7-
repository = "https://github.com/aheber/tree-sitter-apex"
8-
edition = "2018"
2+
name = "tree-sitter-sfapex"
3+
description = "Salesforce Apex, SOQL, SOQL, and Log grammars for tree-sitter"
4+
version = "2.4.0"
5+
authors = ["Anthony Heber <anthony@heber.dev>"]
96
license = "MIT"
7+
readme = "README.md"
8+
keywords = [
9+
"incremental",
10+
"parsing",
11+
"tree-sitter",
12+
"salesforce",
13+
"apex",
14+
"soql",
15+
"sosl",
16+
]
17+
categories = ["parsing", "text-editors"]
18+
repository = "https://github.com/aheber/tree-sitter-sfapex"
19+
edition = "2021"
20+
autoexamples = false
1021

1122
build = "bindings/rust/build.rs"
1223
include = [
13-
"bindings/rust/*",
14-
"grammar.js",
15-
"queries/*",
16-
"src/*",
24+
"common",
25+
"bindings/rust",
26+
"apex/grammar.js",
27+
"apex/src",
28+
"apex/queries",
29+
"soql/grammar.js",
30+
"soql/src",
31+
"soql/queries",
32+
"sosl/grammar.js",
33+
"sosl/src",
34+
"sosl/queries",
35+
"sflog/grammar.js",
36+
"sflog/src",
37+
"sflog/queries",
1738
]
1839

1940
[lib]
2041
path = "bindings/rust/lib.rs"
2142

2243
[dependencies]
23-
tree-sitter = "~0.20"
44+
tree-sitter-language = "0.1"
2445

2546
[build-dependencies]
26-
cc = "1.0"
47+
cc = "1.1"
48+
49+
[dev-dependencies]
50+
tree-sitter = "0.24"

README.md

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ plugin already, [guidance](nvim-treesitter-setup.md).
99

1010
## Usage
1111

12-
### Install
12+
### Node
13+
14+
#### Install
1315

1416
```sh
1517
npm install tree-sitter
1618

1719
npm install tree-sitter-sfapex
1820
```
1921

20-
### Example
22+
#### Example
2123

2224
```JavaScript
2325
// import libraries
@@ -79,6 +81,92 @@ function printTree(node, indent = 0) {
7981

8082
```
8183

84+
### Rust
85+
86+
#### Install
87+
88+
```sh
89+
cargo add tree-sitter
90+
91+
cargo add tree-sitter-sfapex
92+
```
93+
94+
#### Example
95+
96+
```Rust
97+
use tree_sitter::{Parser, TreeCursor};
98+
99+
// just a super simple example of printing the discovered nodes
100+
// to see the anonymous nodes (syntax without formal names) set this to `true`
101+
const INCLUDE_ANONYMOUS_NODES: bool = true;
102+
103+
fn main() {
104+
let mut parser = Parser::new();
105+
106+
let language_fn = tree_sitter_sfapex::apex::LANGUAGE;
107+
parser.set_language(&language_fn.into()).unwrap();
108+
109+
let source_code = r#"
110+
/**
111+
* block comment
112+
*/
113+
global class TestClass implements TestInterface {
114+
public static String Prop1 = 'TestVal';
115+
116+
global Account setName(Account acct, String nameVal){
117+
acct.Name = nameVal;
118+
return acct;
119+
}
120+
}"#;
121+
let tree = parser.parse(source_code, None).unwrap();
122+
123+
124+
println!("APEX TREE");
125+
print_tree(&mut tree.root_node().walk(), INCLUDE_ANONYMOUS_NODES, 0);
126+
127+
// do it with some SOQL this time
128+
let language_fn = tree_sitter_sfapex::soql::LANGUAGE;
129+
parser.set_language(&language_fn.into()).unwrap();
130+
131+
let soql_source_code = r#"
132+
SELECT Id, Name, Parent.Name,
133+
TYPEOF Owner
134+
WHEN User THEN Id, Username, FederationId
135+
WHEN Group THEN Name
136+
END,
137+
(SELECT Id, Name FROM Contacts)
138+
FROM Account
139+
WHERE Name = 'Robots' AND Are_Coming__c = FALSE"#;
140+
141+
let tree = parser.parse(soql_source_code, None).unwrap();
142+
143+
println!("SOQL TREE");
144+
print_tree(&mut tree.root_node().walk(), INCLUDE_ANONYMOUS_NODES, 0);
145+
146+
}
147+
148+
fn print_tree(cursor: &mut TreeCursor, include_anonymous_nodes: bool, indent: usize) {
149+
let t_node = cursor.node();
150+
println!("{}{}{}{}", " ".repeat(indent),
151+
(if t_node.is_named() {"("} else {"\""}).to_owned(),
152+
t_node.kind(),
153+
(if t_node.is_named() {")"} else {"\""}).to_owned());
154+
155+
if cursor.goto_first_child() {
156+
loop {
157+
if cursor.node().is_named() || include_anonymous_nodes {
158+
print_tree(cursor, include_anonymous_nodes, indent + 2);
159+
}
160+
if !cursor.goto_next_sibling() {
161+
break;
162+
}
163+
}
164+
// when we're done here, go up to the parent again
165+
cursor.goto_parent();
166+
}
167+
}
168+
```
169+
82170
## Status
83171

84172
Most of the parsers are built and tested on large corpus of Apex, I still intend to write automated tests that parse large Apex libraries as part of evaluating the grammar.

bindings/rust/build.rs

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,25 @@
11
fn main() {
2-
let src_dir = std::path::Path::new("src");
2+
let root_dir = std::path::Path::new(".");
3+
let apex_dir = root_dir.join("apex").join("src");
4+
let soql_dir = root_dir.join("soql").join("src");
5+
let sosl_dir = root_dir.join("sosl").join("src");
6+
let sflog_dir = root_dir.join("sflog").join("src");
37

4-
let mut c_config = cc::Build::new();
5-
c_config.include(&src_dir);
6-
c_config
7-
.flag_if_supported("-Wno-unused-parameter")
8-
.flag_if_supported("-Wno-unused-but-set-variable")
9-
.flag_if_supported("-Wno-trigraphs");
10-
let parser_path = src_dir.join("parser.c");
11-
c_config.file(&parser_path);
8+
let mut config = cc::Build::new();
9+
config.include(&apex_dir); // why only one language here?
10+
config
11+
.flag_if_supported("-std=c11")
12+
.flag_if_supported("-Wno-unused-parameter");
1213

13-
// If your language uses an external scanner written in C,
14-
// then include this block of code:
14+
for path in &[
15+
apex_dir.join("parser.c"),
16+
soql_dir.join("parser.c"),
17+
sosl_dir.join("parser.c"),
18+
sflog_dir.join("parser.c"),
19+
] {
20+
config.file(path);
21+
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
22+
}
1523

16-
/*
17-
let scanner_path = src_dir.join("scanner.c");
18-
c_config.file(&scanner_path);
19-
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
20-
*/
21-
22-
c_config.compile("parser");
23-
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
24-
25-
// If your language uses an external scanner written in C++,
26-
// then include this block of code:
27-
28-
/*
29-
let mut cpp_config = cc::Build::new();
30-
cpp_config.cpp(true);
31-
cpp_config.include(&src_dir);
32-
cpp_config
33-
.flag_if_supported("-Wno-unused-parameter")
34-
.flag_if_supported("-Wno-unused-but-set-variable");
35-
let scanner_path = src_dir.join("scanner.cc");
36-
cpp_config.file(&scanner_path);
37-
cpp_config.compile("scanner");
38-
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
39-
*/
40-
}
24+
config.compile("tree-sitter-sfapex");
25+
}

0 commit comments

Comments
 (0)