Skip to content

Commit 194c631

Browse files
committed
🔖 v0.1.0
1 parent 43a64b2 commit 194c631

File tree

12 files changed

+639
-0
lines changed

12 files changed

+639
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/target
2+
/Cargo.lock

Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "astmaker"
3+
version = "0.1.0"
4+
5+
description = "Build Abstract Syntax Trees and tree-walking models quickly in Rust."
6+
keywords = ["ast", "abstract", "syntax", "tree", "walk"]
7+
8+
homepage = "https://github.com/linkdd/astmaker"
9+
repository = "https://github.com/linkdd/astmaker"
10+
11+
license = "MIT"
12+
readme = "README.md"
13+
14+
edition = "2021"
15+
16+
[lib]
17+
proc-macro = true
18+
19+
[dependencies]
20+
syn = { version = "2.0", features = ["full", "fold"] }
21+
quote = "1.0"

LICENSE.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Copyright 2023 David Delassus <[email protected]>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
this software and associated documentation files (the "Software"), to deal in
5+
the Software without restriction, including without limitation the rights to
6+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7+
the Software, and to permit persons to whom the Software is furnished to do so,
8+
subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# astmaker
2+
3+
Build Abstract Syntax Trees and tree-walking models quickly in Rust.
4+
5+
## Example
6+
7+
This example creates an AST for simple math expressions, and an interpreter to
8+
evaluate the result:
9+
10+
```rust
11+
use astmaker::{ast, model};
12+
13+
#[derive(Debug, Clone, PartialEq)]
14+
pub enum BinOp {
15+
Add,
16+
Sub,
17+
Mul,
18+
Div,
19+
}
20+
21+
#[derive(Debug, Clone, PartialEq)]
22+
pub enum UnOp {
23+
Add,
24+
Sub,
25+
}
26+
27+
ast!{
28+
location = ();
29+
30+
pub node Expression =
31+
| BinOp -> Node<BinaryOperation>
32+
| UnOp -> Node<UnaryOperation>
33+
| Num -> Node<Number>
34+
;
35+
36+
pub node BinaryOperation = {
37+
lhs: Node<Expression>,
38+
op: BinOp,
39+
rhs: Node<Expression>,
40+
}
41+
42+
pub node UnaryOperation = {
43+
op: UnOp,
44+
expr: Node<Expression>,
45+
}
46+
47+
pub node Number = {
48+
value: f64,
49+
}
50+
}
51+
52+
pub struct Interpreter;
53+
54+
model!{
55+
for Interpreter -> f64 {
56+
where Expression => {
57+
match node.data.as_mut() {
58+
Expression::BinOp(child_node) => context.visit(child_node),
59+
Expression::UnOp(child_node) => context.visit(child_node),
60+
Expression::Num(child_node) => context.visit(child_node),
61+
}
62+
},
63+
where BinaryOperation => {
64+
let lhs = context.visit(&mut node.data.lhs);
65+
let rhs = context.visit(&mut node.data.rhs);
66+
67+
match node.data.op {
68+
BinOp::Add => lhs + rhs,
69+
BinOp::Sub => lhs - rhs,
70+
BinOp::Mul => lhs * rhs,
71+
BinOp::Div => lhs / rhs,
72+
}
73+
},
74+
where UnaryOperation => {
75+
let val = context.visit(&mut node.data.expr);
76+
77+
match node.data.op {
78+
UnOp::Add => val,
79+
UnOp::Sub => -val,
80+
}
81+
},
82+
where Number => node.data.value,
83+
}
84+
}
85+
86+
#[test]
87+
fn eval() {
88+
let mut tree = Node::new((), Expression::BinOp(
89+
Node::new((), BinaryOperation {
90+
lhs: Node::new((), Expression::Num(
91+
Node::new((), Number { value: 1.0 })
92+
)),
93+
op: BinOp::Add,
94+
rhs: Node::new((), Expression::Num(
95+
Node::new((), Number { value: 2.0 })
96+
))
97+
})
98+
));
99+
100+
let mut interpreter = Interpreter;
101+
102+
assert_eq!(interpreter.visit(&mut tree), 3.0);
103+
}
104+
```
105+
106+
## Roadmap
107+
108+
- [ ] :memo: Documentation
109+
- [ ] :sparkles: Generics support
110+
- [ ] :sparkles: Lifetimes support
111+
- [ ] :construction_worker: Github workflows to build, test & publish crate
112+
113+
## License
114+
115+
This project is released under the terms of the [MIT License](./LICENSE.txt).

