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
use std::collections::HashMap;

use thiserror::Error;

use galvan_ast::{Ast, FnDecl, Ident, MainDecl, RootItem, TypeDecl, TypeIdent};

pub struct LookupContext<'a> {
    /// Types are resolved by their name
    pub types: HashMap<TypeId, &'a TypeDecl>,
    /// Functions are resolved by their name and - if present - named arguments and their receiver type
    ///
    /// `fn foo(a: i32, b: i32) -> i32` is identified as `foo`
    /// `fn foo(bar a: i32, b: i32) -> i32` is identified as `foo:bar`
    /// `fn foo(self: i32, b: i32) -> i32` is identified as `i32::foo`
    pub functions: HashMap<FunctionId, &'a FnDecl>,
    // TODO: Nested contexts for resolving names from imported modules
    // pub imports: HashMap<String, LookupContext<'a>>,
    pub main: Option<&'a MainDecl>,
}

// TODO: derive thiserror and add proper error handling #[derive(Error)]
// TODO: Include spans in errors
#[derive(Debug, Error)]
pub enum LookupError {
    #[error("Type not found")]
    TypeNotFound,
    #[error("Function not found")]
    FunctionNotFound,
    #[error("Duplicate main function")]
    DuplicateMain,
    #[error("Duplicate type")]
    DuplicateType,
    #[error("Duplicate function")]
    DuplicateFunction,
}

impl<'a> LookupContext<'a> {
    pub fn new(asts: &'a [Ast]) -> Result<Self, LookupError> {
        let mut types = HashMap::new();
        let mut functions = HashMap::new();
        let mut main = None;

        for ast in asts {
            for top in &ast.toplevel {
                match top {
                    RootItem::Type(type_decl) => {
                        types.insert(type_decl.ident().into(), type_decl);
                    }
                    RootItem::Fn(fn_decl) => {
                        // TODO: Add named arguments and receiver type
                        let func_id = FunctionId::new(None, &fn_decl.signature.identifier, &[]);
                        functions.insert(func_id, fn_decl);
                    }
                    RootItem::Test(_) => {}
                    RootItem::Main(m) => {
                        if main.is_some() {
                            return Err(LookupError::DuplicateMain);
                        }
                        main = Some(m);
                    }
                }
            }
        }

        Ok(LookupContext {
            types,
            functions,
            main,
        })
    }
}

impl LookupContext<'_> {
    pub fn resolve_type(&self, name: &TypeIdent) -> Option<&TypeDecl> {
        self.types.get(&name.into()).copied()
    }

    pub fn resolve_function(
        &self,
        receiver: Option<&TypeIdent>,
        name: &Ident,
        labels: &[&str],
    ) -> Option<&FnDecl> {
        let func_id = FunctionId::new(receiver, name, labels);
        self.functions.get(&func_id).copied()
    }
}

#[derive(Debug, Hash, PartialEq, Eq)]
pub struct TypeId(Box<str>);

impl<S> From<S> for TypeId
where
    S: AsRef<str>,
{
    fn from(ident: S) -> Self {
        Self(ident.as_ref().into())
    }
}

#[derive(Debug, Hash, PartialEq, Eq)]
struct FunctionId(Box<str>);

impl FunctionId {
    fn new(receiver: Option<&TypeIdent>, fn_ident: &Ident, labels: &[&str]) -> Self {
        let mut id = String::new();
        if let Some(receiver) = receiver {
            id.push_str(receiver.as_str());
            id.push_str("::");
        }
        id.push_str(fn_ident.as_str());
        if !labels.is_empty() {
            id.push(':');
            id.push_str(&labels.join(":"));
        }

        Self(id.into())
    }
}