pub mod check_playing;
pub mod prompt;
pub mod token_processor;
pub mod validity;
use std::ops::RangeInclusive;
use num::BigUint;
use thiserror::Error;
#[cfg(feature = "diagnostics")]
use crate::diagnostics::{SimpleSource, ToAriadne};
#[cfg(feature = "diagnostics")]
use ariadne::{Color, Label, Report, ReportKind};
use crate::bms::{
ParseConfig,
command::{
ObjId,
channel::{Channel, mapper::KeyLayoutMapper},
mixin::SourceRangeMixin,
time::{ObjTime, Track},
},
lex::token::TokenWithRange,
model::Bms,
rng::Rng,
};
use self::{
prompt::Prompter,
token_processor::{ProcessContext, TokenModifier, TokenProcessor},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ParseError {
#[error("unexpected control flow {0}")]
UnexpectedControlFlow(&'static str),
#[error("random generated value out of range: expected {expected:?}, got {actual}")]
RandomGeneratedValueOutOfRange {
expected: RangeInclusive<BigUint>,
actual: BigUint,
},
#[error("switch generated value out of range: expected {expected:?}, got {actual}")]
SwitchGeneratedValueOutOfRange {
expected: RangeInclusive<BigUint>,
actual: BigUint,
},
}
pub type ParseErrorWithRange = SourceRangeMixin<ParseError>;
#[cfg(feature = "diagnostics")]
impl ToAriadne for ParseErrorWithRange {
fn to_report<'a>(
&self,
src: &SimpleSource<'a>,
) -> Report<'a, (String, std::ops::Range<usize>)> {
let (start, end) = self.as_span();
let filename = src.name().to_string();
Report::build(ReportKind::Error, (filename.clone(), start..end))
.with_message(format!("parse error: {}", self.content()))
.with_label(Label::new((filename, start..end)).with_color(Color::Red))
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ParseWarning {
#[error("syntax error: {0}")]
SyntaxError(String),
#[error("undefined object: {0:?}")]
UndefinedObject(ObjId),
#[error("duplicating definition: {0}")]
DuplicatingDef(ObjId),
#[error("duplicating track object: {0} {1}")]
DuplicatingTrackObj(Track, Channel),
#[error("duplicating channel object: {0} {1}")]
DuplicatingChannelObj(ObjTime, Channel),
#[error("expected id format is base 62 (`0-9A-Za-z`)")]
OutOfBase62,
}
pub type ParseWarningWithRange = SourceRangeMixin<ParseWarning>;
pub(crate) type Result<T> = core::result::Result<T, ParseWarning>;
#[cfg(feature = "diagnostics")]
impl ToAriadne for ParseWarningWithRange {
fn to_report<'a>(
&self,
src: &SimpleSource<'a>,
) -> Report<'a, (String, std::ops::Range<usize>)> {
let (start, end) = self.as_span();
let filename = src.name().to_string();
Report::build(ReportKind::Warning, (filename.clone(), start..end))
.with_message(format!("parse: {}", self.content()))
.with_label(Label::new((filename, start..end)).with_color(Color::Blue))
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[must_use]
pub struct ParseOutput {
pub bms: core::result::Result<Bms, ParseErrorWithRange>,
pub parse_warnings: Vec<ParseWarningWithRange>,
}
impl Bms {
pub fn from_token_stream<'a, T: KeyLayoutMapper, P: Prompter, R: Rng, M: TokenModifier>(
token_iter: impl IntoIterator<Item = &'a TokenWithRange<'a>>,
config: ParseConfig<T, P, R, M>,
) -> ParseOutput {
let tokens: Vec<_> = token_iter.into_iter().collect();
let mut tokens_slice = tokens.as_slice();
let (proc, prompter) = config.build();
let mut ctx = ProcessContext::new(&mut tokens_slice, &prompter);
let res = proc.process(&mut ctx);
ParseOutput {
bms: res,
parse_warnings: ctx.into_warnings(),
}
}
}