use std::fmt::Display;
use anyhow::bail;
use mcvm_shared::later::Later;
use mcvm_shared::modifications::{ModloaderMatch, PluginLoaderMatch};
use mcvm_shared::pkg::PackageAddonHashes;
use mcvm_shared::util::yes_no;
use mcvm_shared::versions::VersionPattern;
use mcvm_shared::Side;
use super::conditions::Condition;
use super::lex::{TextPos, Token};
use super::parse::BlockId;
use super::vars::Value;
use super::FailReason;
use crate::conditions::{ArchCondition, OSCondition};
use crate::unexpected_token;
use mcvm_shared::addon::AddonKind;
#[derive(Debug, Clone)]
pub struct Instruction {
pub kind: InstrKind,
pub pos: TextPos,
}
#[derive(Debug, Clone)]
pub enum InstrKind {
If {
condition: Condition,
if_block: BlockId,
else_blocks: Vec<ElseBlock>,
},
Name(Later<String>),
Description(Later<String>),
LongDescription(Later<String>),
Authors(Vec<String>),
PackageMaintainers(Vec<String>),
Website(Later<String>),
SupportLink(Later<String>),
Documentation(Later<String>),
Source(Later<String>),
Issues(Later<String>),
Community(Later<String>),
Icon(Later<String>),
Banner(Later<String>),
Gallery(Vec<String>),
License(Later<String>),
Keywords(Vec<String>),
Categories(Vec<String>),
Features(Vec<String>),
DefaultFeatures(Vec<String>),
ModrinthID(Later<String>),
CurseForgeID(Later<String>),
SmithedID(Later<String>),
SupportedVersions(Vec<VersionPattern>),
SupportedModloaders(Vec<ModloaderMatch>),
SupportedPluginLoaders(Vec<PluginLoaderMatch>),
SupportedSides(Vec<Side>),
SupportedOperatingSystems(Vec<OSCondition>),
SupportedArchitectures(Vec<ArchCondition>),
Tags(Vec<String>),
OpenSource(Later<bool>),
Addon {
id: Value,
file_name: Value,
kind: Option<AddonKind>,
url: Value,
path: Value,
version: Value,
hashes: PackageAddonHashes<Value>,
},
Set(Later<String>, Value),
Require(Vec<Vec<super::parse::require::Package>>),
Refuse(Value),
Recommend(bool, Value),
Bundle(Value),
Compat(Value, Value),
Extend(Value),
Finish(),
Fail(Option<FailReason>),
Notice(Value),
Cmd(Vec<Value>),
Call(Later<String>),
Custom(Later<String>),
}
#[derive(Debug, Clone)]
pub struct ElseBlock {
pub block: BlockId,
pub condition: Option<Condition>,
}
impl Display for InstrKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::If { .. } => "if",
Self::Name(..) => "name",
Self::Description(..) => "description",
Self::LongDescription(..) => "long_description",
Self::Authors(..) => "authors",
Self::PackageMaintainers(..) => "package_maintainers",
Self::Website(..) => "website",
Self::SupportLink(..) => "support_link",
Self::Documentation(..) => "documentation",
Self::Source(..) => "source",
Self::Issues(..) => "issues",
Self::Community(..) => "community",
Self::Icon(..) => "icon",
Self::Banner(..) => "banner",
Self::Gallery(..) => "gallery",
Self::License(..) => "license",
Self::Keywords(..) => "keywords",
Self::Categories(..) => "categories",
Self::Features(..) => "features",
Self::DefaultFeatures(..) => "default_features",
Self::ModrinthID(..) => "modrinth_id",
Self::CurseForgeID(..) => "curseforge_id",
Self::SmithedID(..) => "smithed_id",
Self::SupportedVersions(..) => "supported_versions",
Self::SupportedModloaders(..) => "supported_modloaders",
Self::SupportedPluginLoaders(..) => "supported_plugin_loaders",
Self::SupportedSides(..) => "supported_sides",
Self::SupportedOperatingSystems(..) => "supported_operating_systems",
Self::SupportedArchitectures(..) => "supported_architectures",
Self::Tags(..) => "tags",
Self::OpenSource(..) => "open_source",
Self::Addon { .. } => "addon",
Self::Set(..) => "set",
Self::Require(..) => "require",
Self::Refuse(..) => "refuse",
Self::Recommend(..) => "recommend",
Self::Bundle(..) => "bundle",
Self::Compat(..) => "compat",
Self::Extend(..) => "extend",
Self::Finish() => "finish",
Self::Fail(..) => "fail",
Self::Notice(..) => "notice",
Self::Cmd(..) => "cmd",
Self::Call(..) => "call",
Self::Custom(..) => "custom",
}
)
}
}
impl Instruction {
pub fn new(kind: InstrKind, pos: TextPos) -> Self {
Self { kind, pos }
}
pub fn from_str(string: &str, pos: TextPos) -> anyhow::Result<Self> {
let kind = match string {
"name" => Ok::<InstrKind, anyhow::Error>(InstrKind::Name(Later::Empty)),
"description" => Ok(InstrKind::Description(Later::Empty)),
"long_description" => Ok(InstrKind::LongDescription(Later::Empty)),
"authors" => Ok(InstrKind::Authors(Vec::new())),
"package_maintainers" => Ok(InstrKind::PackageMaintainers(Vec::new())),
"website" => Ok(InstrKind::Website(Later::Empty)),
"support_link" => Ok(InstrKind::SupportLink(Later::Empty)),
"documentation" => Ok(InstrKind::Documentation(Later::Empty)),
"source" => Ok(InstrKind::Source(Later::Empty)),
"issues" => Ok(InstrKind::Issues(Later::Empty)),
"community" => Ok(InstrKind::Community(Later::Empty)),
"icon" => Ok(InstrKind::Icon(Later::Empty)),
"banner" => Ok(InstrKind::Banner(Later::Empty)),
"license" => Ok(InstrKind::License(Later::Empty)),
"keywords" => Ok(InstrKind::Keywords(Vec::new())),
"categories" => Ok(InstrKind::Categories(Vec::new())),
"features" => Ok(InstrKind::Features(Vec::new())),
"default_features" => Ok(InstrKind::DefaultFeatures(Vec::new())),
"modrinth_id" => Ok(InstrKind::ModrinthID(Later::Empty)),
"curseforge_id" => Ok(InstrKind::CurseForgeID(Later::Empty)),
"supported_versions" => Ok(InstrKind::SupportedVersions(Vec::new())),
"supported_modloaders" => Ok(InstrKind::SupportedModloaders(Vec::new())),
"supported_plugin_loaders" => Ok(InstrKind::SupportedPluginLoaders(Vec::new())),
"supported_sides" => Ok(InstrKind::SupportedSides(Vec::new())),
"supported_operating_systems" => Ok(InstrKind::SupportedOperatingSystems(Vec::new())),
"supported_architectures" => Ok(InstrKind::SupportedArchitectures(Vec::new())),
"tags" => Ok(InstrKind::Tags(Vec::new())),
"open_source" => Ok(InstrKind::OpenSource(Later::Empty)),
"set" => Ok(InstrKind::Set(Later::Empty, Value::None)),
"finish" => Ok(InstrKind::Finish()),
"fail" => Ok(InstrKind::Fail(None)),
"refuse" => Ok(InstrKind::Refuse(Value::None)),
"recommend" => Ok(InstrKind::Recommend(false, Value::None)),
"bundle" => Ok(InstrKind::Bundle(Value::None)),
"compat" => Ok(InstrKind::Compat(Value::None, Value::None)),
"extend" => Ok(InstrKind::Extend(Value::None)),
"notice" => Ok(InstrKind::Notice(Value::None)),
"call" => Ok(InstrKind::Call(Later::Empty)),
"custom" => Ok(InstrKind::Custom(Later::Empty)),
string => bail!("Unknown instruction '{string}' {}", pos),
}?;
Ok(Instruction::new(kind, pos))
}
pub fn is_finished_parsing(&self) -> bool {
match &self.kind {
InstrKind::Name(val)
| InstrKind::Description(val)
| InstrKind::LongDescription(val)
| InstrKind::SupportLink(val)
| InstrKind::Documentation(val)
| InstrKind::Source(val)
| InstrKind::Issues(val)
| InstrKind::Community(val)
| InstrKind::Icon(val)
| InstrKind::Banner(val)
| InstrKind::License(val)
| InstrKind::ModrinthID(val)
| InstrKind::CurseForgeID(val)
| InstrKind::SmithedID(val)
| InstrKind::Website(val)
| InstrKind::Call(val)
| InstrKind::Custom(val) => val.is_full(),
InstrKind::Features(val)
| InstrKind::Authors(val)
| InstrKind::PackageMaintainers(val)
| InstrKind::DefaultFeatures(val)
| InstrKind::Keywords(val)
| InstrKind::Categories(val)
| InstrKind::Tags(val)
| InstrKind::Gallery(val) => !val.is_empty(),
InstrKind::Refuse(val)
| InstrKind::Recommend(_, val)
| InstrKind::Bundle(val)
| InstrKind::Extend(val)
| InstrKind::Notice(val) => val.is_some(),
InstrKind::SupportedVersions(val) => !val.is_empty(),
InstrKind::SupportedModloaders(val) => !val.is_empty(),
InstrKind::SupportedPluginLoaders(val) => !val.is_empty(),
InstrKind::SupportedSides(val) => !val.is_empty(),
InstrKind::SupportedOperatingSystems(val) => !val.is_empty(),
InstrKind::SupportedArchitectures(val) => !val.is_empty(),
InstrKind::OpenSource(val) => val.is_full(),
InstrKind::Compat(val1, val2) => val1.is_some() && val2.is_some(),
InstrKind::Set(var, val) => var.is_full() && val.is_some(),
InstrKind::Cmd(list) => !list.is_empty(),
InstrKind::Fail(..) | InstrKind::Finish() => true,
InstrKind::If { .. } | InstrKind::Addon { .. } | InstrKind::Require(..) => {
unimplemented!()
}
}
}
pub fn parse(&mut self, tok: &Token, pos: &TextPos) -> anyhow::Result<bool> {
if let Token::Semicolon = tok {
if !self.is_finished_parsing() {
bail!("Instruction was incomplete {pos}");
}
Ok(true)
} else {
match &mut self.kind {
InstrKind::Name(text)
| InstrKind::Description(text)
| InstrKind::LongDescription(text)
| InstrKind::Website(text)
| InstrKind::SupportLink(text)
| InstrKind::Documentation(text)
| InstrKind::Source(text)
| InstrKind::Issues(text)
| InstrKind::Community(text)
| InstrKind::Icon(text)
| InstrKind::Banner(text)
| InstrKind::License(text)
| InstrKind::ModrinthID(text)
| InstrKind::CurseForgeID(text)
| InstrKind::Custom(text) => {
if text.is_empty() {
text.fill(parse_string(tok, pos)?);
} else {
unexpected_token!(tok, pos);
}
}
InstrKind::Refuse(val)
| InstrKind::Bundle(val)
| InstrKind::Notice(val)
| InstrKind::Extend(val) => {
if let Value::None = val {
*val = parse_arg(tok, pos)?;
} else {
unexpected_token!(tok, pos);
}
}
InstrKind::Authors(list)
| InstrKind::PackageMaintainers(list)
| InstrKind::Features(list)
| InstrKind::DefaultFeatures(list)
| InstrKind::Keywords(list)
| InstrKind::Categories(list)
| InstrKind::Tags(list)
| InstrKind::Gallery(list) => list.push(parse_string(tok, pos)?),
InstrKind::Cmd(list) => list.push(parse_arg(tok, pos)?),
InstrKind::Recommend(inverted, val) => match tok {
Token::Bang => {
if *inverted || val.is_some() {
unexpected_token!(tok, pos);
}
*inverted = true;
}
_ => {
if let Value::None = val {
*val = parse_arg(tok, pos)?;
} else {
unexpected_token!(tok, pos);
}
}
},
InstrKind::SupportedModloaders(list) => match tok {
Token::Ident(name) => {
if let Some(val) = ModloaderMatch::parse_from_str(name) {
list.push(val);
} else {
bail!("Value is not a valid modloader match argument")
}
}
_ => unexpected_token!(tok, pos),
},
InstrKind::SupportedPluginLoaders(list) => match tok {
Token::Ident(name) => {
if let Some(val) = PluginLoaderMatch::parse_from_str(name) {
list.push(val);
} else {
bail!("Value is not a valid plugin loader match argument")
}
}
_ => unexpected_token!(tok, pos),
},
InstrKind::SupportedSides(list) => match tok {
Token::Ident(name) => {
if let Some(val) = Side::parse_from_str(name) {
list.push(val);
} else {
bail!("Value is not a valid side argument")
}
}
_ => unexpected_token!(tok, pos),
},
InstrKind::OpenSource(val) => match tok {
Token::Ident(name) => match yes_no(name) {
Some(yes_no) => val.fill(yes_no),
None => bail!("Value is not a valid open_source argument"),
},
_ => unexpected_token!(tok, pos),
},
InstrKind::Compat(package, compat) => {
if let Value::None = package {
*package = parse_arg(tok, pos)?;
} else if let Value::None = compat {
*compat = parse_arg(tok, pos)?;
} else {
unexpected_token!(tok, pos);
}
}
InstrKind::Set(var, val) => {
if var.is_full() {
if let Value::None = val {
*val = parse_arg(tok, pos)?;
} else {
unexpected_token!(tok, pos);
}
} else {
match tok {
Token::Ident(name) => var.fill(name.clone()),
_ => unexpected_token!(tok, pos),
}
}
}
InstrKind::Fail(reason) => match tok {
Token::Ident(name) => {
if reason.is_none() {
*reason = match FailReason::from_string(name) {
Some(reason) => Some(reason),
None => {
bail!("Unknown fail reason '{}' {}", name.clone(), pos.clone());
}
}
} else {
unexpected_token!(tok, pos);
}
}
_ => unexpected_token!(tok, pos),
},
InstrKind::Call(routine) => {
match tok {
Token::Ident(name) => {
if crate::routine::is_reserved(name) {
bail!("Cannot use reserved routine name '{name}' in call instruction {}", pos.clone());
}
routine.fill(name.clone())
}
_ => unexpected_token!(tok, pos),
}
}
_ => {}
}
Ok(false)
}
}
}
impl Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.kind)
}
}
pub fn parse_arg(tok: &Token, pos: &TextPos) -> anyhow::Result<Value> {
match tok {
Token::Variable(name) => Ok(Value::Var(name.to_string())),
Token::Str(text) => Ok(Value::Literal(text.clone())),
Token::Num(num) => Ok(Value::Literal(num.to_string())),
_ => unexpected_token!(tok, pos),
}
}
pub fn parse_string(tok: &Token, pos: &TextPos) -> anyhow::Result<String> {
match tok {
Token::Str(text) => Ok(text.clone()),
_ => unexpected_token!(tok, pos),
}
}