src/codegen/ast.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
use syn::parse_macro_input;
4+
5+
use crate::parser::ast::*;
6+
7+
pub fn generate(input: TokenStream) -> TokenStream {
8+
let AbstractSyntaxTree {
9+
location,
10+
nodes,
11+
} = parse_macro_input!(input as AbstractSyntaxTree);
12+
13+
let location_type = location.datatype;
14+
15+
let typedefs = quote!{
16+
pub trait NodeAttributes {
17+
type Attributes;
18+
}
19+
20+
#[derive(Debug, Clone, PartialEq)]
21+
pub struct Node<T: NodeAttributes> {
22+
pub location: #location_type,
23+
pub attrs: Option<T::Attributes>,
24+
pub data: Box<T>,
25+
}
26+
27+
impl<T: NodeAttributes> Node<T> {
28+
pub fn new(location: #location_type, data: T) -> Self {
29+
Self { location, attrs: None, data: Box::new(data) }
30+
}
31+
}
32+
};
33+
34+
let mut node_defs = vec![];
35+
36+
for node in nodes {
37+
let Node {
38+
visibility,
39+
name,
40+
attrs,
41+
data,
42+
} = node;
43+
44+
let attrs_type = match attrs {
45+
None => quote!{()},
46+
Some(NodeAttributes { datatype }) => quote!{ #datatype }
47+
};
48+
49+
let node_def = match data {
50+
NodeData::Struct(data) => {
51+
let fields = data.members
52+
.iter()
53+
.map(|NodeDataStructField { name, datatype }| quote!{
54+
#name : #datatype
55+
});
56+
57+
quote!{
58+
#[derive(Debug, Clone, PartialEq)]
59+
#visibility struct #name {
60+
#(#fields),*
61+
}
62+
63+
impl NodeAttributes for #name {
64+
type Attributes = #attrs_type;
65+
}
66+
}
67+
},
68+
NodeData::Enum(data) => {
69+
let variants = data.variants
70+
.iter()
71+
.map(|NodeDataEnumVariant { name, datatype }| quote!{
72+
#name ( #datatype )
73+
});
74+
75+
quote!{
76+
#[derive(Debug, Clone, PartialEq)]
77+
#visibility enum #name {
78+
#(#variants),*
79+
}
80+
81+
impl NodeAttributes for #name {
82+
type Attributes = #attrs_type;
83+
}
84+
}
85+
},
86+
};
87+
88+
node_defs.push(node_def);
89+
}
90+
91+
TokenStream::from(quote!{
92+
#typedefs
93+
94+
#(#node_defs)*
95+
})
96+
}

src/codegen/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod ast;
2+
pub mod model;

src/codegen/model.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
use syn::parse_macro_input;
4+
5+
use crate::parser::model::*;
6+
7+
pub fn generate(input: TokenStream) -> TokenStream {
8+
let Model {
9+
context,
10+
output,
11+
clauses,
12+
} = parse_macro_input!(input as Model);
13+
14+
let typedefs = quote!{
15+
pub trait Visitable<T: NodeAttributes> {
16+
fn visit(context: &mut #context, node: &mut Node<T>) -> #output;
17+
}
18+
};
19+
20+
let clause_defs = clauses
21+
.iter()
22+
.map(|Clause { pattern, body }| quote!{
23+
impl Visitable<#pattern> for #pattern {
24+
fn visit(context: &mut #context, node: &mut Node<#pattern>) -> #output {
25+
#body
26+
}
27+
}
28+
});
29+
30+
TokenStream::from(quote!{
31+
#typedefs
32+
#(#clause_defs)*
33+
34+
impl #context {
35+
pub fn visit<T: NodeAttributes + Visitable<T>>(&mut self, node: &mut Node<T>) -> #output {
36+
T::visit(self, node)
37+
}
38+
}
39+
})
40+
}

src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use proc_macro::TokenStream;
2+
3+
mod parser;
4+
mod codegen;
5+
6+
#[proc_macro]
7+
pub fn ast(input: TokenStream) -> TokenStream {
8+
codegen::ast::generate(input)
9+
}
10+
11+
#[proc_macro]
12+
pub fn model(input: TokenStream) -> TokenStream {
13+
codegen::model::generate(input)
14+
}

0 commit comments

Comments
 (0)