1mod source;
7
8pub use source::token;
9
10pub mod ast;
11mod borrowck;
12pub mod index;
13mod lexer;
14pub mod macros;
15pub mod parser;
16pub mod resolve;
17pub mod system;
18mod typeck;
19
20pub use typeck::commands::command_arg;
21pub use typeck::{
22 Message, MethodUse, OperatorUse, Quoted, Type, Types, body_yields, builtin_type, content_path,
23 MessageTemplate, holes, item_of, list_item, leaves, system_name, system_type,
24};
25pub mod versions;
26pub mod words;
27
28pub mod spelling {
31 pub use crate::lexer::string::{CLOSE, ESCAPE, ESCAPES, OPEN, QUOTE, UNICODE_OPEN};
32 pub use crate::lexer::VERBATIM;
33
34 #[must_use]
36 pub fn is_keyword(name: &str) -> bool {
37 crate::words::reserved(name).is_some()
38 }
39}
40pub mod yog;
41
42use std::path::Path;
43
44use yog_data::Diagnostic;
45use yog_ids::Format;
46
47#[derive(Debug)]
49pub struct Parsed {
50 pub file: ast::File,
51 pub problems: Vec<Diagnostic>,
52}
53
54#[derive(Debug)]
56pub struct Checked {
57 pub file: ast::File,
58 pub symbols: resolve::Symbols,
59 pub problems: Vec<Diagnostic>,
60 pub types: Types,
62}
63
64#[must_use]
67pub fn parse(name: &str, src: &str) -> Parsed {
68 let mut report = yog_analysis::Report::new(name, src);
69 let file = read(name, src, (versions::newest(), Env::default()), &mut report);
70 Parsed {
71 file,
72 problems: report.out,
73 }
74}
75
76#[must_use]
79pub fn check(name: &str, src: &str) -> Checked {
80 check_with(name, src, None)
81}
82
83#[must_use]
87pub fn check_with(name: &str, src: &str, game: Option<&index::ClassIndex>) -> Checked {
88 check_in(
89 name,
90 src,
91 Env {
92 game,
93 ..Env::default()
94 },
95 )
96}
97
98#[derive(Clone, Copy, Default)]
101pub struct Env<'a> {
102 pub game: Option<&'a index::ClassIndex>,
103 pub macros: Option<&'a dyn macros::ProcMacros>,
104 pub files: Option<&'a dyn macros::ModFiles>,
106 pub to_server: bool,
108 pub content: Option<&'a [(String, String)]>,
111 pub others: &'a [&'a [ast::Decl]],
115}
116
117#[must_use]
121pub fn declarations(name: &str, src: &str, env: Env<'_>) -> Vec<ast::Decl> {
122 let mut report = yog_analysis::Report::new(name, src);
123 let mc = env.game.map_or(versions::newest(), |g| g.mc.as_str());
124 read(name, src, (mc, env), &mut report).decls
125}
126
127#[must_use]
129pub fn check_in(name: &str, src: &str, env: Env<'_>) -> Checked {
130 let game = env.game;
131 let mut report = yog_analysis::Report::new(name, src);
132 let mc = game.map_or(versions::newest(), |g| g.mc.as_str());
134 let file = read(name, src, (mc, env), &mut report);
135 let symbols = resolve::resolve(&file, &mut report);
136 let game = game.filter(|_| is_nanites(name));
137 let mut types = Types::default();
140 if !report.out.iter().any(yog_data::Diagnostic::is_error) {
141 let side = typeck::Allowed {
142 nanites: is_nanites(name),
143 to_server: env.to_server,
144 content: env.content.map(<[_]>::to_vec),
145 };
146 types = typeck::check(&file, side, (game, env.others), &mut report);
147 borrowck::check(&file, &types, &mut report);
148 }
149 Checked {
150 file,
151 symbols,
152 problems: report.out,
153 types,
154 }
155}
156
157fn read(
159 name: &str,
160 src: &str,
161 (mc, env): (&str, Env<'_>),
162 report: &mut yog_analysis::Report<'_>,
163) -> ast::File {
164 let nanites = is_nanites(name);
165 let text = preprocessed(src, mc, nanites, report);
166 let tokens = macros::expand(
167 lexer::lex(&text, report),
168 (&text, name),
169 (env.macros, env.files),
170 report,
171 );
172 let file = parser::Parser::new(&tokens, report, nanites).file();
173 check_targets(&file, nanites, report);
174 file
175}
176
177fn preprocessed(
182 src: &str,
183 mc: &str,
184 nanites: bool,
185 report: &mut yog_analysis::Report<'_>,
186) -> String {
187 use yog_analysis::preprocess::{Defines, Problem};
188 use yog_ids::script_problem::DIRECTIVE;
189 let defines: Defines = if nanites {
190 [(yog_ids::script_define::GAME.to_string(), mc.to_string())].into()
191 } else {
192 Defines::new()
193 };
194 let out = yog_analysis::preprocess::preprocess(src, &defines);
195 for (at, problem) in out.problems {
196 let span = source::token::Span::new(at.start, at.end);
197 let message = match problem {
198 Problem::Unmatched => "no `#if` before it, or after `#else`".to_string(),
199 Problem::Unterminated => "this `#if` never ends: `#endif`".to_string(),
200 Problem::BadCondition(why) => format!("the condition doesn't read: {why}"),
201 };
202 report.error(DIRECTIVE, span, message);
203 }
204 if out.directives && !nanites {
205 report.error(
206 DIRECTIVE,
207 source::token::Span::default(),
208 "a `.ys` runs on every game version unchanged: directives (`#if`) are for a `.yns`",
209 );
210 }
211 out.text
212}
213
214#[must_use]
217pub fn is_nanites(name: &str) -> bool {
218 Format::Nanites.is(Path::new(name))
219}
220
221#[must_use]
223pub fn targets(file: &ast::File) -> Option<&ast::VersionCond> {
224 file.decls.iter().find_map(|d| match &d.kind {
225 ast::DeclKind::Targets(cond) => Some(cond),
226 _ => None,
227 })
228}
229
230fn check_targets(file: &ast::File, nanites: bool, report: &mut yog_analysis::Report<'_>) {
232 use yog_ids::script_problem::TARGETS;
233 let all: Vec<_> = file
234 .decls
235 .iter()
236 .filter(|d| matches!(d.kind, ast::DeclKind::Targets(_)))
237 .collect();
238 match (nanites, all.as_slice()) {
239 (true, []) => {
240 let all = yog_ids::game_version::ALL_VALUES;
241 let (oldest, newest) = (all.first().unwrap_or(&""), all.last().unwrap_or(&""));
242 report.error(
243 TARGETS,
244 source::token::Span::default(),
245 format!("a `.yns` says which game versions its patches are for: `targets mc {oldest}..{newest};`"),
246 );
247 }
248 (true, [_, again, ..]) => report.error(TARGETS, again.span, "`targets` is said once a file"),
249 (false, [first, ..]) => report.error(
250 TARGETS,
251 first.span,
252 "`targets` is for `.yns` files: a `.ys` reaches Yog, not the game, so it runs on the mod's every version",
253 ),
254 _ => {}
255 }
256}
257
258#[must_use]
261pub fn content_base(kind: &str) -> Option<&'static str> {
262 match kind {
263 k if k == yog_ids::kind::BLOCK => Some(yog_script_ids::system::BLOCK_OBJECT),
264 k if k == yog_ids::kind::ITEM => Some(yog_script_ids::system::ITEM_OBJECT),
265 _ => None,
266 }
267}