1use std::borrow::{Borrow, Cow};
2use std::collections::HashSet;
3use std::ops::Deref;
4use std::path::PathBuf;
5use std::sync::{LazyLock, RwLock};
6
7use cpclib_common::camino::{Utf8Path, Utf8PathBuf};
8use cpclib_common::winnow::BStr;
9use either::Either;
10use regex::Regex;
11
12use super::line_col::LineColLookup;
13use crate::LocatedToken;
14use crate::error::AssemblerError;
15use crate::preamble::*;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ParsingState {
20 Standard,
22 FunctionLimited,
24 StructLimited,
26 GeneratedLimited, SymbolsLimited
30}
31
32pub trait ParsingStateVerified {
33 fn is_accepted(&self, state: &ParsingState) -> bool;
34}
35
36impl ParsingStateVerified for LocatedToken {
37 fn is_accepted(&self, state: &ParsingState) -> bool {
38 self.deref().is_accepted(state)
39 }
40}
41
42macro_rules! parsing_state_verified_inner {
43 () => {
44 fn is_accepted(&self, state: &ParsingState) -> bool {
45 match state {
46 ParsingState::GeneratedLimited => !self.is_directive(),
47 ParsingState::Standard => {
48 match self {
49 Self::Return(..) => false,
50 _ => true
51 }
52 },
53 ParsingState::FunctionLimited => {
54 match self {
55 Self::Equ { .. } | Self::Let(..) => true,
56 Self::If { .. }
57 | Self::Repeat { .. }
58 | Self::Break
59 | Self::Switch { .. }
60 | Self::Iterate { .. } => true,
61 Self::Return(_) => true,
62 Self::Assert(..) | Self::Print(_) | Self::Fail(_) | Self::Comment(_) => {
63 true
64 },
65 _ => false
66 }
67 },
68 ParsingState::StructLimited => {
69 match self {
70 Self::Defb(..) | Self::Defw(..) | Self::Str(..) | Self::MacroCall(..) => {
71 true
72 },
73 _ => false
74 }
75 },
76 ParsingState::SymbolsLimited => {
77 match self {
78 Self::Equ { .. } | Self::Let(..) | Self::Comment(_) => true,
79 _ => false
80 }
81 },
82 }
83 }
84 };
85}
86
87impl ParsingStateVerified for LocatedTokenInner {
88 parsing_state_verified_inner!();
89}
90
91impl ParsingStateVerified for Token {
92 parsing_state_verified_inner!();
93}
94
95#[derive(Debug, PartialEq, Eq, Clone)]
96pub struct ParserOptions {
97 pub search_path: Vec<Utf8PathBuf>,
99 pub read_referenced_files: bool,
101 pub show_progress: bool,
102 pub dotted_directive: bool,
104 pub assembler_flavor: AssemblerFlavor
105}
106
107impl Default for ParserOptions {
108 fn default() -> Self {
109 ParserOptions {
110 search_path: Default::default(),
111 read_referenced_files: true,
112 dotted_directive: false,
113 show_progress: false,
114 assembler_flavor: AssemblerFlavor::Basm
115 }
116 }
117}
118
119impl ParserOptions {
120 pub fn context_builder(self) -> ParserContextBuilder {
121 ParserContextBuilder {
122 options: self,
123 current_filename: None,
124 context_name: None,
125 state: ParsingState::Standard
126 }
127 }
128}
129
130pub struct ParserContextBuilder {
131 options: ParserOptions,
132 current_filename: Option<Utf8PathBuf>,
133 context_name: Option<String>,
134 state: ParsingState
135}
136
137impl Default for ParserContextBuilder {
138 fn default() -> Self {
139 ParserOptions::default().context_builder()
140 }
141}
142
143impl From<ParserContext> for ParserContextBuilder {
144 fn from(ctx: ParserContext) -> Self {
145 Self {
146 state: ctx.state,
147 current_filename: ctx.current_filename,
148 context_name: ctx.context_name,
149 options: ctx.options
150 }
151 }
152}
153
154impl ParserContextBuilder {
155 pub fn current_filename(&self) -> Option<&Utf8Path> {
156 self.current_filename.as_ref().map(|p| p.as_path())
157 }
158
159 pub fn context_name(&self) -> Option<&str> {
160 self.context_name.as_deref()
161 }
162
163 pub fn set_current_filename<S: Into<Utf8PathBuf>>(mut self, fname: S) -> ParserContextBuilder {
164 self.current_filename = Some(fname.into());
165 self
166 }
167
168 pub fn remove_filename(mut self) -> Self {
169 self.current_filename.take();
170 self
171 }
172
173 pub fn set_context_name<S: Into<String>>(mut self, name: S) -> ParserContextBuilder {
174 self.context_name = Some(name.into());
175 self
176 }
177
178 pub fn set_state(mut self, state: ParsingState) -> Self {
179 self.state = state;
180 self
181 }
182
183 pub fn set_options(mut self, options: ParserOptions) -> Self {
184 self.options = options;
185 self
186 }
187
188 #[inline]
190 pub fn build(self, code: &str) -> ParserContext {
191 let code: &'static str = unsafe { std::mem::transmute(code) };
192 let str: &'static BStr = unsafe { std::mem::transmute(BStr::new(code)) };
193 ParserContext {
194 options: self.options,
195 current_filename: self.current_filename,
196 context_name: self.context_name,
197 state: self.state,
198 source: str,
199 line_col_lut: Default::default()
200 }
201 }
202}
203
204impl ParserOptions {
205 pub fn set_read_referenced_files(&mut self, tag: bool) {
206 self.read_referenced_files = tag;
207 }
208
209 pub fn set_dotted_directives(&mut self, tag: bool) {
210 self.dotted_directive = tag;
211 }
212
213 pub fn add_search_path<P: Into<PathBuf>>(&mut self, path: P) -> Result<(), AssemblerError> {
216 let path = path.into();
217
218 if path.is_dir() {
219 #[cfg(not(target_arch = "wasm32"))]
220 let path = path.canonicalize().unwrap();
221
222 let path = path.to_str().unwrap();
224 const PREFIX: &str = "\\\\?\\";
225 let path = if path.starts_with(PREFIX) {
226 path[PREFIX.len()..].to_string()
227 }
228 else {
229 path.to_string()
230 };
231
232 self.search_path.push(path.into());
234 Ok(())
235 }
236 else {
237 Err(AssemblerError::IOError {
238 msg: format!(
239 "{} is not a path and cannot be added in the search path",
240 path.to_str().unwrap()
241 )
242 })
243 }
244 }
245
246 pub fn add_search_path_from_file<P: Into<PathBuf>>(
248 &mut self,
249 file: P
250 ) -> Result<(), AssemblerError> {
251 let file = file.into();
252 let path = file.canonicalize();
253
254 match path {
255 Ok(path) => {
256 let path = path.parent().unwrap().to_owned();
257 self.add_search_path(path)
258 },
259
260 Err(err) => {
261 Err(AssemblerError::IOError {
262 msg: format!(
263 "Unable to add search path for {}. {}",
264 file.to_str().unwrap(),
265 err
266 )
267 })
268 },
269 }
270 }
271
272 pub fn get_path_for(
275 &self,
276 fname: &str,
277 env: Option<&Env>
278 ) -> Result<Utf8PathBuf, either::Either<AssemblerError, Vec<String>>> {
279 use globset::*;
280 let mut does_not_exists = Vec::new();
281 static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{+[^\}]+\}+").unwrap());
282
283 let re = RE.deref();
284 let fname: Cow<str> = if let Some(env) = env {
286 let mut fname = fname.to_owned();
287
288 let mut replace = HashSet::new();
289 for cap in re.captures_iter(&fname) {
290 if cap[0] != fname {
291 replace.insert(cap[0].to_owned());
292 }
293 }
294
295 for model in replace.iter() {
297 let local_symbol = &model[1..model.len() - 1]; let local_value = match env
299 .symbols()
300 .any_value(local_symbol)
301 .map(|vl| vl.map(|vl| vl.value()))
302 {
303 Ok(Some(Value::String(s))) => s.to_string(),
304 Ok(Some(Value::Expr(e))) => e.to_string(),
305 Ok(Some(Value::Counter(e))) => e.to_string(),
306 Ok(Some(unkn)) => {
307 unimplemented!("{:?}", unkn)
308 },
309 Ok(None) => {
310 return Err(Either::Left(AssemblerError::UnknownSymbol {
311 symbol: model.into(),
312 closest: env.symbols().closest_symbol(model, SymbolFor::Any).unwrap()
313 }));
314 },
315 Err(e) => return Err(Either::Left(e.into()))
316 };
317 fname = fname.replace(model, &local_value);
318 }
319 Cow::Owned(fname)
320 }
321 else {
322 Cow::Borrowed(fname)
323 };
324
325 let fname: &str = fname.borrow();
326
327 if fname.starts_with("inner://") {
329 return Ok(Utf8Path::new(fname).into());
330 }
331
332 let fname = Utf8Path::new(fname);
333
334 if fname.is_file() {
336 return Ok(fname.into());
337 }
338 does_not_exists.push(fname.as_str().to_owned());
339
340 if let Some(env) = env.as_ref() {
342 if let Some(search) = env.get_current_working_directory() {
343 let current_path = search.join(fname);
344 if current_path.is_file() {
345 return Ok(current_path.try_into().unwrap());
346 }
347 else {
348 does_not_exists.push(current_path.to_string());
349 }
350 }
351 }
352
353 {
355 for search in &self.search_path {
357 assert!(Utf8Path::new(&search).is_dir());
358 let current_path = search.join(fname);
359
360 if current_path.is_file() {
361 return Ok(current_path);
362 }
363 else {
364 let glob = GlobBuilder::new(current_path.as_path().as_str())
365 .case_insensitive(true)
366 .literal_separator(true)
367 .build()
368 .unwrap();
369 let matcher = glob.compile_matcher();
370
371 for entry in std::fs::read_dir(search).unwrap() {
372 let entry = entry.unwrap();
373 let path = entry.path();
374 if matcher.is_match(&path) {
375 return Ok(path.try_into().unwrap());
376 }
377 }
378
379 does_not_exists.push(current_path.as_str().to_owned());
380 }
381 }
382 }
383
384 Err(Either::Right(does_not_exists))
386 }
387
388 pub fn set_flavor(&mut self, flavor: AssemblerFlavor) -> &mut Self {
389 self.assembler_flavor = flavor;
390 self
391 }
392
393 #[inline(always)]
394 pub fn is_orgams(&self) -> bool {
395 self.assembler_flavor == AssemblerFlavor::Orgams
396 }
397}
398#[derive(Debug)]
401pub struct ParserContext {
402 pub state: ParsingState,
405 pub current_filename: Option<Utf8PathBuf>,
407 pub context_name: Option<String>,
409 pub options: ParserOptions,
410 pub source: &'static BStr,
412 pub line_col_lut: RwLock<Option<LineColLookup<'static>>>
413}
414
415impl Eq for ParserContext {}
416
417impl PartialEq for ParserContext {
418 #[inline]
419 fn eq(&self, other: &Self) -> bool {
420 self.state == other.state
421 && self.current_filename == other.current_filename
422 && self.context_name == other.context_name
423 && self.source == other.source
424 && self.options == other.options
425 }
426}
427
428impl Clone for ParserContext {
429 fn clone(&self) -> Self {
430 panic!();
431
432 Self {
433 current_filename: self.current_filename.clone(),
434 context_name: self.context_name.clone(),
435 state: self.state,
436 source: self.source,
437 options: self.options.clone(),
438 line_col_lut: RwLock::default() }
440 }
441}
442
443impl ParserContext {
460 pub fn clone_with_state(&self, state: ParsingState) -> Self {
461 Self {
462 current_filename: self.current_filename.clone(),
463 context_name: self.context_name.clone(),
464 source: self.source,
465 options: self.options.clone(),
466 line_col_lut: Default::default(), state
468 }
469 }
470}
471
472#[allow(missing_docs)]
473impl ParserContext {
474 #[inline]
475 pub fn context_name(&self) -> Option<&str> {
476 self.context_name.as_deref()
477 }
478
479 #[inline]
480 pub fn filename(&self) -> Option<&Utf8Path> {
481 self.current_filename.as_ref().map(|p| p.as_path())
482 }
483
484 #[inline]
486 pub fn build_span<S: ?Sized + AsRef<[u8]>>(&self, src: &S) -> Z80Span {
487 Z80Span::new_extra(src, self)
488 }
489
490 #[inline]
492 pub fn set_current_filename<P: Into<Utf8PathBuf>>(&mut self, file: P) {
493 let file = file.into();
494 self.current_filename = Some(
495 file.canonicalize()
496 .map(|p| Utf8PathBuf::from_path_buf(p).unwrap())
497 .unwrap_or(file)
498 )
499 }
500
501 #[inline]
502 pub fn remove_filename(&mut self) {
503 self.current_filename = None;
504 }
505
506 #[inline]
507 pub fn set_context_name(&mut self, name: &str) {
508 self.context_name = Some(name.to_owned());
509 }
510
511 #[inline]
512 pub fn complete_source(&self) -> &str {
513 unsafe { std::mem::transmute(self.source.deref()) }
514 }
515
516 #[inline(always)]
517 pub fn options(&self) -> &ParserOptions {
518 &self.options
519 }
520
521 #[inline]
522 pub fn state(&self) -> &ParsingState {
523 &self.state
524 }
525
526 #[inline]
527 pub fn relative_line_and_column(&self, offset: usize) -> (usize, usize) {
528 if self.line_col_lut.read().unwrap().is_none() {
529 let src: &'static str = unsafe { std::mem::transmute(self.source.deref()) };
530
531 self.line_col_lut
532 .write()
533 .unwrap()
534 .replace(LineColLookup::new(src));
535 }
536
537 let res = self
538 .line_col_lut
539 .read()
540 .unwrap()
541 .as_ref()
542 .unwrap()
543 .get(offset);
544
545 res
546 }
547}
548#[cfg(test)]
557mod test_super {
558 use super::*;
559
560 #[test]
561 fn test_function_state() {
562 assert!(Token::Return(0.into()).is_accepted(&ParsingState::FunctionLimited));
563 }
564 #[test]
565
566 fn test_normal_state() {
567 assert!(!Token::Return(0.into()).is_accepted(&ParsingState::Standard));
568 }
569}