1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
extern crate proc_macro; 
use std::io::Read;
use syn::parse_macro_input;
use proc_macro::TokenStream;

mod stack;
mod numeric_type;
mod evaluator;
mod recurrence; 
use recurrence::RecurrencyConstraint;
mod synthesis;
mod macro_core;
mod netcrate_invocation; 
use netcrate_invocation::NetcrateInvocation;
#[macro_use] mod invocation_parser;

/// Adds the required fields and functions for executing a network loaded from a CGE file.
/// - If your network has recurrent     architecture, it only works on unit structs (no fields).
/// - If your network has non-recurrent architecture, it works on any struct or enum.
/// - To control target numeric type (`f32`/`f64`), use the `numeric_type` attribute: `#[network("net.cge", numeric_type = f32)`.
/// ```rust
/// use const_cge::network;
/// 
/// /// Controls the robot's limbs, predicting motor actuations
/// /// that will move it in the desired direction.
/// #[network("./walker.cge")]
/// struct Walker;
/// 
/// fn main() {
///   let mut walk = Walker::default();
///   walk.evaluate(&inputs, &mut outputs);
/// }
/// ```
#[proc_macro_attribute]
pub fn network(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream {
  macro_core::core(parse_invocation!(attr, item, RecurrencyConstraint::DontCare))
}

/// Identical to `#[network]`, but prevents compilation if network is non-recurrent.
#[proc_macro_attribute]
pub fn recurrent(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream {
  macro_core::core(parse_invocation!(attr, item, RecurrencyConstraint::Required))
}

/// Identical to `#[network]`, but prevents compilation if network is recurrent.
#[proc_macro_attribute]
pub fn nonrecurrent(attr: TokenStream, item: TokenStream) -> proc_macro::TokenStream {
  macro_core::core(parse_invocation!(attr, item, RecurrencyConstraint::Forbidden))
}

/// API for `netcrate` authors.
/// 
/// ## Usage
/// ```rust
/// #![cfg_attr(not(feature = "std"), no_std)]
/// const_cge::netcrate!(ocr = "optical_char_recog.cge");
/// ```
/// End users can use your network like:
/// ```rust
/// use const_cge::ocr;
/// #[network(ocr)]
/// struct SomeEndUserStruct;
/// ```
/// 
/// - and choose their own numeric_type, 
/// - and their own floating point backend (`std`, `libm`, `micromath`, etc)
/// 
/// Your job may be finished, go rest.
/// 
/// ## Specifying Path
/// The path you provide is relative to the _crate root_ (e.g. a crate file inside `src` would look like
/// `src/my_net.cge`).
/// 
/// ## Multiple Networks
/// Unfortunately, you cannot use the module system to organize networks for users,
/// they will all be forcibly hoisted to the top level.
/// 
/// ```rust
/// const_cge::netcrate!(ocr          = "ocr.cge");
/// const_cge::netcrate!(denoise      = "denoise.cge");
/// const_cge::netcrate!(cart_pole    = "cart.cge");   
/// const_cge::netcrate!(octopus_legs = "octopus.cge");
/// ```
/// 
/// Now, end users can use your network like:
/// ```rust
/// #[network(network_zoo::ocr, numeric_type = f32)]
/// struct HandwritingOCR;
/// ```
#[proc_macro]
pub fn netcrate(input: TokenStream) -> proc_macro::TokenStream {
  let NetcrateInvocation { name, path } = parse_macro_input!(input as NetcrateInvocation);

  // convert to absolute path based on the currently-building crate
  let manifest_path = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to discover crate manifest directory!");
  let cge_path = std::path::Path::new(&manifest_path).join(path);

  // extract the CGE data
  let cge_data = {
    let mut file = std::fs::File::open(cge_path).expect("Failed to open CGE file");
    let mut contents = String::new();
    file.read_to_string(&mut contents).expect("Failed to read CGE file");
    contents
  };

  quote::quote! {
    #[allow(unused_macros)] // silence warning author will see about some invisible macro
    #[macro_export]         // force this macro to be hoisted and made public
    macro_rules! #name {    // name the macro according to user
      (
        $invocation: ident,
        $item: item,
        $($rest:stmt),*
      ) => {
        #[const_cge::$invocation(#cge_data, $($rest),*)]
        $item
      }
    }
  }.into()
}