cpclib_asm/
orgams.rs

1use std::fmt::Display;
2use std::ops::Deref;
3
4use beef::lean::Cow;
5use cpclib_common::camino::Utf8Path;
6use cpclib_common::itertools::Itertools;
7use cpclib_tokens::{
8    BinaryOperation, BinaryTransformation, DataAccess, DataAccessElem, Expr, ExprElement,
9    ListingElement, MacroParam, MacroParamElement, Mnemonic, TestKind, TestKindElement, Token,
10    UnaryOperation
11};
12
13use crate::{
14    LocatedDataAccess, LocatedExpr, LocatedMacroParam, LocatedTestKind, MayHaveSpan, ParserContext,
15    ParserContextBuilder, SourceString, TokenExt, Z80Span, parse_z80
16};
17
18fn ctx_and_span(code: &'static str) -> (Box<ParserContext>, Z80Span) {
19    let ctx = Box::new(
20        ParserContextBuilder::default()
21            .set_context_name("TEST")
22            .build(code)
23    );
24    let span = Z80Span::new_extra(code, ctx.deref());
25    (ctx, span)
26}
27
28#[derive(Debug)]
29pub struct ToOrgamsError(String);
30
31impl Display for ToOrgamsError {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.write_str(&self.0)
34    }
35}
36impl From<String> for ToOrgamsError {
37    fn from(val: String) -> Self {
38        ToOrgamsError(val)
39    }
40}
41impl From<&std::io::Error> for ToOrgamsError {
42    fn from(val: &std::io::Error) -> Self {
43        let content = val.to_string();
44        content.into()
45    }
46}
47
48/// Convert an item to the orgams format
49pub trait ToOrgams {
50    fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError>;
51}
52
53macro_rules! macro_params_to_orgams {
54    () => {
55        fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
56            let repr: String = if self.is_single() {
57                let arg = self.single_argument();
58                let (_ctx, mut code) = ctx_and_span(unsafe { std::mem::transmute(arg.deref()) });
59                let value = crate::located_expr(&mut code);
60                match value {
61                    Ok(expr) => expr.to_orgams_string()?.into_owned(),
62                    Err(_) => arg.into_owned()
63                }
64            }
65            else {
66                unimplemented!("We consider it does not happens with ORGAMS")
67            };
68
69            Ok(repr.into())
70        }
71    };
72}
73
74impl ToOrgams for MacroParam {
75    macro_params_to_orgams!();
76}
77
78impl ToOrgams for LocatedMacroParam {
79    macro_params_to_orgams!();
80}
81
82impl ToOrgams for Mnemonic {
83    fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
84        Ok(self.to_string().to_lowercase().into())
85    }
86}
87
88impl ToOrgams for BinaryOperation {
89    fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
90        Ok(self.to_string().to_uppercase().into())
91    }
92}
93
94impl ToOrgams for UnaryOperation {
95    fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
96        Ok(self.to_string().to_ascii_uppercase().into())
97    }
98}
99
100macro_rules! expr_to_orgams {
101    () => {
102        fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
103            let repr = match self {
104                Self::Value(v, ..) => {
105                    if self.has_span() {
106                        // basm allow _ between numbers
107                        let span = self.span().as_str().replace("_", "");
108                        if span.starts_with("0x") || span.starts_with("0X") {
109                            format!("&{}", &span[2..])
110                        }
111                        else if span.starts_with("#") {
112                            format!("&{}", &span[1..])
113                        }
114                        else {
115                            format!("{}", v)
116                        }
117                    }
118                    else {
119                        format!("{}", v)
120                    }
121                },
122
123                Self::Label(l) => {
124                    format!("{}", l)
125                },
126
127                Self::String(s) => {
128                    format!("\"{}\"", s)
129                },
130
131                Self::BinaryOperation(op, left, right, ..) => {
132                    let rleft = left.to_orgams_string()?;
133                    let rright = right.to_orgams_string()?;
134                    let rleft = rleft.as_ref();
135                    let op = op.to_orgams_string()?;
136
137                    let protect = |expr: &Self, repr: &str| -> String {
138                        if expr.is_label() || expr.is_value() {
139                            repr.into()
140                        }
141                        else {
142                            format!("[{}]", repr).into()
143                        }
144                    };
145
146                    let rleft = protect(left, rleft.as_ref());
147                    let rright = protect(right, rright.as_ref());
148
149                    format!("{}{}{}", rleft, op, rright)
150                },
151
152                Self::UnaryOperation(op, exp, ..) => {
153                    let exp = exp.to_orgams_string()?;
154                    let op = op.to_orgams_string()?;
155
156                    format!("{} {}", exp, op)
157                },
158
159                Self::Bool(f, ..) => {
160                    format!("{}", if *f { 0 } else { 1 })
161                },
162
163                _ => unimplemented!("{:?}", self)
164            };
165
166            Ok(repr.into())
167        }
168    };
169}
170
171impl ToOrgams for LocatedExpr {
172    expr_to_orgams!();
173}
174
175impl ToOrgams for Expr {
176    expr_to_orgams!();
177}
178
179macro_rules! test_kind_to_orgams {
180    () => {
181        fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
182            if self.is_true_test() {
183                let expr = self.expr_unchecked();
184                Ok(format!("IF {}", expr.to_orgams_string()?).into())
185            }
186            else if self.is_label_exists_test() {
187                let label = self.label_unchecked();
188                Ok(format!("IFDEF {}", label).into())
189            }
190            else if self.is_label_nexists_test() {
191                let label = self.label_unchecked();
192                Ok(format!("IFNDEF {}", label).into())
193            }
194            else {
195                Err(format!("{:?} unhandled", self).into())
196            }
197        }
198    };
199}
200
201impl ToOrgams for LocatedTestKind {
202    test_kind_to_orgams!();
203}
204
205impl ToOrgams for TestKind {
206    test_kind_to_orgams!();
207}
208
209macro_rules! data_access_to_orgams {
210    () => {
211        fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
212            let repr = if self.is_expression() {
213                let exp = self.get_expression().unwrap();
214                return exp.to_orgams_string();
215            }
216            else if self.is_memory() || self.is_port_n() {
217                let exp = self.get_expression().unwrap();
218                let exp = exp.to_orgams_string()?;
219                format!("({})", exp)
220            }
221            else if self.is_register16()
222                || self.is_register8()
223                || self.is_indexregister16()
224                || self.is_indexregister8()
225                || self.is_port_c()
226                || self.is_address_in_register16()
227                || self.is_address_in_indexregister16()
228                || self.is_flag_test()
229            {
230                self.to_string().to_lowercase()
231            }
232            else {
233                unimplemented!("{:?}", self)
234            };
235
236            Ok(repr.into())
237        }
238    };
239}
240
241impl ToOrgams for DataAccess {
242    data_access_to_orgams!();
243}
244
245impl ToOrgams for LocatedDataAccess {
246    data_access_to_orgams!();
247}
248
249impl<T> ToOrgams for T
250where
251    T: TokenExt + MayHaveSpan + ListingElement + ToString + ?Sized,
252    T::DataAccess: ToOrgams,
253    T::Expr: ToOrgams,
254    T::TestKind: ToOrgams,
255    T::MacroParam: ToOrgams
256{
257    fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
258        // we assume it is already a BASM format and not an ORGAMS format
259        let handle_macro_definition = |token: &T| -> Cow<str> {
260            let macro_name = token.macro_definition_name();
261            let arguments_name = token.macro_definition_arguments();
262            let mut macro_content = token.macro_definition_code().to_owned();
263
264            for arg in arguments_name.iter() {
265                macro_content = macro_content.replace(&format!("{{{arg}}}"), arg);
266            }
267            macro_content = macro_content.replace('\n', "\n\t");
268
269            // also transform the content of the macro
270            // in case of failure, fallback to the original content
271            let macro_content = if let Ok(macro_content_listing) = parse_z80(&macro_content) {
272                let macro_content_listing = macro_content_listing.as_slice();
273                macro_content_listing
274                    .to_orgams_string()
275                    .map(|s| s.to_string())
276                    .unwrap_or(macro_content)
277            }
278            else {
279                macro_content
280            };
281
282            let macro_args = arguments_name.into_iter().join(", ");
283
284            let output = format!("\tMACRO {macro_name} {macro_args}\n{macro_content}\tENDM");
285            Cow::owned(output)
286        };
287
288        let handle_macro_call = |token: &T| -> Cow<str> {
289            let name = token.macro_call_name();
290            let arguments = token
291                .macro_call_arguments()
292                .iter()
293                .map(|s| s.to_orgams_string().unwrap())
294                .join(",");
295
296            let repr = format!("{name}({arguments})");
297            repr.into()
298        };
299
300        let handle_standard_directive = |token: &T| -> Cow<str> {
301            // if self.has_span() {
302            // Cow::borrowed(self.span().as_str())
303            // } else {
304            // Cow::owned(self.to_string())
305            // }
306            token.to_token().to_string().into()
307        };
308
309        let comment_token = |token: &T| -> Result<Cow<str>, ToOrgamsError> {
310            let repr = token.to_string();
311            let repr: String = repr.lines().map(|l| format!(" ; {l}")).join("\n");
312            let token = Token::Comment(format!("; {repr}",));
313            let res = token.to_orgams_string()?;
314            Ok(res.into_owned().into())
315        };
316
317        let handle_org = |token: &T| -> Result<Cow<str>, ToOrgamsError> {
318            let org1 = token.org_first();
319            let org2 = token.org_second();
320
321            let org1 = org1.to_orgams_string()?;
322            let repr = if let Some(org2) = org2 {
323                format!("ORG {}, {}", org1, org2.to_orgams_string()?)
324            }
325            else {
326                format!("ORG {}", org1)
327            };
328
329            Ok(repr.into())
330        };
331
332        let handle_data = |token: &T| -> Result<Cow<str>, ToOrgamsError> {
333            let exprs = token
334                .data_exprs()
335                .iter()
336                .map(|e| e.to_orgams_string())
337                .collect::<Result<Vec<_>, ToOrgamsError>>()?;
338            let exprs = exprs.into_iter().join(",");
339            let mne = if token.is_db() {
340                "BYTE"
341            }
342            else if token.is_dw() {
343                "WORD"
344            }
345            else {
346                unreachable!()
347            };
348
349            Ok(format!("{} {}", mne, exprs).into())
350        };
351
352        let handle_run = |token: &T| -> Result<Cow<str>, ToOrgamsError> {
353            let repr = format!("ENT {}", token.run_expr().to_orgams_string()?);
354            Ok(repr.into())
355        };
356
357        // XXX strong limitation, does not yet handle 3 args
358        let handle_opcode = |token: &T| -> String {
359            //            dbg!(token);
360            let mut op = token
361                .mnemonic()
362                .unwrap()
363                .to_orgams_string()
364                .unwrap()
365                .to_string();
366
367            if let Some(arg) = token.mnemonic_arg1() {
368                op.push(' ');
369                op.push_str(&arg.to_orgams_string().unwrap())
370            }
371
372            if let Some(arg) = token.mnemonic_arg2() {
373                if token.mnemonic_arg1().is_some() {
374                    op.push(',');
375                }
376                else {
377                    op.push(' ');
378                }
379                op.push_str(&arg.to_orgams_string().unwrap())
380            }
381
382            op
383        };
384
385        let handle_assign = |token: &T| -> String {
386            let label = token.assign_symbol();
387            let value = token.assign_value();
388
389            format!("{}={}", label, value.to_orgams_string().unwrap())
390        };
391
392        let handle_equ = |token: &T| -> String {
393            let label = token.equ_symbol();
394            let value = token.equ_value();
395
396            format!("{} EQU {}", label, value.to_orgams_string().unwrap())
397        };
398
399        let handle_if = |token: &T| -> String {
400            assert!(self.if_nb_tests() == 1);
401
402            let (test, code) = token.if_test(0);
403            let mut content = format!(
404                "{}\n{}",
405                test.to_orgams_string().unwrap(),
406                code.to_orgams_string().unwrap()
407            );
408
409            if let Some(code) = token.if_else() {
410                content.push_str("\n\tELSE\n");
411                content.push_str(&code.to_orgams_string().unwrap());
412            }
413
414            content.push_str("\n\tEND\n");
415            content
416        };
417
418        // An include is injected in the file
419        // FNAME must be encoded within a string. TO improve this aspect, it is necessary to assemble the file and manipulate the `Env` and its symbols table
420        let handle_include = |token: &T| -> Result<String, ToOrgamsError> {
421            let fname = token.include_fname().string();
422            let mut include = format!(" ; START Included from {fname}\n");
423            let content = convert_from(fname)?;
424            include.push_str(&format!("{content}\n ; STOP Included from {fname}\n"));
425            Ok(include)
426        };
427
428        let handle_incbin = |token: &T| -> Result<String, ToOrgamsError> {
429            let fname = token.incbin_fname().string();
430            let repr = format!("LOAD \"{}\"", fname);
431
432            assert!(token.incbin_length().is_none());
433            assert!(token.incbin_offset().is_none());
434            assert_eq!(token.incbin_transformation(), &BinaryTransformation::None);
435
436            Ok(repr)
437        };
438
439        // This is the default behavior that changes nothing
440        let repr = if self.is_opcode() {
441            Cow::owned(handle_opcode(self))
442        }
443        else if self.is_org() {
444            handle_org(self)?
445        }
446        else if self.is_macro_definition() {
447            handle_macro_definition(self)
448        }
449        else if self.is_call_macro_or_build_struct() {
450            handle_macro_call(self)
451        }
452        else if self.is_assign() {
453            handle_assign(self).into()
454        }
455        else if self.is_equ() {
456            handle_equ(self).into()
457        }
458        else if self.is_if() {
459            handle_if(self).into()
460        }
461        else if self.is_include() {
462            handle_include(self)?.into()
463        }
464        else if self.is_incbin() {
465            handle_incbin(self)?.into()
466        }
467        else if self.is_assert() || self.is_breakpoint() || self.is_print() || self.is_save() {
468            comment_token(self)?
469        }
470        else if self.is_db() || self.is_dw() {
471            handle_data(self)?
472        }
473        else if self.is_run() {
474            handle_run(self)?
475        }
476        else {
477            handle_standard_directive(self)
478        };
479
480        if repr.is_empty() {
481            return Ok(repr);
482        }
483
484        // ensure the is space first
485        let repr = if !self.is_comment() && !self.is_label() && !self.is_equ() && !self.is_assign()
486        {
487            let first = repr.chars().next().unwrap();
488            if first != ' ' && first != '\t' {
489                Cow::owned(format!("\t{}", repr))
490            }
491            else {
492                repr
493            }
494        }
495        else {
496            repr
497        };
498        Ok(repr)
499    }
500}
501
502// impl ToOrgams for Listing {
503// fn to_orgams_string(&self) -> Result<String, ToOrgamsError> {
504// todo!()
505// }
506// }
507//
508// impl ToOrgams for LocatedListing {
509// fn to_orgams_string(&self) -> Result<String, ToOrgamsError> {
510// let mut content = String::new();
511//
512// for token in self.iter() {
513//
514// }
515//
516// Ok(content)
517// }
518// }
519
520impl<T: ToOrgams> ToOrgams for &[T] {
521    fn to_orgams_string(&self) -> Result<Cow<str>, ToOrgamsError> {
522        let mut content = String::with_capacity(self.len() * 10);
523
524        for token in self.iter() {
525            content.push_str(token.to_orgams_string()?.deref());
526            content.push('\n');
527        }
528
529        // TODO do it properly by coding the complete expression display
530        //        let content = content.replace("0x", "&");
531
532        Ok(content.into())
533    }
534}
535
536pub fn convert_source(code: &str) -> Result<String, ToOrgamsError> {
537    let lst = parse_z80(code).map_err(|e| ToOrgamsError(format!("Error while parsing. {}", e)))?;
538    let lst = lst.as_slice();
539    lst.to_orgams_string().map(|s| s.into_owned())
540}
541
542pub fn convert_from<P: AsRef<Utf8Path>>(p: P) -> Result<String, ToOrgamsError> {
543    let p = p.as_ref();
544    let code = std::fs::read_to_string(p)
545        .map_err(|e| ToOrgamsError(format!("Error while reading {}. {}", p, e)))?;
546    convert_source(&code).map_err(|e| format!("Error while handling {}. {}", p, e).into())
547}
548
549/// COnvert a basm txt source file as a orgams text source file.
550///
551/// There are tons of current limitations. I have only implemented what I need
552/// TODO - convert expressions to be orgams compatible. REwrite them ? Write parenthesis ?
553/// TODO - rewrite macros
554pub fn convert_from_to<P1: AsRef<Utf8Path>, P2: AsRef<Utf8Path>>(
555    src: P1,
556    tgt: P2
557) -> Result<(), ToOrgamsError> {
558    let src = src.as_ref();
559    let tgt = tgt.as_ref();
560    let orgams = convert_from(src)?;
561    std::fs::write(tgt, orgams.as_bytes())
562        .map_err(|e| format!("Error while saving {}. {}", tgt, e).into())
563}
564
565#[cfg(test)]
566mod test {
567    use std::ops::Deref;
568
569    use cpclib_common::winnow::ModalParser;
570    use cpclib_common::winnow::error::ParseError;
571    use cpclib_tokens::{DataAccess, Expr};
572
573    use super::{ToOrgams, ctx_and_span};
574    use crate::{
575        AssemblerError, InnerZ80Span, ParserContext, Z80ParserError, Z80Span, located_expr
576    };
577
578    #[derive(Debug)]
579    struct TestResult<O: std::fmt::Debug> {
580        ctx: Box<ParserContext>,
581        span: Z80Span,
582        res: Result<O, ParseError<InnerZ80Span, Z80ParserError>>
583    }
584
585    impl<O: std::fmt::Debug> Deref for TestResult<O> {
586        type Target = Result<O, ParseError<InnerZ80Span, Z80ParserError>>;
587
588        fn deref(&self) -> &Self::Target {
589            &self.res
590        }
591    }
592
593    fn parse_test<O, P: ModalParser<InnerZ80Span, O, Z80ParserError>>(
594        mut parser: P,
595        code: &'static str
596    ) -> TestResult<O>
597    where
598        O: std::fmt::Debug
599    {
600        let (ctx, span) = ctx_and_span(code);
601        let res = parser.parse(span.0);
602        if let Err(e) = &res {
603            let e = e.inner();
604            let e = AssemblerError::SyntaxError { error: e.clone() };
605            eprintln!("Parse error: {}", e);
606        }
607
608        TestResult { ctx, span, res }
609    }
610
611    #[test]
612    fn test_expression() {
613        assert_eq!(
614            parse_test(located_expr, "25")
615                .as_ref()
616                .unwrap()
617                .to_orgams_string()
618                .unwrap(),
619            "25"
620        );
621        assert_eq!(
622            parse_test(located_expr, "0x25")
623                .as_ref()
624                .unwrap()
625                .to_orgams_string()
626                .unwrap(),
627            "&25"
628        );
629    }
630
631    #[test]
632    fn test_data_access() {
633        assert_eq!(
634            DataAccess::Expression(Expr::Value(25))
635                .to_orgams_string()
636                .unwrap(),
637            "25"
638        );
639        assert_eq!(
640            DataAccess::Memory(Expr::Value(25))
641                .to_orgams_string()
642                .unwrap(),
643            "(25)"
644        );
645    }
646}