Skip to content

Commit 3f4b2fd

Browse files
authored
Add ascent_source! macro and include_source! syntax (#63)
as an `#include` style composability feature
1 parent 4547c1b commit 3f4b2fd

File tree

14 files changed

+369
-95
lines changed

14 files changed

+369
-95
lines changed

README.MD

+23
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,29 @@ It may be useful to define macros that expand to either body items or head items
185185

186186
You can find more about macros in Ascent macros [here](MACROS.MD).
187187

188+
### `ascent_source!` and `include_source!`
189+
You can write Ascent code inside `ascent_source!` macro invocations and later include them in actual Ascent programs.
190+
This allows reusing your Ascent code across different Ascent programs, and composing Ascent programs from different code "modules".
191+
```Rust
192+
mod ascent_code {
193+
ascent::ascent_source! { my_awesome_analysis:
194+
// Ascent code for my awesome analysis
195+
}
196+
}
197+
198+
// elsewhere:
199+
ascent! {
200+
struct MyAwesomeAnalysis;
201+
include_source!(ascent_code::my_awesome_analysis);
202+
}
203+
204+
ascent_par! {
205+
struct MyAwesomeAnalysisParallelized;
206+
include_source!(ascent_code::my_awesome_analysis);
207+
}
208+
209+
```
210+
188211
### Misc
189212
- **`#![measure_rule_times]`** causes execution times of individual rules to be measured. Example:
190213
```Rust

ascent/examples/ascent_source.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use ascent::{ascent_run, ascent_source};
2+
3+
mod base {
4+
ascent::ascent_source! {
5+
/// Defines `edge` and `path`, the transitive closure of `edge`.
6+
/// The type of a node is `Node`
7+
tc:
8+
9+
relation edge(Node, Node);
10+
relation path(Node, Node);
11+
path(x, y) <-- edge(x, y);
12+
path(x, z) <-- edge(x, y), path(y, z);
13+
}
14+
}
15+
16+
ascent_source! { symm_edges:
17+
edge(x, y) <-- edge(y, x);
18+
}
19+
20+
fn main() {
21+
type Node = usize;
22+
let res = ascent_run! {
23+
include_source!(base::tc);
24+
include_source!(symm_edges);
25+
26+
edge(1, 2), edge(2, 3), edge(3, 4);
27+
};
28+
29+
assert!(res.path.contains(&(4, 1)));
30+
}

ascent/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ mod tuple_of_borrowed;
2727
mod rel_index_boilerplate;
2828

2929
pub use ascent_base::*;
30-
pub use ascent_macro::{ascent, ascent_run};
30+
pub use ascent_macro::{ascent, ascent_run, ascent_source};
3131
#[cfg(feature = "par")]
3232
pub use ascent_macro::{ascent_par, ascent_run_par};
3333
#[cfg(feature = "par")]

ascent_macro/src/ascent_hir.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ pub(crate) struct AscentIr {
8383
pub relations_ir_relations: HashMap<RelationIdentity, HashSet<IrRelation>>,
8484
pub relations_full_indices: HashMap<RelationIdentity, IrRelation>,
8585
pub lattices_full_indices: HashMap<RelationIdentity, IrRelation>,
86-
// pub relations_no_indices: HashMap<RelationIdentity, IrRelation>,
8786
pub relations_metadata: HashMap<RelationIdentity, RelationMetadata>,
8887
pub rules: Vec<IrRule>,
8988
pub signatures: Signatures,
@@ -231,7 +230,6 @@ pub(crate) fn compile_ascent_program_to_hir(prog: &AscentProgram, is_parallel: b
231230
let mut relations_full_indices = HashMap::with_capacity(num_relations);
232231
let mut relations_initializations = HashMap::new();
233232
let mut relations_metadata = HashMap::with_capacity(num_relations);
234-
// let mut relations_no_indices = HashMap::new();
235233
let mut lattices_full_indices = HashMap::new();
236234

237235
let mut rel_identities = prog.relations.iter().map(|rel| (rel, RelationIdentity::from(rel))).collect_vec();
@@ -251,12 +249,11 @@ pub(crate) fn compile_ascent_program_to_hir(prog: &AscentProgram, is_parallel: b
251249
let rel_full_index = IrRelation::new(rel_identity.clone(), full_indices);
252250

253251
relations_ir_relations.entry(rel_identity.clone()).or_default().insert(rel_full_index.clone());
254-
// relations_ir_relations.entry(rel_identity.clone()).or_default().insert(rel_no_index.clone());
255252
relations_full_indices.insert(rel_identity.clone(), rel_full_index);
256253
if let Some(init_expr) = &rel.initialization {
257254
relations_initializations.insert(rel_identity.clone(), Rc::new(init_expr.clone()));
258255
}
259-
256+
260257
let ds_attr = match (ds_attribute, rel.is_lattice) {
261258
(None, true) => None,
262259
(None, false) => Some(config.default_ds.clone()),
@@ -278,7 +275,6 @@ pub(crate) fn compile_ascent_program_to_hir(prog: &AscentProgram, is_parallel: b
278275
),
279276
ds_attr,
280277
});
281-
// relations_no_indices.insert(rel_identity, rel_no_index);
282278
}
283279
for (ir_rule, extra_relations) in ir_rules.iter() {
284280
for bitem in ir_rule.body_items.iter() {
@@ -303,7 +299,6 @@ pub(crate) fn compile_ascent_program_to_hir(prog: &AscentProgram, is_parallel: b
303299
relations_full_indices,
304300
lattices_full_indices,
305301
relations_metadata,
306-
// relations_no_indices,
307302
signatures,
308303
config,
309304
is_parallel,
@@ -522,7 +517,7 @@ pub(crate) fn prog_get_relation<'a>(
522517
format!(
523518
"wrong arity for relation `{name}` (expected {expected}, found {found})",
524519
expected = rel.field_types.len(),
525-
found = arity,
520+
found = arity,
526521
),
527522
))
528523
} else {

ascent_macro/src/ascent_mir.rs

-1
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ pub(crate) fn compile_hir_to_mir(hir: &AscentIr) -> syn::Result<AscentMir> {
312312
relations_ir_relations: hir.relations_ir_relations.clone(),
313313
relations_full_indices: hir.relations_full_indices.clone(),
314314
lattices_full_indices: hir.lattices_full_indices.clone(),
315-
// relations_no_indices: hir.relations_no_indices.clone(),
316315
relations_metadata: hir.relations_metadata.clone(),
317316
signatures: hir.signatures.clone(),
318317
config: hir.config.clone(),

ascent_macro/src/ascent_syntax.rs

+96-37
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ use std::sync::Mutex;
55

66
use ascent_base::util::update;
77
use derive_syn_parse::Parse;
8-
use itertools::Itertools;
8+
use itertools::{Either, Itertools};
99
use proc_macro2::{Span, TokenStream};
1010
use quote::ToTokens;
1111
use syn::parse::{Parse, ParseStream, Parser};
1212
use syn::punctuated::Punctuated;
1313
use syn::spanned::Spanned;
1414
use syn::{
15-
Attribute, Error, Expr, ExprMacro, Generics, Ident, ImplGenerics, Pat, Result, Token, Type, TypeGenerics,
15+
Attribute, Error, Expr, ExprMacro, Generics, Ident, ImplGenerics, Pat, Path, Result, Token, Type, TypeGenerics,
1616
Visibility, WhereClause, braced, parenthesized, parse2,
1717
};
1818

19+
use crate::AscentMacroKind;
1920
use crate::syn_utils::{
2021
expr_get_vars, expr_visit_free_vars_mut, expr_visit_idents_in_macros_mut, pattern_get_vars, pattern_visit_vars_mut,
2122
token_stream_idents, token_stream_replace_ident,
@@ -45,13 +46,13 @@ mod kw {
4546
pub struct LongLeftArrow(Token![<], Token![-], Token![-]);
4647
#[allow(unused)]
4748
impl LongLeftArrow {
48-
pub fn span(&self) -> Span {
49-
join_spans([self.0.span, self.1.span, self.2.span])
50-
}
49+
pub fn span(&self) -> Span { join_spans([self.0.span, self.1.span, self.2.span]) }
5150
}
5251
syn::custom_keyword!(agg);
5352
syn::custom_keyword!(ident);
5453
syn::custom_keyword!(expr);
54+
55+
syn::custom_keyword!(include_source);
5556
}
5657

5758
#[derive(Clone, Debug)]
@@ -117,7 +118,7 @@ fn parse_generics_with_where_clause(input: ParseStream) -> Result<Generics> {
117118
Ok(res)
118119
}
119120

120-
// #[derive(Clone)]
121+
#[derive(PartialEq, Eq, Clone)]
121122
pub struct RelationNode {
122123
pub attrs: Vec<Attribute>,
123124
pub name: Ident,
@@ -126,6 +127,7 @@ pub struct RelationNode {
126127
pub _semi_colon: Token![;],
127128
pub is_lattice: bool,
128129
}
130+
129131
impl Parse for RelationNode {
130132
fn parse(input: ParseStream) -> Result<Self> {
131133
let is_lattice = input.peek(kw::lattice);
@@ -549,6 +551,18 @@ pub struct MacroDefNode {
549551
body: TokenStream,
550552
}
551553

554+
#[derive(Parse)]
555+
pub struct IncludeSourceNode {
556+
pub include_source_kw: kw::include_source,
557+
_bang: Token![!],
558+
#[paren]
559+
_arg_paren: syn::token::Paren,
560+
#[inside(_arg_paren)]
561+
#[call(syn::Path::parse_mod_style)]
562+
path: syn::Path,
563+
_semi: Token![;],
564+
}
565+
552566
// #[derive(Clone)]
553567
pub(crate) struct AscentProgram {
554568
pub rules: Vec<RuleNode>,
@@ -558,40 +572,85 @@ pub(crate) struct AscentProgram {
558572
pub macros: Vec<MacroDefNode>,
559573
}
560574

561-
impl Parse for AscentProgram {
562-
fn parse(input: ParseStream) -> Result<Self> {
563-
let attributes = Attribute::parse_inner(input)?;
564-
let mut struct_attrs = Attribute::parse_outer(input)?;
565-
let signatures = if input.peek(Token![pub]) || input.peek(Token![struct]) {
566-
let mut signatures = Signatures::parse(input)?;
567-
signatures.declaration.attrs = std::mem::take(&mut struct_attrs);
568-
Some(signatures)
575+
/// The output that should be emitted when an `include_source!()` is encountered
576+
pub(crate) struct IncludeSourceMacroCall {
577+
/// the encountered `include_source!()`
578+
pub include_node: IncludeSourceNode,
579+
pub before_tokens: TokenStream,
580+
pub after_tokens: TokenStream,
581+
pub ascent_macro_name: Path,
582+
}
583+
584+
impl IncludeSourceMacroCall {
585+
/// The output that should be emitted
586+
pub fn macro_call_output(&self) -> TokenStream {
587+
let Self { include_node, before_tokens, after_tokens, ascent_macro_name } = self;
588+
let include_macro_callback = &include_node.path;
589+
quote_spanned! {include_macro_callback.span()=>
590+
#include_macro_callback! { {#ascent_macro_name}, {#before_tokens}, {#after_tokens} }
591+
}
592+
}
593+
}
594+
595+
pub(crate) fn parse_ascent_program(
596+
input: ParseStream, ascent_macro_name: Path,
597+
) -> Result<Either<AscentProgram, IncludeSourceMacroCall>> {
598+
let input_clone = input.cursor();
599+
let attributes = Attribute::parse_inner(input)?;
600+
let mut struct_attrs = Attribute::parse_outer(input)?;
601+
let signatures = if input.peek(Token![pub]) || input.peek(Token![struct]) {
602+
let mut signatures = Signatures::parse(input)?;
603+
signatures.declaration.attrs = std::mem::take(&mut struct_attrs);
604+
Some(signatures)
605+
} else {
606+
None
607+
};
608+
let mut rules = vec![];
609+
let mut relations = vec![];
610+
let mut macros = vec![];
611+
while !input.is_empty() {
612+
let attrs =
613+
if !struct_attrs.is_empty() { std::mem::take(&mut struct_attrs) } else { Attribute::parse_outer(input)? };
614+
if input.peek(kw::relation) || input.peek(kw::lattice) {
615+
let mut relation_node = RelationNode::parse(input)?;
616+
relation_node.attrs = attrs;
617+
relations.push(relation_node);
618+
} else if input.peek(Token![macro]) {
619+
if !attrs.is_empty() {
620+
return Err(Error::new(attrs[0].span(), "unexpected attribute(s)"));
621+
}
622+
macros.push(MacroDefNode::parse(input)?);
623+
} else if input.peek(kw::include_source) {
624+
if !attrs.is_empty() {
625+
return Err(Error::new(attrs[0].span(), "unexpected attribute(s)"));
626+
}
627+
let before_tokens = input_clone
628+
.token_stream()
629+
.into_iter()
630+
.take_while(|tt| !spans_eq(&tt.span(), &input.span()))
631+
.collect::<TokenStream>();
632+
let include_node = IncludeSourceNode::parse(input)?;
633+
let after_tokens: TokenStream = input.parse()?;
634+
let include_source_macro_call =
635+
IncludeSourceMacroCall { include_node, before_tokens, after_tokens, ascent_macro_name };
636+
return Ok(Either::Right(include_source_macro_call));
569637
} else {
570-
None
571-
};
572-
let mut rules = vec![];
573-
let mut relations = vec![];
574-
let mut macros = vec![];
575-
while !input.is_empty() {
576-
let attrs =
577-
if !struct_attrs.is_empty() { std::mem::take(&mut struct_attrs) } else { Attribute::parse_outer(input)? };
578-
if input.peek(kw::relation) || input.peek(kw::lattice) {
579-
let mut relation_node = RelationNode::parse(input)?;
580-
relation_node.attrs = attrs;
581-
relations.push(relation_node);
582-
} else if input.peek(Token![macro]) {
583-
if !attrs.is_empty() {
584-
return Err(Error::new(attrs[0].span(), "unexpected attribute(s)"));
585-
}
586-
macros.push(MacroDefNode::parse(input)?);
587-
} else {
588-
if !attrs.is_empty() {
589-
return Err(Error::new(attrs[0].span(), "unexpected attribute(s)"));
590-
}
591-
rules.push(RuleNode::parse(input)?);
638+
if !attrs.is_empty() {
639+
return Err(Error::new(attrs[0].span(), "unexpected attribute(s)"));
592640
}
641+
rules.push(RuleNode::parse(input)?);
642+
}
643+
}
644+
Ok(Either::Left(AscentProgram { rules, relations, signatures, attributes, macros }))
645+
}
646+
647+
impl Parse for AscentProgram {
648+
fn parse(input: ParseStream) -> Result<Self> {
649+
match parse_ascent_program(input, AscentMacroKind::default().macro_path(Span::call_site()))? {
650+
Either::Left(parsed) => Ok(parsed),
651+
Either::Right(include) =>
652+
Err(Error::new(include.include_node.include_source_kw.span(), "Encountered `include_source!`")),
593653
}
594-
Ok(AscentProgram { rules, relations, signatures, attributes, macros })
595654
}
596655
}
597656

0 commit comments

Comments
 (0)