tycho_asm/
ast.rs

1use std::str::FromStr;
2
3use chumsky::DefaultExpected;
4use chumsky::prelude::*;
5use chumsky::text::{Char, TextExpected};
6use chumsky::util::MaybeRef;
7use num_bigint::BigInt;
8use num_traits::Num;
9use tycho_types::boc::Boc;
10use tycho_types::cell::{CellType, HashBytes};
11use tycho_types::crc::crc_16;
12use tycho_types::prelude::{Cell, CellBuilder};
13
14pub type Span = SimpleSpan<usize>;
15
16pub fn parse(s: &'_ str) -> ParseResult<Code<'_>, ParserError> {
17    parser().parse(s)
18}
19
20#[derive(Debug, Clone)]
21pub struct Code<'a> {
22    pub span: Span,
23    pub items: Vec<Stmt<'a>>,
24}
25
26#[derive(Debug, Clone)]
27pub enum Stmt<'a> {
28    Instr(Instr<'a>),
29    Inline(Inline<'a>),
30    NewCell(#[allow(unused)] NewCell),
31    Define(Define<'a>),
32    Invalid(Span),
33}
34
35#[derive(Debug, Clone)]
36pub struct Instr<'a> {
37    pub span: Span,
38    pub ident_span: Span,
39    pub ident: &'a str,
40    pub args: Vec<InstrArg<'a>>,
41}
42
43#[derive(Debug, Clone)]
44pub struct Inline<'a> {
45    pub span: Span,
46    pub value: InstrArg<'a>,
47}
48
49#[derive(Debug, Clone)]
50pub struct Define<'a> {
51    #[allow(unused)]
52    pub span: Span,
53    pub name: DefineName<'a>,
54    pub value: InstrArg<'a>,
55}
56
57#[derive(Debug, Clone)]
58pub struct DefineName<'a> {
59    pub span: Span,
60    pub name: &'a str,
61}
62
63#[derive(Debug, Clone)]
64pub struct NewCell {
65    #[allow(unused)]
66    pub span: Span,
67}
68
69#[derive(Debug, Clone)]
70pub struct InstrArg<'a> {
71    pub span: Span,
72    pub value: InstrArgValue<'a>,
73}
74
75#[derive(Debug, Clone)]
76pub struct MethodId<'a> {
77    #[allow(unused)]
78    pub span: Span,
79    pub value: MethodIdValue<'a>,
80}
81
82#[derive(Debug, Clone)]
83pub struct MethodIdValue<'a> {
84    #[allow(unused)]
85    pub span: Span,
86    #[allow(unused)]
87    pub text: &'a str,
88    pub computed: BigInt,
89}
90
91#[derive(Debug, Clone)]
92pub struct Use<'a> {
93    pub span: Span,
94    pub value: UseValue<'a>,
95}
96
97#[derive(Debug, Clone)]
98pub struct UseValue<'a> {
99    #[allow(unused)]
100    pub span: Span,
101    pub name: &'a str,
102}
103
104#[derive(Debug, Clone)]
105pub struct JumpTable<'a> {
106    pub methods: Vec<JumpTableItem<'a>>,
107}
108
109#[derive(Debug, Clone)]
110pub struct JumpTableItem<'a> {
111    pub key: JumpTableItemKey<'a>,
112    pub value: JumpTableItemValue<'a>,
113}
114
115#[derive(Debug, Clone)]
116pub struct JumpTableItemKey<'a> {
117    pub span: Span,
118    pub data: JumpTableItemKeyData<'a>,
119}
120
121#[derive(Debug, Clone)]
122pub enum JumpTableItemKeyData<'a> {
123    Nat(BigInt),
124    MethodId(MethodId<'a>),
125    Use(Use<'a>),
126    Invalid,
127}
128
129#[derive(Debug, Clone)]
130pub struct JumpTableItemValue<'a> {
131    pub span: Span,
132    pub data: JumpTableItemValueData<'a>,
133}
134
135#[derive(Debug, Clone)]
136pub enum JumpTableItemValueData<'a> {
137    Block(Vec<Stmt<'a>>),
138    #[allow(unused)]
139    Invalid,
140}
141
142#[derive(Debug, Clone)]
143pub enum InstrArgValue<'a> {
144    Nat(BigInt),
145    SReg(i16),
146    CReg(u8),
147    Slice(Cell),
148    Lib(Cell),
149    Cell(Cell),
150    Block(Vec<Stmt<'a>>),
151    JumpTable(JumpTable<'a>),
152    MethodId(MethodId<'a>),
153    Use(Use<'a>),
154    Invalid,
155}
156
157type ParserExtra = extra::Full<ParserError, (), ()>;
158
159fn parser<'a>() -> impl Parser<'a, &'a str, Code<'a>, ParserExtra> {
160    let whitespace_or_comments = whitespace_or_comments();
161
162    whitespace_or_comments.ignore_then(
163        stmt()
164            .recover_with(via_parser(
165                any()
166                    .filter(|c: &char| !c.is_whitespace())
167                    .ignore_then(choice((any().ignored(), end())))
168                    .map_with(|_, e| Stmt::Invalid(e.span())),
169            ))
170            .padded_by(whitespace_or_comments)
171            .repeated()
172            .collect()
173            .map_with(|items, e| Code {
174                span: e.span(),
175                items,
176            }),
177    )
178}
179
180fn stmt<'a>() -> impl Parser<'a, &'a str, Stmt<'a>, ParserExtra> {
181    recursive(|stmt| {
182        let whitespace_or_comment = whitespace_or_comments();
183
184        let inline = just("@inline")
185            .padded_by(whitespace_or_comment)
186            .ignore_then(instr_arg(stmt.clone()))
187            .map_with(|value, e| {
188                Stmt::Inline(Inline {
189                    value,
190                    span: e.span(),
191                })
192            });
193
194        let jumpref = just("@newcell")
195            .padded_by(whitespace_or_comment)
196            .map_with(|_, e| Stmt::NewCell(NewCell { span: e.span() }));
197
198        let define = define(stmt.clone()).map_with(|define, e| match define {
199            Some(value) => Stmt::Define(value),
200            None => Stmt::Invalid(e.span()),
201        });
202
203        let instr = instr(stmt).map(Stmt::Instr);
204
205        choice((inline, define, jumpref, instr))
206    })
207}
208
209fn define<'a>(
210    stmt: Recursive<dyn Parser<'a, &'a str, Stmt<'a>, ParserExtra> + 'a>,
211) -> impl Parser<'a, &'a str, Option<Define<'a>>, ParserExtra> + Clone {
212    just("@define(")
213        .ignore_then(
214            none_of(")")
215                .repeated()
216                .to_slice()
217                .try_map(move |s, mut span| match parse_ident_name(s, &mut span) {
218                    Ok(name) => Ok(Some(DefineName { span, name })),
219                    Err(e) => Err(ParserError::InvalidName {
220                        span,
221                        inner: e.into(),
222                    }),
223                })
224                .recover_with(via_parser(until_next_arg_or_whitespace().map(|_| None))),
225        )
226        .then_ignore(just(')'))
227        .padded_by(whitespace_or_comments())
228        .then(instr_arg(stmt.clone()))
229        .map_with(|(name, value), e| {
230            name.map(|name| Define {
231                span: e.span(),
232                name,
233                value,
234            })
235        })
236}
237
238fn instr<'a>(
239    stmt: Recursive<dyn Parser<'a, &'a str, Stmt<'a>, ParserExtra> + 'a>,
240) -> impl Parser<'a, &'a str, Instr<'a>, ParserExtra> + Clone {
241    fn compute_min_span(ident_span: Span, args: &[InstrArg<'_>]) -> Span {
242        let mut res = ident_span;
243        for arg in args {
244            res.start = std::cmp::min(res.start, arg.span.start);
245            res.end = std::cmp::max(res.end, arg.span.end);
246        }
247        res
248    }
249
250    let whitespace_or_comment = whitespace_or_comments();
251
252    let args = instr_arg(stmt)
253        .separated_by(just(',').padded_by(whitespace_or_comment))
254        .collect::<Vec<_>>();
255
256    instr_ident()
257        .map_with(|ident, e| (ident, e.span()))
258        .padded_by(whitespace_or_comment)
259        .then(args)
260        .map(|((ident, ident_span), args)| Instr {
261            span: compute_min_span(ident_span, &args),
262            ident,
263            ident_span,
264            args,
265        })
266}
267
268fn instr_arg<'a>(
269    stmt: Recursive<dyn Parser<'a, &'a str, Stmt<'a>, ParserExtra> + 'a>,
270) -> impl Parser<'a, &'a str, InstrArg<'a>, ParserExtra> + Clone {
271    choice((
272        nat().map(|value| {
273            value
274                .map(InstrArgValue::Nat)
275                .unwrap_or(InstrArgValue::Invalid)
276        }),
277        just("@method")
278            .rewind()
279            .ignore_then(method_id().map(|value| {
280                value
281                    .map(InstrArgValue::MethodId)
282                    .unwrap_or(InstrArgValue::Invalid)
283            })),
284        just("@use").rewind().ignore_then(r#use().map(|value| {
285            value
286                .map(InstrArgValue::Use)
287                .unwrap_or(InstrArgValue::Invalid)
288        })),
289        stack_register().map(|idx| {
290            idx.map(InstrArgValue::SReg)
291                .unwrap_or(InstrArgValue::Invalid)
292        }),
293        control_register().map(|idx| {
294            idx.map(InstrArgValue::CReg)
295                .unwrap_or(InstrArgValue::Invalid)
296        }),
297        jump_table(stmt.clone()).map(InstrArgValue::JumpTable),
298        cont_block(stmt).map(InstrArgValue::Block),
299        cell_slice().map(|slice| {
300            slice
301                .map(InstrArgValue::Slice)
302                .unwrap_or(InstrArgValue::Invalid)
303        }),
304        library_cell().map(|lib| {
305            lib.map(InstrArgValue::Lib)
306                .unwrap_or(InstrArgValue::Invalid)
307        }),
308        raw_cell().map(|cell| {
309            cell.map(InstrArgValue::Cell)
310                .unwrap_or(InstrArgValue::Invalid)
311        }),
312    ))
313    .map_with(|value, e| InstrArg {
314        value,
315        span: e.span(),
316    })
317}
318
319fn instr_ident<'a>() -> impl Parser<'a, &'a str, &'a str, ParserExtra> + Clone {
320    until_next_arg_or_whitespace()
321        .at_least(1)
322        .to_slice()
323        .validate(|ident: &str, e, emitter| {
324            let invalid_char = 'char: {
325                let mut chars = ident.chars().peekable();
326                let Some(first) = chars.peek() else {
327                    break 'char None;
328                };
329
330                if *first == '-' {
331                    chars.next();
332                }
333
334                let Some(first) = chars.peek() else {
335                    break 'char None;
336                };
337
338                if first.is_ascii_digit() {
339                    chars.next();
340                    let Some(second) = chars.next() else {
341                        break 'char None;
342                    };
343                    if !second.is_ascii_uppercase() {
344                        break 'char Some(second);
345                    }
346                }
347
348                for c in chars {
349                    if !(c.is_ascii_uppercase()
350                        || c.is_ascii_digit()
351                        || c == '#'
352                        || c == '_'
353                        || c == ':')
354                    {
355                        break 'char Some(c);
356                    }
357                }
358
359                return ident;
360            };
361
362            emitter.emit(ParserError::ExpectedFound {
363                span: e.span(),
364                found: invalid_char,
365            });
366
367            "#INVALID#"
368        })
369}
370
371fn nat<'a>() -> impl Parser<'a, &'a str, Option<BigInt>, ParserExtra> + Clone {
372    fn parse_int(mut s: &str, radix: u32, span: Span) -> Result<BigInt, ParserError> {
373        if !s.is_empty() {
374            s = s.trim_start_matches('0');
375            if s.is_empty() {
376                s = "0";
377            }
378        }
379
380        let in_range = match radix {
381            2 => s.len() <= 256,
382            10 => s.len() <= 78,
383            16 => s.len() <= 64,
384            _ => return Err(ParserError::UnknownError),
385        };
386
387        if !in_range {
388            return Err(ParserError::TooBigInteger { span });
389        }
390
391        match BigInt::from_str_radix(s, radix) {
392            Ok(n) => Ok(n),
393            Err(e) => Err(ParserError::InvalidInt { span, inner: e }),
394        }
395    }
396
397    let num_slice = until_next_arg_or_whitespace().at_least(1).to_slice();
398
399    let after_decimal =
400        choice((end(), any().filter(|c: &char| !c.is_alphabetic()).ignored())).rewind();
401
402    let number = choice((
403        just("0x").ignore_then(num_slice).validate(|s, e, emitter| {
404            match parse_int(s, 16, e.span()) {
405                Ok(res) => Some(res),
406                Err(e) => {
407                    emitter.emit(e);
408                    None
409                }
410            }
411        }),
412        just("0b").ignore_then(num_slice).validate(|s, e, emitter| {
413            match parse_int(s, 2, e.span()) {
414                Ok(res) => Some(res),
415                Err(e) => {
416                    emitter.emit(e);
417                    None
418                }
419            }
420        }),
421        any()
422            .filter(|c: &char| c.is_ascii_digit())
423            .repeated()
424            .at_least(1)
425            .to_slice()
426            .then_ignore(after_decimal)
427            .validate(|s, e, emitter| match parse_int(s, 10, e.span()) {
428                Ok(res) => Some(res),
429                Err(e) => {
430                    emitter.emit(e);
431                    None
432                }
433            }),
434    ));
435
436    choice((
437        just('-')
438            .ignore_then(number)
439            .map(|value| value.map(std::ops::Neg::neg)),
440        number,
441    ))
442}
443
444fn stack_register<'a>() -> impl Parser<'a, &'a str, Option<i16>, ParserExtra> + Clone {
445    let until_eof_or_paren = none_of(")\n").repeated().then(just(')').or_not());
446
447    let idx = text::int::<_, ParserExtra>(10).try_map(|s, span| match i16::from_str(s) {
448        Ok(n) => Ok(n),
449        Err(e) => Err(ParserError::InvalidStackRegister {
450            span,
451            inner: e.into(),
452        }),
453    });
454
455    just('s').ignore_then(
456        choice((
457            just('(').ignore_then(
458                just('-')
459                    .ignore_then(idx)
460                    .map(|idx| Some(-idx))
461                    .then_ignore(just(')'))
462                    .recover_with(via_parser(until_eof_or_paren.map(|_| None))),
463            ),
464            idx.map(Some),
465        ))
466        .recover_with(via_parser(until_next_arg_or_whitespace().map(|_| None))),
467    )
468}
469
470fn control_register<'a>() -> impl Parser<'a, &'a str, Option<u8>, ParserExtra> + Clone {
471    let idx = text::int::<_, ParserExtra>(10)
472        .try_map(|s, span| match u8::from_str(s) {
473            Ok(n) if (0..=5).contains(&n) || n == 7 => Ok(Some(n)),
474            Ok(n) => Err(ParserError::InvalidControlRegister {
475                span,
476                inner: ControlRegisterError::OutOfRange(n).into(),
477            }),
478            Err(e) => Err(ParserError::InvalidControlRegister {
479                span,
480                inner: e.into(),
481            }),
482        })
483        .recover_with(via_parser(until_next_arg_or_whitespace().map(|_| None)));
484
485    just('c').ignore_then(idx)
486}
487
488fn cont_block<'a>(
489    stmt: Recursive<dyn Parser<'a, &'a str, Stmt<'a>, ParserExtra> + 'a>,
490) -> impl Parser<'a, &'a str, Vec<Stmt<'a>>, ParserExtra> + Clone {
491    stmt.padded().repeated().collect().delimited_by(
492        just('{').padded(),
493        just('}')
494            .ignored()
495            .recover_with(via_parser(end()))
496            .recover_with(skip_then_retry_until(any().ignored(), end())),
497    )
498}
499
500fn jump_table<'a>(
501    stmt: Recursive<dyn Parser<'a, &'a str, Stmt<'a>, ParserExtra> + 'a>,
502) -> impl Parser<'a, &'a str, JumpTable<'a>, ParserExtra> + Clone {
503    let key = choice((
504        nat().map(|value| {
505            value
506                .map(JumpTableItemKeyData::Nat)
507                .unwrap_or(JumpTableItemKeyData::Invalid)
508        }),
509        just("@method")
510            .rewind()
511            .ignore_then(method_id().map(|value| {
512                value
513                    .map(JumpTableItemKeyData::MethodId)
514                    .unwrap_or(JumpTableItemKeyData::Invalid)
515            })),
516        just("@use").rewind().ignore_then(r#use().map(|value| {
517            value
518                .map(JumpTableItemKeyData::Use)
519                .unwrap_or(JumpTableItemKeyData::Invalid)
520        })),
521    ))
522    .map_with(|data, e| JumpTableItemKey {
523        data,
524        span: e.span(),
525    });
526
527    let value = cont_block(stmt).map_with(|value, e| JumpTableItemValue {
528        span: e.span(),
529        data: JumpTableItemValueData::Block(value),
530    });
531
532    let item = key
533        .then_ignore(just("=>").padded())
534        .then(value)
535        .map(|(key, value)| JumpTableItem { key, value });
536
537    item.padded()
538        .repeated()
539        .collect()
540        .delimited_by(
541            just('[').padded(),
542            just(']')
543                .ignored()
544                .recover_with(via_parser(end()))
545                .recover_with(skip_then_retry_until(any().ignored(), end())),
546        )
547        .map_with(|methods: Vec<JumpTableItem<'_>>, _e| JumpTable { methods })
548}
549
550fn library_cell<'a>() -> impl Parser<'a, &'a str, Option<Cell>, ParserExtra> + Clone {
551    just("@{")
552        .ignore_then(any().filter(|&c: &char| c != '}').repeated().to_slice())
553        .then(choice((
554            just('}').map_with(|_, e| (true, e.span())),
555            end().map_with(|_, e| (false, e.span())),
556        )))
557        .validate(|(s, (terminated, end_span)), e, emitter| {
558            if !terminated {
559                emitter.emit(ParserError::ExpectedFound {
560                    span: end_span,
561                    found: None,
562                });
563                return None;
564            }
565            match parse_library_ref(s) {
566                Ok(lib) => Some(lib),
567                Err(err) => {
568                    emitter.emit(ParserError::InvalidLibrary {
569                        span: e.span(),
570                        inner: err.into(),
571                    });
572                    None
573                }
574            }
575        })
576}
577
578fn raw_cell<'a>() -> impl Parser<'a, &'a str, Option<Cell>, ParserExtra> + Clone {
579    just("te6ccg").rewind().ignore_then(
580        any()
581            .filter(|&c: &char| match c {
582                '{' | '}' | '(' | ')' | ',' => false,
583                _ => !c.is_whitespace(),
584            })
585            .repeated()
586            .to_slice()
587            .try_map(move |s, span| match parse_cell_boc(s) {
588                Ok(cell) => Ok(Some(cell)),
589                Err(e) => Err(ParserError::InvalidCell {
590                    span,
591                    inner: e.into(),
592                }),
593            })
594            .recover_with(via_parser(until_next_arg_or_whitespace().map(|_| None))),
595    )
596}
597
598fn method_id<'a>() -> impl Parser<'a, &'a str, Option<MethodId<'a>>, ParserExtra> + Clone {
599    let until_next_arg_or_whitespace = until_next_arg_or_whitespace();
600
601    just("@method(")
602        .ignore_then(
603            none_of(")")
604                .repeated()
605                .to_slice()
606                .try_map(move |s, mut span| match parse_ident_name(s, &mut span) {
607                    Ok(text) => Ok(Some(MethodIdValue {
608                        span,
609                        text,
610                        computed: {
611                            let crc = crc_16(text.as_bytes());
612                            BigInt::from(crc as u32 | 0x10000)
613                        },
614                    })),
615                    Err(e) => Err(ParserError::InvalidMethodId {
616                        span,
617                        inner: e.into(),
618                    }),
619                })
620                .recover_with(via_parser(until_next_arg_or_whitespace.map(|_| None))),
621        )
622        .then_ignore(just(')'))
623        .map_with(|value, e| {
624            value.map(|value| MethodId {
625                span: e.span(),
626                value,
627            })
628        })
629        .recover_with(via_parser(until_next_arg_or_whitespace.map(|_| None)))
630}
631
632fn r#use<'a>() -> impl Parser<'a, &'a str, Option<Use<'a>>, ParserExtra> + Clone {
633    let until_next_arg_or_eof = until_next_arg_or_whitespace();
634
635    just("@use(")
636        .ignore_then(
637            none_of(")")
638                .repeated()
639                .to_slice()
640                .try_map(move |s, mut span| match parse_ident_name(s, &mut span) {
641                    Ok(name) => Ok(Some(UseValue { span, name })),
642                    Err(e) => Err(ParserError::InvalidMethodId {
643                        span,
644                        inner: e.into(),
645                    }),
646                })
647                .recover_with(via_parser(until_next_arg_or_eof.map(|_| None))),
648        )
649        .then_ignore(just(')'))
650        .map_with(|value, e| {
651            value.map(|value| Use {
652                span: e.span(),
653                value,
654            })
655        })
656        .recover_with(via_parser(until_next_arg_or_eof.map(|_| None)))
657}
658
659fn cell_slice<'a>() -> impl Parser<'a, &'a str, Option<Cell>, ParserExtra> + Clone {
660    let content_recovery = any()
661        .filter(|&c: &char| match c {
662            '}' | '/' => false,
663            _ => !c.is_whitespace(),
664        })
665        .repeated();
666
667    let braces_recovery = none_of("}\n").repeated().then(just('}').or_not());
668
669    let make_slice_parser =
670        |prefix: &'static str, parser: fn(&'a str) -> Result<Cell, SliceError>| {
671            just(prefix)
672                .ignore_then(
673                    any()
674                        .filter(|&c: &char| c != '}' && !c.is_whitespace())
675                        .repeated()
676                        .to_slice()
677                        .try_map(move |s, span| match (parser)(s) {
678                            Ok(s) => Ok(Some(s)),
679                            Err(e) => Err(ParserError::InvalidSlice {
680                                span,
681                                inner: e.into(),
682                            }),
683                        })
684                        .recover_with(via_parser(content_recovery.map(|_| None))),
685                )
686                .then(
687                    just('}')
688                        .map(|_| true)
689                        .recover_with(via_parser(braces_recovery.map(|_| false))),
690                )
691                .map(|(mut t, valid)| {
692                    if !valid {
693                        t = None;
694                    }
695                    t
696                })
697        };
698
699    choice((
700        make_slice_parser("x{", parse_hex_slice),
701        make_slice_parser("b{", parse_bin_slice),
702    ))
703}
704
705fn whitespace_or_comments<'a>() -> impl Parser<'a, &'a str, (), ParserExtra> + Copy {
706    choice((
707        any().filter(|c: &char| c.is_whitespace()).ignored(),
708        just("//")
709            .then(any().filter(|c: &char| !c.is_newline()).repeated())
710            .ignored(),
711    ))
712    .repeated()
713}
714
715fn until_next_arg_or_whitespace<'a>() -> chumsky::combinator::Repeated<
716    impl Parser<'a, &'a str, char, ParserExtra> + Copy,
717    char,
718    &'a str,
719    ParserExtra,
720> {
721    any()
722        .filter(|&c: &char| match c {
723            '{' | '}' | '(' | ')' | ',' | '/' | '@' => false,
724            _ => !c.is_whitespace(),
725        })
726        .repeated()
727}
728
729fn parse_hex_slice(s: &str) -> Result<Cell, SliceError> {
730    fn hex_char(c: u8) -> Result<u8, SliceError> {
731        match c {
732            b'A'..=b'F' => Ok(c - b'A' + 10),
733            b'a'..=b'f' => Ok(c - b'a' + 10),
734            b'0'..=b'9' => Ok(c - b'0'),
735            _ => Err(SliceError::InvalidHex(c as char)),
736        }
737    }
738
739    if !s.is_ascii() {
740        return Err(SliceError::NonAscii);
741    }
742
743    let s = s.as_bytes();
744    let (mut s, with_tag) = match s.strip_suffix(b"_") {
745        Some(s) => (s, true),
746        None => (s, false),
747    };
748
749    let mut half_byte = None;
750    if s.len() % 2 != 0 {
751        if let Some((last, prefix)) = s.split_last() {
752            half_byte = Some(hex_char(*last)?);
753            s = prefix;
754        }
755    }
756
757    if s.len() > 128 * 2 {
758        return Err(SliceError::TooLong);
759    }
760
761    let mut builder = CellBuilder::new();
762
763    let mut bytes = hex::decode(s)?;
764
765    let mut bits = bytes.len() as u16 * 8;
766    if let Some(half_byte) = half_byte {
767        bits += 4;
768        bytes.push(half_byte << 4);
769    }
770
771    if with_tag {
772        bits = bytes.len() as u16 * 8;
773        for byte in bytes.iter().rev() {
774            if *byte == 0 {
775                bits -= 8;
776            } else {
777                bits -= 1 + byte.trailing_zeros() as u16;
778                break;
779            }
780        }
781    }
782
783    builder.store_raw(&bytes, bits)?;
784    builder.build().map_err(SliceError::CellError)
785}
786
787fn parse_bin_slice(s: &str) -> Result<Cell, SliceError> {
788    use tycho_types::cell::MAX_BIT_LEN;
789
790    let mut bits = 0;
791    let mut bytes = [0; 128];
792
793    for char in s.chars() {
794        let value = match char {
795            '0' => 0u8,
796            '1' => 1,
797            c => return Err(SliceError::InvalidBin(c)),
798        };
799        bytes[bits / 8] |= value << (7 - bits % 8);
800
801        bits += 1;
802        if bits > MAX_BIT_LEN as usize {
803            return Err(SliceError::TooLong);
804        }
805    }
806
807    let mut builder = CellBuilder::new();
808    builder.store_raw(&bytes, bits as _)?;
809    builder.build().map_err(SliceError::CellError)
810}
811
812fn parse_cell_boc(s: &str) -> Result<Cell, CellError> {
813    Boc::decode_base64(s).map_err(Into::into)
814}
815
816fn parse_library_ref(s: &str) -> Result<Cell, LibraryError> {
817    let hash = s.parse::<HashBytes>()?;
818    let mut b = CellBuilder::new();
819    b.set_exotic(true);
820    b.store_u8(CellType::LibraryReference.to_byte())?;
821    b.store_u256(&hash)?;
822    b.build().map_err(LibraryError::CellError)
823}
824
825fn parse_ident_name<'a>(mut s: &'a str, span: &mut Span) -> Result<&'a str, NameError> {
826    // Shift span start by the trimmed prefix.
827    let mut original = s;
828    s = s.trim_start();
829    span.start += original.len() - s.len();
830
831    // Shift span end by the trimmed suffix.
832    original = s;
833    s = s.trim_end();
834    span.end -= original.len() - s.len();
835
836    // Validate method name.
837    let mut chars = s.chars();
838    let Some(first) = chars.next() else {
839        return Err(NameError::Empty);
840    };
841    if !first.is_ascii_alphabetic() {
842        return Err(NameError::InvalidPrefix);
843    }
844    for c in chars {
845        if !c.is_ascii_alphanumeric() && c != '_' {
846            return Err(NameError::UnexpectedChar(c));
847        }
848    }
849    Ok(s)
850}
851
852#[derive(thiserror::Error, Debug)]
853pub enum ParserError {
854    #[error("unexpected character found: {found:?}")]
855    ExpectedFound { span: Span, found: Option<char> },
856    #[error("invalid int: {inner}")]
857    InvalidInt {
858        span: Span,
859        inner: num_bigint::ParseBigIntError,
860    },
861    #[error("too big integer")]
862    TooBigInteger { span: Span },
863    #[error("invalid stack register: {inner}")]
864    InvalidStackRegister { span: Span, inner: anyhow::Error },
865    #[error("invalid control register: {inner}")]
866    InvalidControlRegister { span: Span, inner: anyhow::Error },
867    #[error("invalid slice: {inner}")]
868    InvalidSlice { span: Span, inner: anyhow::Error },
869    #[error("invalid cell: {inner}")]
870    InvalidCell { span: Span, inner: anyhow::Error },
871    #[error("invalid library cell: {inner}")]
872    InvalidLibrary { span: Span, inner: anyhow::Error },
873    #[error("invalid method id: {inner}")]
874    InvalidMethodId { span: Span, inner: anyhow::Error },
875    #[error("invalid name: {inner}")]
876    InvalidName { span: Span, inner: anyhow::Error },
877    #[error("unknown error")]
878    UnknownError,
879}
880
881impl ParserError {
882    pub fn span(&self) -> Option<Span> {
883        match self {
884            Self::ExpectedFound { span, .. }
885            | Self::InvalidInt { span, .. }
886            | Self::TooBigInteger { span }
887            | Self::InvalidStackRegister { span, .. }
888            | Self::InvalidControlRegister { span, .. }
889            | Self::InvalidSlice { span, .. }
890            | Self::InvalidCell { span, .. }
891            | Self::InvalidLibrary { span, .. }
892            | Self::InvalidMethodId { span, .. }
893            | Self::InvalidName { span, .. } => Some(*span),
894            Self::UnknownError => None,
895        }
896    }
897}
898
899#[derive(thiserror::Error, Debug)]
900enum ControlRegisterError {
901    #[error("control register `c{0}` is out of range")]
902    OutOfRange(u8),
903}
904
905#[derive(thiserror::Error, Debug)]
906enum SliceError {
907    #[error("non-ascii characters in bitstring")]
908    NonAscii,
909    #[error("unexpected char `{0}` in hex bitstring")]
910    InvalidHex(char),
911    #[error("invalid hex bitstring: {0}")]
912    InvalidHexFull(#[from] hex::FromHexError),
913    #[error("unexpected char `{0}` in binary bitstring")]
914    InvalidBin(char),
915    #[error("bitstring is too long")]
916    TooLong,
917    #[error("cell build error: {0}")]
918    CellError(#[from] tycho_types::error::Error),
919}
920
921#[derive(thiserror::Error, Debug)]
922#[error("invalid cell BOC: {0}")]
923struct CellError(#[from] tycho_types::boc::de::Error);
924
925#[derive(thiserror::Error, Debug)]
926pub enum NameError {
927    #[error("name cannot be empty")]
928    Empty,
929    #[error("name must start with a letter")]
930    InvalidPrefix,
931    #[error("unexpected char `{0}` in a name")]
932    UnexpectedChar(char),
933}
934
935#[derive(thiserror::Error, Debug)]
936enum LibraryError {
937    #[error("invalid hash: {0}")]
938    InvalidHexFull(#[from] tycho_types::error::ParseHashBytesError),
939    #[error("cell build error: {0}")]
940    CellError(#[from] tycho_types::error::Error),
941}
942
943impl<'a> chumsky::error::LabelError<'a, &'a str, MaybeRef<'a, char>> for ParserError {
944    fn expected_found<Iter: IntoIterator<Item = MaybeRef<'a, char>>>(
945        _: Iter,
946        found: Option<MaybeRef<'a, char>>,
947        span: Span,
948    ) -> Self {
949        Self::ExpectedFound {
950            span,
951            found: found.as_deref().copied(),
952        }
953    }
954}
955
956impl<'a> chumsky::error::LabelError<'a, &'a str, TextExpected<'a, &'a str>> for ParserError {
957    fn expected_found<Iter: IntoIterator<Item = TextExpected<'a, &'a str>>>(
958        _: Iter,
959        found: Option<MaybeRef<'a, char>>,
960        span: Span,
961    ) -> Self {
962        Self::ExpectedFound {
963            span,
964            found: found.as_deref().copied(),
965        }
966    }
967}
968
969impl<'a> chumsky::error::LabelError<'a, &'a str, DefaultExpected<'a, char>> for ParserError {
970    fn expected_found<Iter: IntoIterator<Item = DefaultExpected<'a, char>>>(
971        _: Iter,
972        found: Option<MaybeRef<'a, char>>,
973        span: Span,
974    ) -> Self {
975        Self::ExpectedFound {
976            span,
977            found: found.as_deref().copied(),
978        }
979    }
980}
981
982impl<'a> chumsky::error::Error<'a, &'a str> for ParserError {
983    fn merge(self, _: Self) -> Self {
984        self
985    }
986}
987
988#[cfg(test)]
989mod tests {
990    use super::*;
991
992    #[test]
993    fn empty_asm() {
994        assert!(parse("").unwrap().items.is_empty());
995    }
996
997    #[test]
998    fn simple_asm() {
999        const CODE: &str = r#"
1000        PUSHCONT {
1001            PUSHREF x{afff_}
1002            PUSH s(-1)
1003            OVER
1004            LESSINT 2
1005            PUSHCONT {
1006                2DROP
1007                PUSHINT 1
1008            }
1009            IFJMP
1010            OVER
1011            DEC
1012            SWAP
1013            DUP
1014            EXECUTE
1015            MUL
1016        }
1017        DUP
1018        JMPX
1019        "#;
1020
1021        let (output, errors) = parse(CODE).into_output_errors();
1022        println!("OUTPUT: {:#?}", output);
1023        println!("ERRORS: {:#?}", errors);
1024
1025        assert!(output.is_some());
1026        assert!(errors.is_empty());
1027    }
1028
1029    #[test]
1030    fn example_asm() {
1031        const CODE: &str = include_str!("tests/example.tvm");
1032
1033        let (output, errors) = parse(CODE).into_output_errors();
1034        println!("OUTPUT: {:#?}", output);
1035        println!("ERRORS: {:#?}", errors);
1036
1037        assert!(output.is_some());
1038        assert!(errors.is_empty());
1039    }
1040
1041    #[test]
1042    fn complex_asm() {
1043        const CODE: &str = include_str!("tests/walletv3.tvm");
1044
1045        let (output, errors) = parse(CODE).into_output_errors();
1046        println!("OUTPUT: {:#?}", output);
1047        println!("ERRORS: {:#?}", errors);
1048
1049        assert!(output.is_some());
1050        assert!(errors.is_empty());
1051    }
1052
1053    #[test]
1054    fn strange_opcodes() {
1055        let (output, errors) = parse(
1056            r#"
1057            NOP
1058            2DROP
1059            OVER
1060            LESSINT 2
1061            "#,
1062        )
1063        .into_output_errors();
1064        println!("OUTPUT: {:#?}", output);
1065        println!("ERRORS: {:#?}", errors);
1066
1067        assert!(output.is_some());
1068        assert!(errors.is_empty());
1069    }
1070
1071    #[test]
1072    fn library_cells() {
1073        let (output, errors) = parse(
1074            r#"
1075            PUSHREF @{aabbaaccaabbaaccaabbaaccaabbaaccaabbaaccaabbaaccaabbaaccaabbaacc}
1076            "#,
1077        )
1078        .into_output_errors();
1079        println!("OUTPUT: {:#?}", output);
1080        println!("ERRORS: {:#?}", errors);
1081
1082        assert!(output.is_some());
1083        assert!(errors.is_empty());
1084    }
1085}