cpclib_asm/assembler/
processed_token.rs

1use std::borrow::{Borrow, Cow};
2use std::cell::OnceCell;
3use std::collections::{BTreeMap, HashMap};
4use std::fmt::{Debug, Formatter};
5use std::ops::Deref;
6use std::sync::Arc;
7
8use cpclib_common::camino::Utf8PathBuf;
9use cpclib_common::itertools::Itertools;
10#[cfg(all(not(target_arch = "wasm32"), feature = "rayon"))]
11use cpclib_common::rayon::prelude::*;
12use cpclib_disc::amsdos::AmsdosFileType;
13use cpclib_tokens::symbols::{SymbolFor, SymbolsTableTrait};
14use cpclib_tokens::{
15    AssemblerControlCommand, AssemblerFlavor, BinaryTransformation, ExprElement, ListingElement,
16    MacroParamElement, TestKindElement, ToSimpleToken, Token
17};
18use ouroboros::*;
19
20use super::AssemblerWarning;
21use super::control::ControlOutputStore;
22use super::file::{get_filename_to_read, load_file, read_source};
23use super::function::{Function, FunctionBuilder};
24use super::r#macro::Expandable;
25use crate::implementation::expression::ExprEvaluationExt;
26use crate::implementation::instructions::Cruncher;
27use crate::preamble::{LocatedListing, MayHaveSpan, Z80Span};
28use crate::progress::{self, Progress};
29use crate::{AssemblerError, Env, LocatedToken, Visited, r#macro, parse_z80_with_context_builder};
30
31/// Tokens are read only elements extracted from the parser
32/// ProcessedTokens allow to maintain their state during assembling
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct ProcessedToken<'token, T: Visited + Debug + ListingElement + Sync> {
35    /// The token being processed by the assembler
36    token: &'token T,
37    state: Option<ProcessedTokenState<'token, T>>
38}
39
40/// Specific state to maintain for the current token
41#[derive(Debug, Clone, PartialEq, Eq)]
42enum ProcessedTokenState<'token, T: Visited + ListingElement + Debug + Sync> {
43    RestrictedAssemblingEnvironment {
44        listing: SimpleListingState<'token, T>,
45        commands: Option<ControlOutputStore>
46    },
47    Confined(SimpleListingState<'token, T>),
48    CrunchedSection {
49        /// The token to assemble
50        listing: SimpleListingState<'token, T>,
51        // The bytes previously generated - to be compared to avoid a second slow assembling
52        previous_bytes: Option<Vec<u8>>,
53        // The previous compressed flux - to reuse if needed
54        previous_compressed_bytes: Option<Vec<u8>>
55    },
56    For(SimpleListingState<'token, T>),
57    FunctionDefinition(FunctionDefinitionState),
58    /// If state encodes previous choice
59    If(IfState<'token, T>),
60    /// Included file must read at some moment the file to handle
61    Include(IncludeState),
62    /// Included binary needs to be read
63    /// TODO add parameters
64    Incbin(IncbinState),
65
66    Iterate(SimpleListingState<'token, T>),
67    MacroCallOrBuildStruct(ExpandState),
68    Module(SimpleListingState<'token, T>),
69    RepeatToken(
70        SingleTokenState<'token, T>,
71        &'token <T as ListingElement>::Expr
72    ),
73    Repeat(SimpleListingState<'token, T>),
74    RepeatUntil(SimpleListingState<'token, T>),
75    While(SimpleListingState<'token, T>),
76    Rorg(SimpleListingState<'token, T>),
77    Switch(SwitchState<'token, T>),
78    Warning(Box<ProcessedToken<'token, T>>)
79}
80
81#[derive(PartialEq, Eq, Clone, Debug, Default)]
82struct IncbinState {
83    contents: BTreeMap<Utf8PathBuf, Vec<u8>>
84}
85
86#[derive(PartialEq, Eq, Clone, Debug)]
87struct SingleTokenState<'token, T: Visited + ListingElement + Debug + Sync> {
88    token: Box<ProcessedToken<'token, T>>
89}
90
91#[derive(PartialEq, Eq, Clone)]
92struct SimpleListingState<'token, T: Visited + ListingElement + Debug + Sync> {
93    processed_tokens: Vec<ProcessedToken<'token, T>>,
94    span: Option<Z80Span>
95}
96
97impl<T: Visited + ListingElement + Debug + Sync> Debug for SimpleListingState<'_, T> {
98    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
99        write!(fmt, "SimpleListingState")
100    }
101}
102
103impl<'token, T: Visited + ListingElement + Debug + Sync + MayHaveSpan> SimpleListingState<'token, T>
104where <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt
105{
106    fn build(
107        tokens: &'token [T],
108        span: Option<Z80Span>,
109        env: &mut Env
110    ) -> Result<Self, AssemblerError> {
111        Ok(Self {
112            processed_tokens: build_processed_tokens_list(tokens, env)?,
113            span
114        })
115    }
116
117    fn tokens_mut(&mut self) -> &mut [ProcessedToken<'token, T>] {
118        &mut self.processed_tokens
119    }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123struct FunctionDefinitionState(Option<Arc<Function>>);
124
125#[derive(PartialEq, Eq, Clone, Debug)]
126struct SwitchState<'token, T: Visited + ListingElement + Debug + Sync> {
127    cases: Vec<SimpleListingState<'token, T>>,
128    default: Option<SimpleListingState<'token, T>>
129}
130
131#[derive(PartialEq, Eq, Clone, Debug, Default)]
132struct IncludeState(BTreeMap<Utf8PathBuf, IncludeStateInner>);
133
134impl IncludeState {
135    /// By constructon fname exists and is correct
136    fn retreive_listing(
137        &mut self,
138        env: &mut Env,
139        fname: &Utf8PathBuf
140    ) -> Result<&mut IncludeStateInner, AssemblerError> {
141        if cfg!(target_arch = "wasm32") {
142            return Err(AssemblerError::AssemblingError {
143                msg: "INCLUDE-like directives are not allowed in a web-based assembling."
144                    .to_owned()
145            });
146        }
147
148        // Build the state if needed / retreive it otherwise
149        let state: &mut IncludeStateInner = if !self.0.contains_key(fname) {
150            let content = read_source(fname.clone(), env.options().parse_options())?;
151
152            if env.options().show_progress() {
153                Progress::progress().add_parse(progress::normalize(fname));
154            }
155
156            let builder = env
157                .options()
158                .clone()
159                .context_builder()
160                .set_current_filename(fname.clone());
161
162            let listing = parse_z80_with_context_builder(content, builder)?;
163
164            // Remove the progression
165            if env.options().show_progress() {
166                Progress::progress().remove_parse(progress::normalize(fname));
167            }
168
169            let include_state = IncludeStateInnerTryBuilder {
170                listing,
171                processed_tokens_builder: |listing: &LocatedListing| {
172                    build_processed_tokens_list(listing.as_slice(), env)
173                }
174            }
175            .try_build()?;
176
177            self.0.try_insert(fname.clone(), include_state).unwrap()
178        }
179        else {
180            self.0.get_mut(fname).unwrap()
181        };
182
183        // handle the listing
184        env.included_marks_add(fname.clone());
185
186        Ok(state)
187    }
188
189    fn handle(
190        &mut self,
191        env: &mut Env,
192        fname: &str,
193        namespace: Option<&str>,
194        once: bool
195    ) -> Result<(), AssemblerError> {
196        let fname = get_filename_to_read(fname, env.options().parse_options(), Some(env))?;
197
198        let need_to_include = !once || !env.included_marks_includes(&fname);
199
200        // Process the inclusion only if necessary
201        if need_to_include {
202            // most of the time, file has been loaded
203            let state = self.retreive_listing(env, &fname)?;
204
205            // handle module if necessary
206            if let Some(namespace) = namespace {
207                env.enter_namespace(namespace)?;
208                // TODO handle the locating of error
209                //.map_err(|e| e.locate(span.clone()))?;
210            }
211
212            // Visit the included listing
213            env.enter_current_working_file(fname);
214            let res = state.with_processed_tokens_mut(|tokens| {
215                let tokens: &mut [ProcessedToken<'_, LocatedToken>] = &mut tokens[..];
216                visit_processed_tokens::<'_, LocatedToken>(tokens, env)
217            });
218            env.leave_current_working_file();
219            res?;
220
221            // Remove module if necessary
222            if namespace.is_some() {
223                env.leave_namespace()?;
224                //.map_err(|e| e.locate(span.clone()))?;
225            }
226
227            Ok(())
228        }
229        else {
230            Ok(())
231        }
232    }
233}
234
235#[self_referencing]
236struct IncludeStateInner {
237    listing: LocatedListing,
238    #[borrows(listing)]
239    #[covariant]
240    processed_tokens: Vec<ProcessedToken<'this, LocatedToken>>
241}
242
243impl Clone for IncludeStateInner {
244    fn clone(&self) -> Self {
245        todo!()
246    }
247}
248
249impl PartialEq for IncludeStateInner {
250    fn eq(&self, other: &Self) -> bool {
251        self.with_listing(|l1| other.with_listing(|l2| l1.eq(l2)))
252    }
253}
254
255impl Eq for IncludeStateInner {}
256
257impl Debug for IncludeStateInner {
258    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
259        write!(fmt, "IncludeState")
260    }
261}
262
263#[self_referencing]
264struct ExpandState {
265    listing: LocatedListing,
266    #[borrows(listing)]
267    #[covariant]
268    processed_tokens: Vec<ProcessedToken<'this, LocatedToken>>
269}
270
271impl PartialEq for ExpandState {
272    fn eq(&self, other: &Self) -> bool {
273        self.with_listing(|l1| other.with_listing(|l2| l1.eq(l2)))
274    }
275}
276
277impl Eq for ExpandState {}
278
279impl Clone for ExpandState {
280    fn clone(&self) -> Self {
281        todo!()
282    }
283}
284
285impl Debug for ExpandState {
286    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
287        write!(fmt, "ExpandState")
288    }
289}
290
291/// Store for each branch (if passed at some point) the test result and the listing
292#[derive(Debug, Clone, PartialEq, Eq)]
293struct IfState<'token, T: Visited + Debug + ListingElement + Sync> {
294    // The token that contains the tests and listings
295    token: &'token T,
296    if_token_adr_to_used_decision: std::collections::HashMap<usize, bool>,
297    if_token_adr_to_unused_decision: std::collections::HashMap<usize, bool>,
298    // Processed listing build on demand
299    tests_listing: HashMap<usize, Vec<ProcessedToken<'token, T>>>,
300    // else listing build on demand
301    else_listing: Option<Vec<ProcessedToken<'token, T>>>
302}
303
304impl<'token, T: Visited + Debug + ListingElement + Sync + MayHaveSpan> IfState<'token, T>
305where <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt
306{
307    fn new(token: &'token T) -> Self {
308        Self {
309            token,
310            if_token_adr_to_used_decision: Default::default(),
311            if_token_adr_to_unused_decision: Default::default(),
312            tests_listing: Default::default(),
313            else_listing: None
314        }
315    }
316
317    fn choose_listing_to_assemble(
318        &mut self,
319        env: &mut Env
320    ) -> Result<Option<&mut [ProcessedToken<'token, T>]>, AssemblerError>
321    where
322        <<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr:
323            ExprEvaluationExt,
324        <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt
325    {
326        let mut selected_idx = None;
327        let mut request_additional_pass = false;
328        use cpclib_tokens::ExprResult;
329        let FLAG_FAILURE: OnceCell<ExprResult> = OnceCell::new();
330        let FLAG_FAILURE =
331            FLAG_FAILURE.get_or_init(|| "__BASM_INNER_TEST_FAILURE__".to_owned().into());
332
333        for idx in 0..self.token.if_nb_tests() {
334            let (test, _) = self.token.if_test(idx);
335            let token_adr = test as *const _ as usize;
336
337            // Expression must be true
338            // IF
339            if test.is_true_test() {
340                let exp = test.expr_unchecked();
341                // Expression must be true
342                let value = env
343                    .resolve_expr_may_fail_in_first_pass_with_default(exp, FLAG_FAILURE.clone())?;
344                if &value == FLAG_FAILURE {
345                    // no code is executed if the test cannot be done
346                    return Ok(None);
347                }
348                if value.bool()? {
349                    selected_idx = Some(idx);
350                    break;
351                }
352            }
353            // Expression must be false
354            // IFNOT
355            else if test.is_false_test() {
356                let exp = test.expr_unchecked();
357                let value = env
358                    .resolve_expr_may_fail_in_first_pass_with_default(exp, FLAG_FAILURE.clone())?;
359                if &value == FLAG_FAILURE {
360                    // no code is executed if the test cannot be done
361                    return Ok(None);
362                }
363                if !value.bool()? {
364                    selected_idx = Some(idx);
365                    break;
366                }
367            }
368            // IFUSED
369            else if test.is_label_used_test() {
370                let label = test.label_unchecked();
371                let decision = env.symbols().is_used(label);
372
373                // Add an extra pass if the test differ
374                if let Some(res) = self.if_token_adr_to_used_decision.get(&token_adr) {
375                    if *res != decision {
376                        request_additional_pass = true;
377                    }
378                }
379
380                // replace the previously stored value
381                self.if_token_adr_to_used_decision
382                    .insert(token_adr, decision);
383
384                if decision {
385                    selected_idx = Some(idx);
386                    break;
387                }
388            }
389            // IFNUSED
390            else if test.is_label_nused_test() {
391                let label = test.label_unchecked();
392                let decision = !env.symbols().is_used(label);
393
394                // Add an extra pass if the test differ
395                if let Some(res) = self.if_token_adr_to_unused_decision.get(&token_adr) {
396                    if *res != decision {
397                        request_additional_pass = true;
398                    }
399                }
400
401                // replace the previously stored value
402                self.if_token_adr_to_unused_decision
403                    .insert(token_adr, decision);
404
405                if decision {
406                    selected_idx = Some(idx);
407                    break;
408                }
409            }
410            // Label must exist at this specific moment
411            // IFDEF
412            else if test.is_label_exists_test() {
413                let label = test.label_unchecked();
414                if env.symbols().symbol_exist_in_current_pass(label)? {
415                    selected_idx = Some(idx);
416                    break;
417                }
418            }
419            // IFNDEF
420            // Label must not exist at this specific moment
421            else {
422                let label = test.label_unchecked();
423                if !env.symbols().symbol_exist_in_current_pass(label)? {
424                    selected_idx = Some(idx);
425                    break;
426                }
427            }
428        }
429
430        let selected_listing = match selected_idx {
431            Some(selected_idx) => {
432                // build the listing if never done
433                if self.tests_listing.get(&selected_idx).is_none() {
434                    let listing = self.token.if_test(selected_idx).1;
435                    let listing = build_processed_tokens_list(listing, env)?;
436                    self.tests_listing.insert(selected_idx, listing);
437                }
438                self.tests_listing.get_mut(&selected_idx)
439            },
440            None => {
441                // build else listing if needed
442                if self.else_listing.is_none() && self.token.if_else().is_some() {
443                    let listing = self.token.if_else();
444                    self.else_listing = listing
445                        .map(|listing| build_processed_tokens_list(listing, env))
446                        .transpose()?;
447                }
448                self.else_listing.as_mut()
449            }
450        };
451
452        // update env to request an additional pass
453        let request_additional_pass =
454            *env.request_additional_pass.read().unwrap().deref() | request_additional_pass;
455        *env.request_additional_pass.write().unwrap() = request_additional_pass;
456
457        Ok(selected_listing.map(|l| l.as_mut_slice()))
458    }
459}
460
461impl<T: Visited + Debug + ListingElement + Sync + ToSimpleToken> ToSimpleToken
462    for ProcessedToken<'_, T>
463where <T as ListingElement>::Expr: ExprEvaluationExt
464{
465    fn as_simple_token(&self) -> Cow<Token> {
466        self.token.as_simple_token()
467    }
468}
469
470pub type AssemblerInfo = AssemblerError;
471
472/// Build a processed token based on the base token
473pub fn build_processed_token<'token, T: Visited + Debug + Sync + ListingElement + MayHaveSpan>(
474    token: &'token T,
475    env: &mut Env
476) -> Result<ProcessedToken<'token, T>, AssemblerError>
477where
478    <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt
479{
480    let state = if token.is_confined() {
481        Some(ProcessedTokenState::Confined(SimpleListingState {
482            processed_tokens: build_processed_tokens_list(token.confined_listing(), env)?,
483            span: token.possible_span().cloned()
484        }))
485    }
486    else if token.is_if() {
487        let state = IfState::new(token);
488        Some(ProcessedTokenState::If(state))
489    }
490    else if token.is_include() {
491        // we cannot use the real method onf IncludeState because it modifies env and here wa cannot
492        let fname = token.include_fname();
493        let fname = env.build_fname(fname)?;
494        let options = env.options().parse_options();
495        match get_filename_to_read(fname, options, Some(env)) {
496            Ok(fname) => {
497                match read_source(fname.clone(), options) {
498                    Ok(content) => {
499                        let ctx_builder = options
500                            .clone()
501                            .context_builder()
502                            .set_current_filename(fname.clone());
503
504                        match parse_z80_with_context_builder(content, ctx_builder) {
505                            Ok(listing) => {
506                                // Filename has already been added
507                                if token.include_is_standard_include()
508                                    && env.options().show_progress()
509                                {
510                                    Progress::progress().remove_parse(progress::normalize(&fname));
511                                }
512
513                                let include_state = IncludeStateInnerTryBuilder {
514                                    listing,
515                                    processed_tokens_builder: |listing: &LocatedListing| {
516                                        build_processed_tokens_list(listing, env)
517                                    }
518                                }
519                                .try_build()?;
520
521                                let mut map = BTreeMap::new();
522                                map.insert(fname, include_state);
523
524                                Some(ProcessedTokenState::Include(IncludeState(map)))
525                            },
526                            Err(_) => Some(ProcessedTokenState::Include(Default::default()))
527                        }
528                    },
529                    Err(_) => Some(ProcessedTokenState::Include(Default::default()))
530                }
531            },
532            Err(_) => Some(ProcessedTokenState::Include(Default::default())) /* we were unable to get the filename with the provided information */
533        }
534    }
535    else if token.is_incbin() {
536        Some(ProcessedTokenState::Incbin(Default::default()))
537    }
538    else if token.is_crunched_section() {
539        Some(ProcessedTokenState::CrunchedSection {
540            listing: SimpleListingState {
541                processed_tokens: build_processed_tokens_list(
542                    token.crunched_section_listing(),
543                    env
544                )?,
545                span: token.possible_span().cloned()
546            },
547            previous_bytes: None,
548            previous_compressed_bytes: None
549        })
550    }
551    else if token.is_for() {
552        Some(ProcessedTokenState::For(SimpleListingState {
553            processed_tokens: build_processed_tokens_list(token.for_listing(), env)?,
554            span: token.possible_span().cloned()
555        }))
556    }
557    else if token.is_function_definition() {
558        Some(ProcessedTokenState::FunctionDefinition(
559            FunctionDefinitionState(None)
560        ))
561    }
562    else if token.is_iterate() {
563        Some(ProcessedTokenState::Iterate(SimpleListingState {
564            processed_tokens: build_processed_tokens_list(token.iterate_listing(), env)?,
565            span: token.possible_span().cloned()
566        }))
567    }
568    else if token.is_module() {
569        Some(ProcessedTokenState::Module(SimpleListingState {
570            processed_tokens: build_processed_tokens_list(token.module_listing(), env)?,
571            span: token.possible_span().cloned()
572        }))
573    }
574    else if token.is_repeat() {
575        Some(ProcessedTokenState::Repeat(SimpleListingState {
576            processed_tokens: build_processed_tokens_list(token.repeat_listing(), env)?,
577            span: token.possible_span().cloned()
578        }))
579    }
580    else if token.is_repeat_token() {
581        Some(ProcessedTokenState::RepeatToken(
582            SingleTokenState {
583                token: Box::new(build_processed_token(token.repeat_token(), env)?)
584            },
585            token.repeat_count()
586        ))
587    }
588    else if token.is_assembler_control()
589        && token
590            .assembler_control_command()
591            .is_restricted_assembling_environment()
592    {
593        assert!(
594            token.assembler_control_get_max_passes().is_some(),
595            "We currently only support a maximum number of passes, so it as to be provided ..."
596        );
597
598        let passes = match token.assembler_control_get_max_passes() {
599            Some(passes) => Some(env.resolve_expr_must_never_fail(passes)?.int()? as u8),
600            None => None
601        };
602
603        let tokens = token.assembler_control_get_listing();
604        Some(ProcessedTokenState::RestrictedAssemblingEnvironment {
605            listing: SimpleListingState {
606                processed_tokens: build_processed_tokens_list(tokens, env)?,
607                span: token.possible_span().cloned()
608            },
609            commands: Some(ControlOutputStore::with_passes(passes.unwrap()))
610        })
611    }
612    else if token.is_repeat_until() {
613        Some(ProcessedTokenState::RepeatUntil(SimpleListingState {
614            processed_tokens: build_processed_tokens_list(token.repeat_until_listing(), env)?,
615            span: token.possible_span().cloned()
616        }))
617    }
618    else if token.is_rorg() {
619        Some(ProcessedTokenState::Rorg(SimpleListingState {
620            processed_tokens: build_processed_tokens_list(token.rorg_listing(), env)?,
621            span: token.possible_span().cloned()
622        }))
623    }
624    else if token.is_switch() {
625        // todo setup properly the spans
626        Some(ProcessedTokenState::Switch(SwitchState {
627            cases: token
628                .switch_cases()
629                .map(|(_v, l, _b)| {
630                    SimpleListingState::build(l, token.possible_span().cloned(), env)
631                })
632                .collect::<Result<Vec<_>, _>>()?,
633
634            default: token
635                .switch_default()
636                .map(|l| SimpleListingState::build(l, token.possible_span().cloned(), env))
637                .transpose()?
638        }))
639    }
640    else if token.is_warning() {
641        Some(ProcessedTokenState::Warning(Box::new(
642            build_processed_token(token.warning_token(), env)?
643        )))
644    }
645    else if token.is_while() {
646        Some(ProcessedTokenState::While(SimpleListingState {
647            processed_tokens: build_processed_tokens_list(token.while_listing(), env)?,
648            span: token.possible_span().cloned()
649        }))
650    }
651    else if token.is_call_macro_or_build_struct() {
652        // one day, we may whish to maintain a state
653        None
654    }
655    else {
656        None
657    };
658
659    Ok(ProcessedToken { token, state })
660}
661
662pub fn build_processed_tokens_list<
663    'token,
664    T: Visited + Debug + Sync + ListingElement + MayHaveSpan
665>(
666    tokens: &'token [T],
667    env: &mut Env
668) -> Result<Vec<ProcessedToken<'token, T>>, AssemblerError>
669where
670    <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt
671{
672    let show_progress = env.options().parse_options().show_progress;
673    if show_progress {
674        // // temporarily deactivate parallel processing while i have not found a way to compile it
675        // #[cfg(not(target_arch = "wasm32"))]
676        // let iter = tokens.par_iter();
677        // #[cfg(target_arch = "wasm32")]
678        let iter = tokens.iter();
679
680        // get filename of files that will be read in parallel
681        let include_fnames = iter
682            .filter(|t| t.include_is_standard_include())
683            .flat_map(|t| {
684                let fname = t.include_fname();
685                let fname = env.build_fname(fname)?;
686                get_filename_to_read(fname, env.options().parse_options(), Some(env))
687            })
688            .collect::<Vec<_>>();
689        let include_fnames = include_fnames.iter().map(|t| progress::normalize(t));
690
691        // inform the progress bar
692        // add all fnames in one time
693        Progress::progress().add_parses(include_fnames);
694    }
695
696    // the files will be read here while token are built
697    // this is really important to keep this place parallelized
698    #[cfg(all(not(target_arch = "wasm32"), feature = "rayon"))]
699    let iter = tokens.par_iter();
700    #[cfg(any(target_arch = "wasm32", not(feature = "rayon")))]
701    let iter = tokens.iter();
702    iter.map(|t| build_processed_token(t, env))
703        .collect::<Result<Vec<_>, _>>()
704}
705
706/// Visit all the tokens until an error occurs
707pub fn visit_processed_tokens<'token, T: Visited + Debug + ListingElement + Sync + MayHaveSpan>(
708    tokens: &mut [ProcessedToken<'token, T>],
709    env: &mut Env
710) -> Result<(), AssemblerError>
711where
712    <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
713    <<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr: ExprEvaluationExt,
714    ProcessedToken<'token, T>: FunctionBuilder
715{
716    // Ignore if function has already returned (mainly a workaround for switch case)
717    if env.return_value.is_some() {
718        return Ok(());
719    }
720
721    let options = env.options();
722
723    if options.show_progress() {
724        // setup the amount of tokens that will be processed
725        Progress::progress().add_expected_to_pass(tokens.len() as _);
726        for chunk in &tokens.iter_mut().chunks(64) {
727            let mut visited = 0;
728            for token in chunk {
729                token.visited(env)?;
730                visited += 1;
731            }
732
733            Progress::progress().add_visited_to_pass(visited);
734        }
735    }
736    else {
737        // normal iteration
738        for token in tokens.iter_mut() {
739            token.visited(env)?;
740        }
741    }
742
743    env.cleanup_warnings();
744
745    Ok(())
746}
747
748impl<T: Visited + Debug + ListingElement + Sync + MayHaveSpan> MayHaveSpan for ProcessedToken<'_, T>
749where <T as ListingElement>::Expr: ExprEvaluationExt
750{
751    fn possible_span(&self) -> Option<&Z80Span> {
752        self.token.possible_span()
753    }
754
755    fn span(&self) -> &Z80Span {
756        self.token.span()
757    }
758
759    fn has_span(&self) -> bool {
760        self.token.has_span()
761    }
762}
763
764impl<T: Visited + Debug + ListingElement + Sync + MayHaveSpan> ProcessedToken<'_, T>
765where <T as ListingElement>::Expr: ExprEvaluationExt
766{
767    /// Generate the tokens needed for the macro or the struct
768    #[inline]
769    pub fn update_macro_or_struct_state(&mut self, env: &mut Env) -> Result<(), AssemblerError>
770    where <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt {
771        let caller = self.token;
772        let name = caller.macro_call_name();
773        let parameters = caller.macro_call_arguments();
774
775        let listing = {
776            // Retreive the macro or structure definition
777            let r#macro = env
778                .symbols()
779                .macro_value(name)?
780                .map(|m| r#macro::MacroWithArgs::build(m, parameters))
781                .transpose()?;
782            let r#struct = if r#macro.is_none() {
783                env.symbols()
784                    .struct_value(name)?
785                    .map(|s| r#macro::StructWithArgs::build(s, parameters))
786                    .transpose()?
787            }
788            else {
789                None
790            };
791
792            // Leave now if it corresponds to no macro or struct
793            if r#macro.is_none() && r#struct.is_none() {
794                let e = AssemblerError::UnknownMacro {
795                    symbol: name.into(),
796                    closest: env.symbols().closest_symbol(name, SymbolFor::Macro)?
797                };
798                return match self.possible_span() {
799                    Some(span) => {
800                        Err(AssemblerError::RelocatedError {
801                            error: e.into(),
802                            span: span.clone()
803                        })
804                    },
805                    None => Err(e)
806                };
807            }
808
809            // get the generated code
810            // TODO handle some errors there
811            let (source, code, flavor) = if let Some(r#macro) = &r#macro {
812                let source = r#macro.source();
813                let flavor = r#macro.flavor();
814                let code = r#macro.expand(env)?;
815                (source, code, flavor)
816            }
817            else {
818                let r#struct = r#struct.as_ref().unwrap();
819                let mut parameters = parameters.to_vec();
820                parameters.resize(r#struct.r#struct().nb_args(), T::MacroParam::empty());
821                (
822                    r#struct.source(),
823                    r#struct.expand(env)?,
824                    AssemblerFlavor::Basm
825                )
826            };
827
828            // Tokenize with the same parsing  parameters and context when possible
829            let listing = match self.token.possible_span() {
830                Some(span) => {
831                    use crate::ParserContextBuilder;
832                    let ctx_builder = ParserContextBuilder::default() // nothing is specified
833                        //                    from(span.state.clone())
834                        .set_state(span.state.state)
835                        .set_options(span.state.options.clone())
836                        .set_context_name(format!(
837                            "{}:{}:{} > {} {}:",
838                            source.map(|s| s.fname()).unwrap_or_else(|| "???"),
839                            source.map(|s| s.line()).unwrap_or(0),
840                            source.map(|s| s.column()).unwrap_or(0),
841                            if r#macro.is_some() { "MACRO" } else { "STRUCT" },
842                            name,
843                        ));
844                    parse_z80_with_context_builder(code, ctx_builder)?
845                },
846                _ => {
847                    use crate::parse_z80_str;
848                    parse_z80_str(&code)?
849                }
850            };
851            listing
852        };
853
854        let expand_state = ExpandStateTryBuilder {
855            listing,
856            processed_tokens_builder: |listing: &LocatedListing| {
857                build_processed_tokens_list(listing, env)
858            }
859        }
860        .try_build()?;
861
862        self.state = Some(ProcessedTokenState::MacroCallOrBuildStruct(expand_state));
863
864        Ok(())
865    }
866}
867
868impl<'token, T: Visited + Debug + ListingElement + Sync + MayHaveSpan> ProcessedToken<'token, T>
869where
870    <T as cpclib_tokens::ListingElement>::Expr: ExprEvaluationExt,
871    <<T as cpclib_tokens::ListingElement>::TestKind as TestKindElement>::Expr: ExprEvaluationExt,
872    ProcessedToken<'token, T>: FunctionBuilder
873{
874    /// Due to the state management, the signature requires mutability
875    pub fn visited(&mut self, env: &mut Env) -> Result<(), AssemblerError> {
876        let possible_span = self.possible_span().cloned();
877
878        let mut really_does_the_job = move |possible_span: Option<&Z80Span>| {
879            let deferred = self.token.defer_listing_output();
880            if !deferred {
881                // dbg!(&self.token, deferred);
882                let outer_token = unsafe {
883                    (self.token as *const T as *const LocatedToken)
884                        .as_ref()
885                        .unwrap()
886                };
887
888                env.handle_output_trigger(outer_token);
889            }
890
891            // Generate the code of a macro/struct
892            if self.token.is_call_macro_or_build_struct() {
893                self.update_macro_or_struct_state(env)?;
894            }
895
896            // Behavior based on the token
897            if self.token.is_macro_definition() {
898                // TODO really implement logic here
899                let name = self.token.macro_definition_name();
900                let arguments = self.token.macro_definition_arguments();
901                let code = self.token.macro_definition_code();
902                env.visit_macro_definition(
903                    name,
904                    &arguments,
905                    code,
906                    self.possible_span(),
907                    self.token.macro_flavor()
908                )
909            }
910            // Behavior based on the state (for ease of writting)
911            else {
912                // Handle the tokens depending on their specific state
913                match &mut self.state {
914                    Some(ProcessedTokenState::RestrictedAssemblingEnvironment {
915                        listing,
916                        commands
917                    }) => {
918                        let mut new_commands = commands.take().unwrap();
919
920                        if !new_commands.has_remaining_passes() {
921                            new_commands.execute(env)?;
922                        }
923                        else {
924                            // TODO move that code directly inside ControlOutputStore
925                            new_commands.new_pass();
926                            env.assembling_control_current_output_commands
927                                .push(new_commands);
928                            visit_processed_tokens(&mut listing.processed_tokens, env)?;
929                            new_commands = env
930                                .assembling_control_current_output_commands
931                                .pop()
932                                .unwrap();
933                        }
934                        commands.replace(new_commands);
935                        Ok(())
936                    },
937
938                    Some(ProcessedTokenState::Confined(SimpleListingState {
939                        processed_tokens,
940                        span
941                    })) => env.visit_confined(processed_tokens, span.as_ref()),
942                    Some(ProcessedTokenState::CrunchedSection {
943                        listing:
944                            SimpleListingState {
945                                processed_tokens,
946                                span
947                            },
948                        previous_bytes,
949                        previous_compressed_bytes
950                    }) => {
951                        env.visit_crunched_section(
952                            self.token.crunched_section_kind(),
953                            processed_tokens,
954                            previous_bytes,
955                            previous_compressed_bytes,
956                            span.as_ref()
957                        )
958                    },
959
960                    Some(ProcessedTokenState::For(SimpleListingState {
961                        processed_tokens,
962                        span
963                    })) => {
964                        env.visit_for(
965                            self.token.for_label(),
966                            self.token.for_start(),
967                            self.token.for_stop(),
968                            self.token.for_step(),
969                            processed_tokens,
970                            span.as_ref()
971                        )
972                    },
973
974                    Some(ProcessedTokenState::RepeatToken(state, count)) => {
975                        env.visit_repeat_token(&mut state.token, count)
976                    },
977
978                    Some(ProcessedTokenState::FunctionDefinition(FunctionDefinitionState(
979                        Some(_fun)
980                    ))) => {
981                        // TODO check if the funtion has already been defined during this pass
982                        Ok(())
983                    },
984                    Some(ProcessedTokenState::FunctionDefinition(FunctionDefinitionState(
985                        option
986                    ))) => {
987                        let name = self.token.function_definition_name();
988                        if !env.functions.contains_key(name) {
989                            let inner = self.token.function_definition_inner();
990                            let params = self.token.function_definition_params();
991
992                            let inner = build_processed_tokens_list(inner, env)?;
993                            let f =
994                                Arc::new(unsafe { FunctionBuilder::new(&name, &params, inner) }?);
995                            option.replace(f.clone());
996
997                            env.functions.insert(name.to_owned(), f);
998                        }
999                        else {
1000                            // TODO raise an error ?
1001                        }
1002                        Ok(())
1003                    },
1004
1005                    Some(ProcessedTokenState::Incbin(IncbinState { contents })) => {
1006                        if cfg!(target_arch = "wasm32") {
1007                            return Err(AssemblerError::AssemblingError { msg:
1008                                "INCBIN-like directives are not allowed in a web-based assembling.".to_owned()
1009                            });
1010                        }
1011
1012                        // Handle file loading
1013                        let fname = self.token.incbin_fname();
1014                        let fname = env.build_fname(fname)?;
1015                        let to_print_fname = &fname;
1016                        let fname =
1017                            get_filename_to_read(&fname, env.options().parse_options(), Some(env))?;
1018
1019                        // Add a warning when incbin is used on a possible assembly file
1020                        if let Some(extension) = fname.extension() {
1021                            match extension.to_ascii_uppercase().as_str() {
1022                                "ASM" | "Z80" => {
1023                                    let warning = format!(
1024                                        "{} seems to be a source code and not a binary file.",
1025                                        &fname
1026                                    );
1027                                    env.add_warning(dbg!(AssemblerWarning::AssemblingError {
1028                                        msg: warning
1029                                    }));
1030                                },
1031                                _ => {}
1032                            }
1033                        }
1034
1035                        // get the data for the given file
1036                        let data = if !contents.contains_key(&fname) {
1037                            // need to load the file
1038
1039                            let (data, header) =
1040                                load_file(fname.as_path(), env.options().parse_options())?;
1041
1042                            if let Some(header) = header {
1043                                let ams_fname = header
1044                                    .amsdos_filename()
1045                                    .map(|ams_fname| ams_fname.filename_with_user())
1046                                    .unwrap_or_else(|_| "<WRONG FILENAME>".to_owned());
1047                                let txt = match header.file_type() {
1048                                    Ok(AmsdosFileType::Binary) => {
1049                                        format! {"{to_print_fname}|{ams_fname} BINARY  L:0x{:x} X:0x{:x}", header.loading_address(), header.execution_address()}
1050                                    },
1051                                    Ok(AmsdosFileType::Protected) => {
1052                                        format! {"{to_print_fname}|{ams_fname} PROTECTED L:0x{:x} X:0x{:x}", header.loading_address(), header.execution_address()}
1053                                    },
1054                                    Ok(AmsdosFileType::Basic) => format!("{ams_fname} BASIC"),
1055                                    Err(_) => format!("{ams_fname} <WRONG FILETYPE>")
1056                                };
1057
1058                                let warning = AssemblerWarning::AssemblingError {
1059                                    msg: format!("Header has been removed for {txt}")
1060                                };
1061                                let warning = if let Some(span) = possible_span {
1062                                    warning.locate_warning(span.clone())
1063                                }
1064                                else {
1065                                    warning
1066                                };
1067
1068                                env.add_warning(warning)
1069                            }
1070
1071                            contents.try_insert(fname.clone(), data.into()).unwrap()
1072                        }
1073                        else {
1074                            contents.get(&fname).unwrap()
1075                        };
1076
1077                        let mut data = data.as_slice();
1078
1079                        // Extract the appropriate content to the file
1080                        let offset = self.token.incbin_offset();
1081                        let length = self.token.incbin_length();
1082                        let transformation = self.token.incbin_transformation();
1083
1084                        if let Some(offset) = offset {
1085                            let offset = env.resolve_expr_must_never_fail(offset)?.int()? as usize;
1086                            if offset >= data.len() {
1087                                return Err(AssemblerError::AssemblingError {
1088                                    msg: format!(
1089                                        "Unable to read {:?}. Only {} are available",
1090                                        self.token.incbin_fname(),
1091                                        data.len()
1092                                    )
1093                                });
1094                            }
1095                            data = &data[offset..];
1096                        }
1097
1098                        if let Some(length) = length {
1099                            let length = env.resolve_expr_must_never_fail(length)?.int()? as usize;
1100                            if data.len() < length {
1101                                return Err(AssemblerError::AssemblingError {
1102                                    msg: format!(
1103                                        "Unable to read {:?}. Only {} bytes are available ({} expected)",
1104                                        self.token.incbin_fname(),
1105                                        data.len(),
1106                                        length
1107                                    )
1108                                });
1109                            }
1110                            data = &data[..length];
1111                        }
1112
1113                        let data = match transformation {
1114                            BinaryTransformation::None => Cow::Borrowed(data),
1115
1116                            other => {
1117                                if data.is_empty() {
1118                                    return Err(AssemblerError::EmptyBinaryFile(
1119                                        self.token.incbin_fname().to_string()
1120                                    ));
1121                                }
1122
1123                                let crunch_type = other.crunch_type().unwrap();
1124                                Cow::Owned(crunch_type.crunch(data)?)
1125                            }
1126                        };
1127
1128                        env.visit_incbin(data.borrow())
1129                    },
1130
1131                    Some(ProcessedTokenState::Include(state)) => {
1132                        let fname = env.build_fname(self.token.include_fname())?;
1133
1134                        state.handle(
1135                            env,
1136                            &fname,
1137                            self.token.include_namespace(),
1138                            self.token.include_once()
1139                        )
1140                    },
1141
1142                    Some(ProcessedTokenState::If(if_state)) => {
1143                        let listing = if_state.choose_listing_to_assemble(env)?;
1144
1145                        if let Some(listing) = listing {
1146                            visit_processed_tokens(listing, env)?;
1147                        }
1148
1149                        Ok(())
1150                    },
1151
1152                    Some(ProcessedTokenState::Iterate(SimpleListingState {
1153                        processed_tokens,
1154                        span
1155                    })) => {
1156                        env.visit_iterate(
1157                            self.token.iterate_counter_name(),
1158                            self.token.iterate_values(),
1159                            processed_tokens,
1160                            span.as_ref()
1161                        )
1162                    },
1163
1164                    Some(ProcessedTokenState::MacroCallOrBuildStruct(state)) => {
1165                        let name = self.token.macro_call_name();
1166
1167                        env.inc_macro_seed();
1168                        let seed = env.macro_seed();
1169                        env.symbols_mut().push_seed(seed);
1170
1171                        // save the number of prints to patch the ones added by the macro
1172                        // to properly locate them
1173                        let nb_prints = env
1174                            .sna
1175                            .pages_info
1176                            .iter()
1177                            .map(|ti| ti.print_commands().len())
1178                            .collect_vec();
1179
1180                        state
1181                            .with_processed_tokens_mut(|listing| {
1182                                let tokens: &mut [ProcessedToken<'_, LocatedToken>] =
1183                                    &mut listing[..];
1184                                visit_processed_tokens::<'_, LocatedToken>(tokens, env)
1185                            })
1186                            .map_err(|e| {
1187                                let location = env
1188                                    .symbols()
1189                                    .any_value(name)
1190                                    .unwrap()
1191                                    .unwrap()
1192                                    .location()
1193                                    .cloned();
1194
1195                                let e = AssemblerError::MacroError {
1196                                    name: name.into(),
1197                                    root: Box::new(e),
1198                                    location
1199                                };
1200                                let caller_span = self.possible_span();
1201                                match caller_span {
1202                                    Some(span) => {
1203                                        AssemblerError::RelocatedError {
1204                                            error: e.into(),
1205                                            span: span.clone()
1206                                        }
1207                                    },
1208                                    None => e
1209                                }
1210                            })?;
1211
1212                        let caller_span = self.possible_span();
1213                        if let Some(span) = caller_span {
1214                            env.sna
1215                                .pages_info
1216                                .iter_mut()
1217                                .zip(nb_prints.into_iter())
1218                                .for_each(|(ti, count)| {
1219                                    ti.print_commands_mut()[count..]
1220                                        .iter_mut()
1221                                        .for_each(|cmd| cmd.relocate(span.clone()))
1222                                });
1223                        }
1224
1225                        env.symbols_mut().pop_seed();
1226
1227                        Ok(())
1228                    },
1229
1230                    Some(ProcessedTokenState::Module(SimpleListingState {
1231                        processed_tokens,
1232                        ..
1233                    })) => {
1234                        env.enter_namespace(self.token.module_name())?;
1235                        visit_processed_tokens(processed_tokens, env)?;
1236                        env.leave_namespace()?;
1237
1238                        Ok(())
1239                    },
1240
1241                    Some(ProcessedTokenState::Repeat(SimpleListingState {
1242                        processed_tokens,
1243                        ..
1244                    })) => {
1245                        env.visit_repeat(
1246                            self.token.repeat_count(),
1247                            processed_tokens,
1248                            self.token.repeat_counter_name(),
1249                            self.token.repeat_counter_start(),
1250                            self.token.repeat_counter_step(),
1251                            self.token.possible_span()
1252                        )
1253                    },
1254
1255                    Some(ProcessedTokenState::RepeatUntil(SimpleListingState {
1256                        processed_tokens,
1257                        ..
1258                    })) => {
1259                        env.visit_repeat_until(
1260                            self.token.repeat_until_condition(),
1261                            processed_tokens,
1262                            self.token.possible_span()
1263                        )
1264                    },
1265
1266                    Some(ProcessedTokenState::Rorg(SimpleListingState {
1267                        processed_tokens,
1268                        span
1269                    })) => env.visit_rorg(self.token.rorg_expr(), processed_tokens, span.as_ref()),
1270
1271                    Some(ProcessedTokenState::Switch(state)) => {
1272                        let value = env.resolve_expr_must_never_fail(self.token.switch_expr())?;
1273                        let mut met = false;
1274                        let mut broken = false;
1275                        for (case, listing, r#break) in state
1276                            .cases
1277                            .iter_mut()
1278                            .zip(self.token.switch_cases())
1279                            .map(|(pt, t)| (t.0, pt.tokens_mut(), t.2))
1280                        {
1281                            // check if case must be executed
1282                            let case = env.resolve_expr_must_never_fail(case)?;
1283                            met |= case == value;
1284
1285                            // inject code if needed and leave if break is present
1286                            if met {
1287                                visit_processed_tokens(listing, env)?;
1288                                if r#break {
1289                                    broken = true;
1290                                    break;
1291                                }
1292                            }
1293                        }
1294
1295                        // execute default if any
1296                        if !met || !broken {
1297                            if let Some(default) = &mut state.default {
1298                                visit_processed_tokens(&mut default.processed_tokens, env)?;
1299                            }
1300                        }
1301
1302                        Ok(())
1303                    },
1304
1305                    Some(ProcessedTokenState::Warning(box token)) => {
1306                        let warning = AssemblerError::RelocatedWarning {
1307                            warning: Box::new(AssemblerError::AssemblingError {
1308                                msg: self.token.warning_message().to_owned()
1309                            }),
1310                            span: self.token.possible_span().unwrap().clone()
1311                        };
1312                        let warning = AssemblerError::AlreadyRenderedError(warning.to_string());
1313
1314                        env.add_warning(warning);
1315                        token.visited(env)
1316                    },
1317                    Some(ProcessedTokenState::While(SimpleListingState {
1318                        processed_tokens,
1319                        ..
1320                    })) => {
1321                        env.visit_while(
1322                            self.token.while_expr(),
1323                            processed_tokens,
1324                            self.token.possible_span()
1325                        )
1326                    },
1327
1328                    // no state implies a standard visit
1329                    None => self.token.visited(env)
1330                }
1331            }?;
1332
1333            if !self.token.is_buildcpr() {
1334                // we lack of some datastructures
1335                env.update_dollar();
1336            }
1337
1338            if deferred {
1339                let outer_token = unsafe {
1340                    (self.token as *const T as *const LocatedToken)
1341                        .as_ref()
1342                        .unwrap()
1343                };
1344
1345                env.handle_output_trigger(outer_token);
1346            }
1347            Ok(())
1348        };
1349
1350        really_does_the_job(possible_span.as_ref()).map_err(|e| {
1351            let e = match possible_span {
1352                Some(span) => e.locate(span.clone()),
1353                None => e
1354            };
1355            AssemblerError::AlreadyRenderedError(e.to_string())
1356        })
1357    }
1358}
1359
1360// let fname = span
1361// .context()
1362// .get_path_for(fname)
1363// .unwrap_or("will_fail".into());
1364// if (!*once) || (!env.has_included(&fname)) {
1365// env.mark_included(fname);
1366//
1367// if cell.read().unwrap().is_some() {
1368// if let Some(namespace) = namespace {
1369// env.enter_namespace(namespace)
1370// .map_err(|e| e.locate(span.clone()))?;
1371// }
1372//
1373// env.visit_listing(cell.read().unwrap().as_ref().unwrap())?;
1374//
1375// if namespace.is_some() {
1376// env.leave_namespace().map_err(|e| e.locate(span.clone()))?;
1377// }
1378// Ok(())
1379// }
1380// else {
1381// outer_token
1382// .read_referenced_file(&outer_token.context().1)
1383// .and_then(|_| visit_located_token(outer_token, env))
1384// .map_err(|e| e.locate(span.clone()))
1385// }
1386// .map_err(|err| {
1387// AssemblerError::IncludedFileError {
1388// span: span.clone(),
1389// error: Box::new(err)
1390// }
1391// })
1392// }
1393// else {
1394// Ok(()) // we include nothing
1395// }
1396
1397#[cfg(test)]
1398mod test_super {
1399    use super::*;
1400
1401    #[test]
1402    fn test_located_include() {
1403        use crate::parse_z80;
1404
1405        let src = "include \"toto\"";
1406
1407        let tokens = parse_z80(src).unwrap();
1408        let token = &tokens[0];
1409        let mut env = Env::default();
1410
1411        let processed = build_processed_token(token, &mut env);
1412        assert!(matches!(
1413            processed.unwrap().state,
1414            Some(ProcessedTokenState::Include(..))
1415        ));
1416    }
1417}