Skip to content

Commit

Permalink
🔖 v0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
linkdd committed May 17, 2023
1 parent 43a64b2 commit 194c631
Show file tree
Hide file tree
Showing 12 changed files with 639 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/Cargo.lock
21 changes: 21 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "astmaker"
version = "0.1.0"

description = "Build Abstract Syntax Trees and tree-walking models quickly in Rust."
keywords = ["ast", "abstract", "syntax", "tree", "walk"]

homepage = "https://github.com/linkdd/astmaker"
repository = "https://github.com/linkdd/astmaker"

license = "MIT"
readme = "README.md"

edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = { version = "2.0", features = ["full", "fold"] }
quote = "1.0"
18 changes: 18 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Copyright 2023 David Delassus <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
115 changes: 115 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# astmaker

Build Abstract Syntax Trees and tree-walking models quickly in Rust.

## Example

This example creates an AST for simple math expressions, and an interpreter to
evaluate the result:

```rust
use astmaker::{ast, model};

#[derive(Debug, Clone, PartialEq)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
}

#[derive(Debug, Clone, PartialEq)]
pub enum UnOp {
Add,
Sub,
}

ast!{
location = ();

pub node Expression =
| BinOp -> Node<BinaryOperation>
| UnOp -> Node<UnaryOperation>
| Num -> Node<Number>
;

pub node BinaryOperation = {
lhs: Node<Expression>,
op: BinOp,
rhs: Node<Expression>,
}

pub node UnaryOperation = {
op: UnOp,
expr: Node<Expression>,
}

pub node Number = {
value: f64,
}
}

pub struct Interpreter;

model!{
for Interpreter -> f64 {
where Expression => {
match node.data.as_mut() {
Expression::BinOp(child_node) => context.visit(child_node),
Expression::UnOp(child_node) => context.visit(child_node),
Expression::Num(child_node) => context.visit(child_node),
}
},
where BinaryOperation => {
let lhs = context.visit(&mut node.data.lhs);
let rhs = context.visit(&mut node.data.rhs);

match node.data.op {
BinOp::Add => lhs + rhs,
BinOp::Sub => lhs - rhs,
BinOp::Mul => lhs * rhs,
BinOp::Div => lhs / rhs,
}
},
where UnaryOperation => {
let val = context.visit(&mut node.data.expr);

match node.data.op {
UnOp::Add => val,
UnOp::Sub => -val,
}
},
where Number => node.data.value,
}
}

#[test]
fn eval() {
let mut tree = Node::new((), Expression::BinOp(
Node::new((), BinaryOperation {
lhs: Node::new((), Expression::Num(
Node::new((), Number { value: 1.0 })
)),
op: BinOp::Add,
rhs: Node::new((), Expression::Num(
Node::new((), Number { value: 2.0 })
))
})
));

let mut interpreter = Interpreter;

assert_eq!(interpreter.visit(&mut tree), 3.0);
}
```

## Roadmap

- [ ] :memo: Documentation
- [ ] :sparkles: Generics support
- [ ] :sparkles: Lifetimes support
- [ ] :construction_worker: Github workflows to build, test & publish crate

## License

This project is released under the terms of the [MIT License](./LICENSE.txt).
96 changes: 96 additions & 0 deletions src/codegen/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;

use crate::parser::ast::*;

pub fn generate(input: TokenStream) -> TokenStream {
let AbstractSyntaxTree {
location,
nodes,
} = parse_macro_input!(input as AbstractSyntaxTree);

let location_type = location.datatype;

let typedefs = quote!{
pub trait NodeAttributes {
type Attributes;
}

#[derive(Debug, Clone, PartialEq)]
pub struct Node<T: NodeAttributes> {
pub location: #location_type,
pub attrs: Option<T::Attributes>,
pub data: Box<T>,
}

impl<T: NodeAttributes> Node<T> {
pub fn new(location: #location_type, data: T) -> Self {
Self { location, attrs: None, data: Box::new(data) }
}
}
};

let mut node_defs = vec![];

for node in nodes {
let Node {
visibility,
name,
attrs,
data,
} = node;

let attrs_type = match attrs {
None => quote!{()},
Some(NodeAttributes { datatype }) => quote!{ #datatype }
};

let node_def = match data {
NodeData::Struct(data) => {
let fields = data.members
.iter()
.map(|NodeDataStructField { name, datatype }| quote!{
#name : #datatype
});

quote!{
#[derive(Debug, Clone, PartialEq)]
#visibility struct #name {
#(#fields),*
}

impl NodeAttributes for #name {
type Attributes = #attrs_type;
}
}
},
NodeData::Enum(data) => {
let variants = data.variants
.iter()
.map(|NodeDataEnumVariant { name, datatype }| quote!{
#name ( #datatype )
});

quote!{
#[derive(Debug, Clone, PartialEq)]
#visibility enum #name {
#(#variants),*
}

impl NodeAttributes for #name {
type Attributes = #attrs_type;
}
}
},
};

node_defs.push(node_def);
}

TokenStream::from(quote!{
#typedefs

#(#node_defs)*
})
}
2 changes: 2 additions & 0 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod ast;
pub mod model;
40 changes: 40 additions & 0 deletions src/codegen/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;

use crate::parser::model::*;

pub fn generate(input: TokenStream) -> TokenStream {
let Model {
context,
output,
clauses,
} = parse_macro_input!(input as Model);

let typedefs = quote!{
pub trait Visitable<T: NodeAttributes> {
fn visit(context: &mut #context, node: &mut Node<T>) -> #output;
}
};

let clause_defs = clauses
.iter()
.map(|Clause { pattern, body }| quote!{
impl Visitable<#pattern> for #pattern {
fn visit(context: &mut #context, node: &mut Node<#pattern>) -> #output {
#body
}
}
});

TokenStream::from(quote!{
#typedefs
#(#clause_defs)*

impl #context {
pub fn visit<T: NodeAttributes + Visitable<T>>(&mut self, node: &mut Node<T>) -> #output {
T::visit(self, node)
}
}
})
}
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use proc_macro::TokenStream;

mod parser;
mod codegen;

#[proc_macro]
pub fn ast(input: TokenStream) -> TokenStream {
codegen::ast::generate(input)
}

#[proc_macro]
pub fn model(input: TokenStream) -> TokenStream {
codegen::model::generate(input)
}
Loading

0 comments on commit 194c631

Please sign in to comment.