use std::borrow::{Borrow, Cow};
use std::collections::HashSet;
use std::ops::Deref;
use std::path::PathBuf;
use std::sync::RwLock;
use cpclib_common::lazy_static;
use either::Either;
use regex::Regex;
use crate::error::AssemblerError;
use crate::preamble::*;
use crate::LocatedToken;
#[derive(Debug, Clone, Copy)]
pub enum ParsingState {
Standard,
FunctionLimited,
StructLimited,
GeneratedLimited
}
pub trait ParsingStateVerified {
fn is_accepted(&self, state: &ParsingState) -> bool;
}
impl ParsingStateVerified for LocatedToken {
fn is_accepted(&self, state: &ParsingState) -> bool {
match state {
ParsingState::GeneratedLimited => !self.is_directive(),
ParsingState::Standard => {
match self {
LocatedToken::Standard { token, span: _span } => token.is_accepted(state),
_ => true
}
}
ParsingState::FunctionLimited => {
match self {
LocatedToken::Standard { token, span: _span } => token.is_accepted(state),
LocatedToken::If { .. }
| LocatedToken::Repeat { .. }
| LocatedToken::Switch { .. }
| LocatedToken::Iterate { .. } => true,
_ => false
}
}
ParsingState::StructLimited => todo!()
}
}
}
impl ParsingStateVerified for Token {
fn is_accepted(&self, state: &ParsingState) -> bool {
match state {
ParsingState::GeneratedLimited => !self.is_directive(),
ParsingState::Standard => {
match self {
Token::Return(_) => false,
_ => true
}
}
ParsingState::FunctionLimited => {
match self {
Token::Equ(..) | Token::Let(..) => true,
Token::If { .. }
| Token::Repeat { .. }
| Token::Break
| Token::Switch { .. }
| Token::Iterate { .. } => true,
Token::Return(_) => true,
Token::Assert(..) | Token::Print(_) | Token::Fail(_) | Token::Comment(_) => {
true
}
_ => false
}
}
ParsingState::StructLimited => todo!()
}
}
}
#[derive(Debug)]
pub struct ParserContext {
pub state: ParsingState,
pub current_filename: Option<PathBuf>,
pub context_name: Option<String>,
pub search_path: Vec<PathBuf>,
pub read_referenced_files: bool,
pub dotted_directive: bool,
pub parse_warning: RwLock<Vec<AssemblerError>>,
pub source: Option<&'static str>
}
impl Clone for ParserContext {
fn clone(&self) -> Self {
Self {
current_filename: self.current_filename.clone(),
context_name: self.context_name.clone(),
search_path: self.search_path.clone(),
read_referenced_files: self.read_referenced_files.clone(),
parse_warning: self.parse_warning.write().unwrap().clone().into(),
state: self.state.clone(),
dotted_directive: self.dotted_directive.clone(),
source: self.source.clone()
}
}
}
impl Default for ParserContext {
fn default() -> Self {
ParserContext {
current_filename: None,
context_name: None,
search_path: Default::default(),
read_referenced_files: true,
parse_warning: Default::default(),
state: ParsingState::Standard,
dotted_directive: false,
source: None
}
}
}
impl ParserContext {
pub fn clone_with_state(&self, state: ParsingState) -> Self {
Self {
current_filename: self.current_filename.clone(),
context_name: self.context_name.clone(),
search_path: self.search_path.clone(),
read_referenced_files: self.read_referenced_files.clone(),
parse_warning: self.parse_warning.write().unwrap().clone().into(),
dotted_directive: self.dotted_directive.clone(),
source: self.source.clone(),
state
}
}
}
#[allow(missing_docs)]
impl ParserContext {
pub fn build_span<S: AsRef<str>>(&self, src: S) -> Z80Span {
Z80Span::new_extra(src.as_ref(), self)
}
pub fn set_current_filename<P: Into<PathBuf>>(&mut self, file: P) {
let file = file.into();
self.current_filename = Some(file.canonicalize().unwrap_or(file))
}
pub fn remove_filename(&mut self) {
self.current_filename = None;
}
pub fn set_context_name(&mut self, name: &str) {
self.context_name = Some(name.to_owned());
}
pub fn set_read_referenced_files(&mut self, tag: bool) {
self.read_referenced_files = tag;
}
pub fn set_dotted_directives(&mut self, tag: bool) {
self.dotted_directive = tag;
}
pub fn add_search_path<P: Into<PathBuf>>(&mut self, path: P) -> Result<(), AssemblerError> {
let path = path.into();
if std::path::Path::new(&path).is_dir() {
let path = path.canonicalize().unwrap();
let path = path.to_str().unwrap();
const PREFIX: &'static str = "\\\\?\\";
let path = if path.starts_with(PREFIX) {
path[PREFIX.len()..].to_string()
}
else {
path.to_string()
};
self.search_path.push(path.into());
Ok(())
}
else {
Err(AssemblerError::IOError {
msg: format!(
"{} is not a path and cannot be added in the search path",
path.to_str().unwrap().to_string()
)
})
}
}
pub fn add_search_path_from_file<P: Into<PathBuf>>(
&mut self,
file: P
) -> Result<(), AssemblerError> {
let file = file.into();
let path = file.canonicalize();
match path {
Ok(path) => {
let path = path.parent().unwrap().to_owned();
self.add_search_path(path)
}
Err(err) => {
Err(AssemblerError::IOError {
msg: format!(
"Unable to add search path for {}. {}",
file.to_str().unwrap().to_string(),
err.to_string()
)
})
}
}
}
pub fn get_path_for(
&self,
fname: &str,
env: Option<&Env>
) -> Result<PathBuf, either::Either<AssemblerError, Vec<String>>> {
use globset::*;
let mut does_not_exists = Vec::new();
let fname: Cow<str> = if let Some(env) = env {
let mut fname = fname.to_owned();
lazy_static::lazy_static! {
static ref RE: Regex = Regex::new(r"\{+[^\}]+\}+").unwrap();
}
let mut replace = HashSet::new();
for cap in RE.captures_iter(&fname) {
if cap[0] != fname {
replace.insert(cap[0].to_owned());
}
}
for model in replace.iter() {
let local_symbol = &model[1..model.len() - 1]; let local_value = match env.symbols().value(local_symbol) {
Ok(Some(Value::String(s))) => s.to_string(),
Ok(Some(Value::Expr(e))) => e.to_string(),
Ok(Some(Value::Counter(e))) => e.to_string(),
Ok(Some(unkn)) => {
unimplemented!("{:?}", unkn)
}
Ok(None) => {
return Err(Either::Left(AssemblerError::UnknownSymbol {
symbol: model.into(),
closest: env.symbols().closest_symbol(model, SymbolFor::Any).unwrap()
}))
}
Err(e) => return Err(Either::Left(e.into()))
};
fname = fname.replace(model, &local_value);
}
Cow::Owned(fname)
}
else {
Cow::Borrowed(fname)
};
let fname: &str = fname.borrow();
let fname = std::path::Path::new(fname);
if self.search_path.is_empty() {
if fname.is_file() {
return Ok(fname.into());
}
else {
does_not_exists.push(fname.to_str().unwrap().to_owned());
}
}
else {
for search in &self.search_path {
assert!(std::path::Path::new(&search).is_dir());
let current_path = search.join(fname.clone());
if current_path.is_file() {
return Ok(current_path);
}
else {
let glob =
GlobBuilder::new(current_path.as_path().display().to_string().as_str())
.case_insensitive(true)
.literal_separator(true)
.build()
.unwrap();
let matcher = glob.compile_matcher();
for entry in std::fs::read_dir(search).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if matcher.is_match(&path) {
return Ok(path);
}
}
does_not_exists.push(current_path.to_str().unwrap().to_owned());
}
}
}
return Err(Either::Right(does_not_exists));
}
pub fn add_warning(&self, warning: AssemblerError) {
self.parse_warning.write().unwrap().push(warning)
}
pub fn warnings(&self) -> Vec<AssemblerError> {
self.parse_warning.write().unwrap().deref().clone() }
pub fn pop_warning(&self) -> Option<AssemblerError> {
self.parse_warning.write().unwrap().pop() }
pub fn complete_source(&self) -> &str {
self.source.unwrap()
}
}
#[cfg(test)]
mod test_super {
use super::*;
#[test]
fn test_function_state() {
assert!(Token::Return(0.into()).is_accepted(&ParsingState::FunctionLimited));
}
#[test]
fn test_normal_state() {
assert!(!Token::Return(0.into()).is_accepted(&ParsingState::Standard));
}
}