Skip to main content

yog_script_analyze/
index.rs

1//! The class index (`docs/design/ys.md` §8): what the compiler knows of the
2//! game for one Minecraft version — each class by Mojang's name, the side
3//! it's on, what it extends and implements, its fields and methods with
4//! their descriptors and access. Built by `yog index` from the game's own
5//! jars; the resolver and the type checker look the game's names up in it.
6
7use std::collections::HashMap;
8
9use yog_data::Schema;
10
11/// Where a class exists: on both sides, or only in the client's or the
12/// dedicated server's jar. Code of one side can't reach the other's.
13#[derive(Schema, Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub enum Side {
15    #[default]
16    Shared,
17    Client,
18    Server,
19}
20
21/// A field or a method: its name, JVM descriptor (`(F)Z`, `I`) and access
22/// flags (`ACC_PUBLIC` 0x0001, `ACC_STATIC` 0x0008, …).
23#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
24pub struct Member {
25    pub name: String,
26    pub desc: String,
27    pub access: u16,
28}
29
30/// A class by its internal name (`net/minecraft/world/entity/LivingEntity`).
31#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
32pub struct Class {
33    pub name: String,
34    pub side: Side,
35    pub access: u16,
36    pub extends: Option<String>,
37    pub implements: Vec<String>,
38    pub fields: Vec<Member>,
39    pub methods: Vec<Member>,
40}
41
42/// The game's classes for Minecraft `mc`.
43#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
44pub struct ClassIndex {
45    pub mc: String,
46    pub classes: Vec<Class>,
47}
48
49pub mod access;
50
51/// A [`ClassIndex`] ready to look things up in: classes by name, and by
52/// simple name (`LivingEntity`) for what a source writes unqualified.
53pub struct Lookup<'i> {
54    by_name: HashMap<&'i str, &'i Class>,
55    by_simple: HashMap<&'i str, Vec<&'i Class>>,
56}
57
58impl ClassIndex {
59    pub fn lookup(&self) -> Lookup<'_> {
60        let mut by_name = HashMap::new();
61        let mut by_simple: HashMap<&str, Vec<&Class>> = HashMap::new();
62        for c in &self.classes {
63            by_name.insert(c.name.as_str(), c);
64            by_simple.entry(simple_name(&c.name)).or_default().push(c);
65        }
66        Lookup { by_name, by_simple }
67    }
68}
69
70/// `net/minecraft/world/entity/LivingEntity` → `LivingEntity`; a nested
71/// class (`Outer$Inner`) by its own name (`Inner`).
72pub fn simple_name(internal: &str) -> &str {
73    let tail = internal.rsplit('/').next().unwrap_or(internal);
74    tail.rsplit('$').next().unwrap_or(tail)
75}
76
77impl<'i> Lookup<'i> {
78    /// The class of internal name `name`.
79    pub fn class(&self, name: &str) -> Option<&'i Class> {
80        self.by_name.get(name).copied()
81    }
82
83    /// The classes a source's unqualified `name` may mean.
84    pub fn simple(&self, name: &str) -> &[&'i Class] {
85        self.by_simple
86            .get(name)
87            .map(Vec::as_slice)
88            .unwrap_or_default()
89    }
90
91    /// `class` and every class it extends, nearest first (the index's).
92    pub fn ancestry(&self, class: &'i Class) -> Vec<&'i Class> {
93        let mut out = vec![class];
94        while let Some(parent) = out
95            .last()
96            .and_then(|c| c.extends.as_deref())
97            .and_then(|p| self.class(p))
98        {
99            if out.iter().any(|c| c.name == parent.name) {
100                break;
101            }
102            out.push(parent);
103        }
104        out
105    }
106
107    /// The methods named `name` that `class` has, its own or inherited
108    /// (through what it extends and implements).
109    pub fn methods(&self, class: &'i Class, name: &str) -> Vec<&'i Member> {
110        let mut out = Vec::new();
111        let mut seen = Vec::new();
112        self.collect_methods(class, name, &mut out, &mut seen);
113        out
114    }
115
116    fn collect_methods(
117        &self,
118        class: &'i Class,
119        name: &str,
120        out: &mut Vec<&'i Member>,
121        seen: &mut Vec<&'i str>,
122    ) {
123        if seen.contains(&class.name.as_str()) {
124            return;
125        }
126        seen.push(&class.name);
127        for m in class.methods.iter().filter(|m| m.name == name) {
128            if !out.iter().any(|o| o.desc == m.desc) {
129                out.push(m);
130            }
131        }
132        let parents = class.extends.iter().chain(&class.implements);
133        for p in parents.filter_map(|p| self.class(p)) {
134            self.collect_methods(p, name, out, seen);
135        }
136    }
137
138    /// The field `name` of `class`, its own or inherited.
139    pub fn field(&self, class: &'i Class, name: &str) -> Option<&'i Member> {
140        self.ancestry(class)
141            .into_iter()
142            .find_map(|c| c.fields.iter().find(|f| f.name == name))
143    }
144}