1use std::fmt::Display;
2
3use anyhow::bail;
4use mcvm_shared::later::Later;
5use mcvm_shared::modifications::{ModloaderMatch, PluginLoaderMatch};
6use mcvm_shared::pkg::PackageAddonHashes;
7use mcvm_shared::util::yes_no;
8use mcvm_shared::versions::VersionPattern;
9use mcvm_shared::Side;
10
11use super::conditions::Condition;
12use super::lex::{TextPos, Token};
13use super::parse::BlockId;
14use super::vars::Value;
15use super::FailReason;
16use crate::conditions::{ArchCondition, OSCondition};
17use crate::unexpected_token;
18use mcvm_shared::addon::AddonKind;
19
20#[derive(Debug, Clone)]
22pub struct Instruction {
23 pub kind: InstrKind,
25 pub pos: TextPos,
27}
28
29#[derive(Debug, Clone)]
31pub enum InstrKind {
32 If {
34 condition: Condition,
36 if_block: BlockId,
38 else_blocks: Vec<ElseBlock>,
40 },
41 Name(Later<String>),
43 Description(Later<String>),
45 LongDescription(Later<String>),
47 Authors(Vec<String>),
49 PackageMaintainers(Vec<String>),
51 Website(Later<String>),
53 SupportLink(Later<String>),
55 Documentation(Later<String>),
57 Source(Later<String>),
59 Issues(Later<String>),
61 Community(Later<String>),
63 Icon(Later<String>),
65 Banner(Later<String>),
67 Gallery(Vec<String>),
69 License(Later<String>),
71 Keywords(Vec<String>),
73 Categories(Vec<String>),
75 Features(Vec<String>),
77 DefaultFeatures(Vec<String>),
79 ContentVersions(Vec<String>),
81 ModrinthID(Later<String>),
83 CurseForgeID(Later<String>),
85 SmithedID(Later<String>),
87 SupportedVersions(Vec<VersionPattern>),
89 SupportedModloaders(Vec<ModloaderMatch>),
91 SupportedPluginLoaders(Vec<PluginLoaderMatch>),
93 SupportedSides(Vec<Side>),
95 SupportedOperatingSystems(Vec<OSCondition>),
97 SupportedArchitectures(Vec<ArchCondition>),
99 Tags(Vec<String>),
101 OpenSource(Later<bool>),
103 Addon {
105 id: Value,
107 file_name: Value,
109 kind: Option<AddonKind>,
111 url: Value,
113 path: Value,
115 version: Value,
117 hashes: PackageAddonHashes<Value>,
119 },
120 Set(Later<String>, Value),
122 Require(Vec<Vec<super::parse::require::Package>>),
124 Refuse(Value),
126 Recommend(bool, Value),
128 Bundle(Value),
130 Compat(Value, Value),
132 Extend(Value),
134 Finish(),
136 Fail(Option<FailReason>),
138 Notice(Value),
140 Cmd(Vec<Value>),
142 Call(Later<String>),
144 Custom(Later<String>, Vec<String>),
146}
147
148#[derive(Debug, Clone)]
150pub struct ElseBlock {
151 pub block: BlockId,
153 pub condition: Option<Condition>,
155}
156
157impl Display for InstrKind {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 write!(
160 f,
161 "{}",
162 match self {
163 Self::If { .. } => "if",
164 Self::Name(..) => "name",
165 Self::Description(..) => "description",
166 Self::LongDescription(..) => "long_description",
167 Self::Authors(..) => "authors",
168 Self::PackageMaintainers(..) => "package_maintainers",
169 Self::Website(..) => "website",
170 Self::SupportLink(..) => "support_link",
171 Self::Documentation(..) => "documentation",
172 Self::Source(..) => "source",
173 Self::Issues(..) => "issues",
174 Self::Community(..) => "community",
175 Self::Icon(..) => "icon",
176 Self::Banner(..) => "banner",
177 Self::Gallery(..) => "gallery",
178 Self::License(..) => "license",
179 Self::Keywords(..) => "keywords",
180 Self::Categories(..) => "categories",
181 Self::Features(..) => "features",
182 Self::DefaultFeatures(..) => "default_features",
183 Self::ContentVersions(..) => "content_versions",
184 Self::ModrinthID(..) => "modrinth_id",
185 Self::CurseForgeID(..) => "curseforge_id",
186 Self::SmithedID(..) => "smithed_id",
187 Self::SupportedVersions(..) => "supported_versions",
188 Self::SupportedModloaders(..) => "supported_modloaders",
189 Self::SupportedPluginLoaders(..) => "supported_plugin_loaders",
190 Self::SupportedSides(..) => "supported_sides",
191 Self::SupportedOperatingSystems(..) => "supported_operating_systems",
192 Self::SupportedArchitectures(..) => "supported_architectures",
193 Self::Tags(..) => "tags",
194 Self::OpenSource(..) => "open_source",
195 Self::Addon { .. } => "addon",
196 Self::Set(..) => "set",
197 Self::Require(..) => "require",
198 Self::Refuse(..) => "refuse",
199 Self::Recommend(..) => "recommend",
200 Self::Bundle(..) => "bundle",
201 Self::Compat(..) => "compat",
202 Self::Extend(..) => "extend",
203 Self::Finish() => "finish",
204 Self::Fail(..) => "fail",
205 Self::Notice(..) => "notice",
206 Self::Cmd(..) => "cmd",
207 Self::Call(..) => "call",
208 Self::Custom(..) => "custom",
209 }
210 )
211 }
212}
213
214impl Instruction {
215 pub fn new(kind: InstrKind, pos: TextPos) -> Self {
217 Self { kind, pos }
218 }
219
220 pub fn from_str(string: &str, pos: TextPos) -> anyhow::Result<Self> {
222 let kind = match string {
223 "name" => Ok::<InstrKind, anyhow::Error>(InstrKind::Name(Later::Empty)),
224 "description" => Ok(InstrKind::Description(Later::Empty)),
225 "long_description" => Ok(InstrKind::LongDescription(Later::Empty)),
226 "authors" => Ok(InstrKind::Authors(Vec::new())),
227 "package_maintainers" => Ok(InstrKind::PackageMaintainers(Vec::new())),
228 "website" => Ok(InstrKind::Website(Later::Empty)),
229 "support_link" => Ok(InstrKind::SupportLink(Later::Empty)),
230 "documentation" => Ok(InstrKind::Documentation(Later::Empty)),
231 "source" => Ok(InstrKind::Source(Later::Empty)),
232 "issues" => Ok(InstrKind::Issues(Later::Empty)),
233 "community" => Ok(InstrKind::Community(Later::Empty)),
234 "icon" => Ok(InstrKind::Icon(Later::Empty)),
235 "banner" => Ok(InstrKind::Banner(Later::Empty)),
236 "license" => Ok(InstrKind::License(Later::Empty)),
237 "keywords" => Ok(InstrKind::Keywords(Vec::new())),
238 "categories" => Ok(InstrKind::Categories(Vec::new())),
239 "features" => Ok(InstrKind::Features(Vec::new())),
240 "default_features" => Ok(InstrKind::DefaultFeatures(Vec::new())),
241 "content_versions" => Ok(InstrKind::ContentVersions(Vec::new())),
242 "modrinth_id" => Ok(InstrKind::ModrinthID(Later::Empty)),
243 "curseforge_id" => Ok(InstrKind::CurseForgeID(Later::Empty)),
244 "supported_versions" => Ok(InstrKind::SupportedVersions(Vec::new())),
245 "supported_modloaders" => Ok(InstrKind::SupportedModloaders(Vec::new())),
246 "supported_plugin_loaders" => Ok(InstrKind::SupportedPluginLoaders(Vec::new())),
247 "supported_sides" => Ok(InstrKind::SupportedSides(Vec::new())),
248 "supported_operating_systems" => Ok(InstrKind::SupportedOperatingSystems(Vec::new())),
249 "supported_architectures" => Ok(InstrKind::SupportedArchitectures(Vec::new())),
250 "tags" => Ok(InstrKind::Tags(Vec::new())),
251 "open_source" => Ok(InstrKind::OpenSource(Later::Empty)),
252 "set" => Ok(InstrKind::Set(Later::Empty, Value::None)),
253 "finish" => Ok(InstrKind::Finish()),
254 "fail" => Ok(InstrKind::Fail(None)),
255 "refuse" => Ok(InstrKind::Refuse(Value::None)),
256 "recommend" => Ok(InstrKind::Recommend(false, Value::None)),
257 "bundle" => Ok(InstrKind::Bundle(Value::None)),
258 "compat" => Ok(InstrKind::Compat(Value::None, Value::None)),
259 "extend" => Ok(InstrKind::Extend(Value::None)),
260 "notice" => Ok(InstrKind::Notice(Value::None)),
261 "call" => Ok(InstrKind::Call(Later::Empty)),
262 "custom" => Ok(InstrKind::Custom(Later::Empty, Vec::new())),
263 string => bail!("Unknown instruction '{string}' {}", pos),
264 }?;
265
266 Ok(Instruction::new(kind, pos))
267 }
268
269 pub fn is_finished_parsing(&self) -> bool {
272 match &self.kind {
273 InstrKind::Name(val)
274 | InstrKind::Description(val)
275 | InstrKind::LongDescription(val)
276 | InstrKind::SupportLink(val)
277 | InstrKind::Documentation(val)
278 | InstrKind::Source(val)
279 | InstrKind::Issues(val)
280 | InstrKind::Community(val)
281 | InstrKind::Icon(val)
282 | InstrKind::Banner(val)
283 | InstrKind::License(val)
284 | InstrKind::ModrinthID(val)
285 | InstrKind::CurseForgeID(val)
286 | InstrKind::SmithedID(val)
287 | InstrKind::Website(val)
288 | InstrKind::Call(val)
289 | InstrKind::Custom(val, _) => val.is_full(),
290 InstrKind::Features(val)
291 | InstrKind::Authors(val)
292 | InstrKind::PackageMaintainers(val)
293 | InstrKind::DefaultFeatures(val)
294 | InstrKind::ContentVersions(val)
295 | InstrKind::Keywords(val)
296 | InstrKind::Categories(val)
297 | InstrKind::Tags(val)
298 | InstrKind::Gallery(val) => !val.is_empty(),
299 InstrKind::Refuse(val)
300 | InstrKind::Recommend(_, val)
301 | InstrKind::Bundle(val)
302 | InstrKind::Extend(val)
303 | InstrKind::Notice(val) => val.is_some(),
304 InstrKind::SupportedVersions(val) => !val.is_empty(),
305 InstrKind::SupportedModloaders(val) => !val.is_empty(),
306 InstrKind::SupportedPluginLoaders(val) => !val.is_empty(),
307 InstrKind::SupportedSides(val) => !val.is_empty(),
308 InstrKind::SupportedOperatingSystems(val) => !val.is_empty(),
309 InstrKind::SupportedArchitectures(val) => !val.is_empty(),
310 InstrKind::OpenSource(val) => val.is_full(),
311 InstrKind::Compat(val1, val2) => val1.is_some() && val2.is_some(),
312 InstrKind::Set(var, val) => var.is_full() && val.is_some(),
313 InstrKind::Cmd(list) => !list.is_empty(),
314 InstrKind::Fail(..) | InstrKind::Finish() => true,
315 InstrKind::If { .. } | InstrKind::Addon { .. } | InstrKind::Require(..) => {
316 unimplemented!()
317 }
318 }
319 }
320
321 pub fn parse(&mut self, tok: &Token, pos: &TextPos) -> anyhow::Result<bool> {
323 if let Token::Semicolon = tok {
324 if !self.is_finished_parsing() {
325 bail!("Instruction was incomplete {pos}");
326 }
327 Ok(true)
328 } else {
329 match &mut self.kind {
330 InstrKind::Name(text)
331 | InstrKind::Description(text)
332 | InstrKind::LongDescription(text)
333 | InstrKind::Website(text)
334 | InstrKind::SupportLink(text)
335 | InstrKind::Documentation(text)
336 | InstrKind::Source(text)
337 | InstrKind::Issues(text)
338 | InstrKind::Community(text)
339 | InstrKind::Icon(text)
340 | InstrKind::Banner(text)
341 | InstrKind::License(text)
342 | InstrKind::ModrinthID(text)
343 | InstrKind::CurseForgeID(text) => {
344 if text.is_empty() {
345 text.fill(parse_string(tok, pos)?);
346 } else {
347 unexpected_token!(tok, pos);
348 }
349 }
350 InstrKind::Refuse(val)
351 | InstrKind::Bundle(val)
352 | InstrKind::Notice(val)
353 | InstrKind::Extend(val) => {
354 if let Value::None = val {
355 *val = parse_arg(tok, pos)?;
356 } else {
357 unexpected_token!(tok, pos);
358 }
359 }
360 InstrKind::Authors(list)
361 | InstrKind::PackageMaintainers(list)
362 | InstrKind::Features(list)
363 | InstrKind::DefaultFeatures(list)
364 | InstrKind::ContentVersions(list)
365 | InstrKind::Keywords(list)
366 | InstrKind::Categories(list)
367 | InstrKind::Tags(list)
368 | InstrKind::Gallery(list) => list.push(parse_string(tok, pos)?),
369 InstrKind::Cmd(list) => list.push(parse_arg(tok, pos)?),
370 InstrKind::Custom(cmd, args) => {
371 if cmd.is_empty() {
372 cmd.fill(parse_string(tok, pos)?);
373 } else {
374 args.push(parse_string(tok, pos)?);
375 }
376 }
377 InstrKind::Recommend(inverted, val) => match tok {
378 Token::Bang => {
379 if *inverted || val.is_some() {
380 unexpected_token!(tok, pos);
381 }
382
383 *inverted = true;
384 }
385 _ => {
386 if let Value::None = val {
387 *val = parse_arg(tok, pos)?;
388 } else {
389 unexpected_token!(tok, pos);
390 }
391 }
392 },
393 InstrKind::SupportedModloaders(list) => match tok {
394 Token::Ident(name) => {
395 if let Some(val) = ModloaderMatch::parse_from_str(name) {
396 list.push(val);
397 } else {
398 bail!("Value is not a valid modloader match argument")
399 }
400 }
401 _ => unexpected_token!(tok, pos),
402 },
403 InstrKind::SupportedPluginLoaders(list) => match tok {
404 Token::Ident(name) => {
405 if let Some(val) = PluginLoaderMatch::parse_from_str(name) {
406 list.push(val);
407 } else {
408 bail!("Value is not a valid plugin loader match argument")
409 }
410 }
411 _ => unexpected_token!(tok, pos),
412 },
413 InstrKind::SupportedSides(list) => match tok {
414 Token::Ident(name) => {
415 if let Some(val) = Side::parse_from_str(name) {
416 list.push(val);
417 } else {
418 bail!("Value is not a valid side argument")
419 }
420 }
421 _ => unexpected_token!(tok, pos),
422 },
423 InstrKind::OpenSource(val) => match tok {
424 Token::Ident(name) => match yes_no(name) {
425 Some(yes_no) => val.fill(yes_no),
426 None => bail!("Value is not a valid open_source argument"),
427 },
428 _ => unexpected_token!(tok, pos),
429 },
430 InstrKind::Compat(package, compat) => {
431 if let Value::None = package {
432 *package = parse_arg(tok, pos)?;
433 } else if let Value::None = compat {
434 *compat = parse_arg(tok, pos)?;
435 } else {
436 unexpected_token!(tok, pos);
437 }
438 }
439 InstrKind::Set(var, val) => {
440 if var.is_full() {
441 if let Value::None = val {
442 *val = parse_arg(tok, pos)?;
443 } else {
444 unexpected_token!(tok, pos);
445 }
446 } else {
447 match tok {
448 Token::Ident(name) => var.fill(name.clone()),
449 _ => unexpected_token!(tok, pos),
450 }
451 }
452 }
453 InstrKind::Fail(reason) => match tok {
454 Token::Ident(name) => {
455 if reason.is_none() {
456 *reason = match FailReason::from_string(name) {
457 Some(reason) => Some(reason),
458 None => {
459 bail!("Unknown fail reason '{}' {}", name.clone(), pos.clone());
460 }
461 }
462 } else {
463 unexpected_token!(tok, pos);
464 }
465 }
466 _ => unexpected_token!(tok, pos),
467 },
468 InstrKind::Call(routine) => {
469 match tok {
470 Token::Ident(name) => {
471 if crate::routine::is_reserved(name) {
472 bail!("Cannot use reserved routine name '{name}' in call instruction {}", pos.clone());
473 }
474 routine.fill(name.clone())
475 }
476 _ => unexpected_token!(tok, pos),
477 }
478 }
479 _ => {}
480 }
481
482 Ok(false)
483 }
484 }
485}
486
487impl Display for Instruction {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 write!(f, "{}", self.kind)
490 }
491}
492
493pub fn parse_arg(tok: &Token, pos: &TextPos) -> anyhow::Result<Value> {
495 match tok {
496 Token::Variable(name) => Ok(Value::Var(name.to_string())),
497 Token::Str(text) => Ok(Value::Literal(text.clone())),
498 Token::Num(num) => Ok(Value::Literal(num.to_string())),
499 _ => unexpected_token!(tok, pos),
500 }
501}
502
503pub fn parse_string(tok: &Token, pos: &TextPos) -> anyhow::Result<String> {
505 match tok {
506 Token::Str(text) => Ok(text.clone()),
507 _ => unexpected_token!(tok, pos),
508 }
509}