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
use proc_macro2::Span;
use quote::quote;
use syn::parse::{Parse, ParseBuffer, Result};
use syn::{parse_macro_input, Ident, LitInt, LitStr, Token};

use crate::util::*;

pub fn functor_macro(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let functor = parse_macro_input!(stream as Functor);
    let functor_name_str = LitStr::new(&functor.name, functor.name_span);
    let functor_arity_int = LitInt::new(&functor.arity.to_string(), functor.arity_span);
    let crt = crate_token();
    let result = quote! {
        { static FUNCTOR: #crt::prelude::LazyFunctor = #crt::prelude::LazyFunctor::new(#functor_name_str, #functor_arity_int);
          FUNCTOR.as_functor()
        }
    };
    result.into()
}

pub(crate) struct Functor {
    pub name: String,
    pub name_span: Span,
    pub arity: u16,
    pub arity_span: Span,
}

impl Parse for Functor {
    fn parse(input: &ParseBuffer) -> Result<Self> {
        let name: String;
        let name_span: Span;
        if input.peek(LitStr) {
            // There is two possibilities.
            //
            // 1. This is a string of format "name/arity", which we'll
            // need to parse.
            // 2. This is a a string that is followed by a comma or
            // slash, and an arity.
            //
            // We will deal with 1 here and return early. Second part is equal for ident so we'll handle it in a common code path.
            let x: LitStr = input.parse()?;

            if input.is_empty() {
                // we need to parse
                let s = x.value();
                if let Some(pos) = s.rfind('/') {
                    let name = &s[..pos];
                    let arity_s = &s[pos + 1..];

                    if let Ok(arity) = arity_s.parse::<u16>() {
                        return Ok(Functor {
                            name: name.to_string(),
                            name_span: x.span(),
                            arity,
                            arity_span: x.span(),
                        });
                    } else {
                        return Err(syn::parse::Error::new(
                            x.span(),
                            format!("\"{}\" is not a valid arity", arity_s),
                        ));
                    }
                } else {
                    return Err(syn::parse::Error::new(
                        x.span(),
                        "expected functor format <name>/<arity> but no '/' found",
                    ));
                }
            } else {
                name = x.value();
                name_span = x.span();
            }
        } else {
            let x: Ident = input.parse()?;

            name = x.to_string();
            name_span = x.span();
        }

        if input.peek(Token![,]) {
            input.parse::<Token![,]>()?;
        } else if input.peek(Token![/]) {
            input.parse::<Token![/]>()?;
        } else {
            return Err(syn::parse::Error::new(
                input.span(),
                "invalid functor separator",
            ));
        }

        let arity_token = input.parse::<LitInt>()?;
        let arity: u16 = arity_token.base10_parse()?;

        Ok(Self {
            name,
            name_span,
            arity,
            arity_span: arity_token.span(),
        })
    }
}