etk_asm/parse/
mod.rs

1mod args;
2mod expression;
3mod macros;
4
5pub(crate) mod error;
6mod parser {
7    #![allow(clippy::upper_case_acronyms)]
8
9    use pest_derive::Parser;
10
11    #[derive(Parser)]
12    #[grammar = "parse/asm.pest"]
13    pub(super) struct AsmParser;
14}
15
16use std::convert::TryInto;
17
18use self::{
19    error::ParseError,
20    parser::{AsmParser, Rule},
21};
22use crate::ast::Node;
23use crate::ops::AbstractOp;
24use etk_ops::shanghai::Op;
25use num_bigint::BigInt;
26use pest::{iterators::Pair, Parser};
27
28pub(crate) fn parse_asm(asm: &str) -> Result<Vec<Node>, ParseError> {
29    let mut program: Vec<Node> = Vec::new();
30
31    let pairs = AsmParser::parse(Rule::program, asm)?;
32    for pair in pairs {
33        let node = match pair.as_rule() {
34            Rule::builtin => macros::parse_builtin(pair)?,
35            Rule::EOI => continue,
36            _ => parse_abstract_op(pair)?.into(),
37        };
38        program.push(node);
39    }
40
41    Ok(program)
42}
43
44fn parse_abstract_op(pair: Pair<Rule>) -> Result<AbstractOp, ParseError> {
45    let ret = match pair.as_rule() {
46        Rule::local_macro => macros::parse(pair)?,
47        Rule::label_definition => {
48            AbstractOp::Label(pair.into_inner().next().unwrap().as_str().to_string())
49        }
50        Rule::push => parse_push(pair)?,
51        Rule::op => {
52            let spec: Op<()> = pair.as_str().parse().unwrap();
53            let op = Op::new(spec).unwrap();
54            AbstractOp::Op(op)
55        }
56        _ => unreachable!(),
57    };
58
59    Ok(ret)
60}
61
62fn parse_push(pair: Pair<Rule>) -> Result<AbstractOp, ParseError> {
63    let mut pair = pair.into_inner();
64    let size = pair.next().unwrap();
65    let size: usize = size.as_str().parse().unwrap();
66    let operand = pair.next().unwrap();
67
68    let spec = Op::<()>::push(size).unwrap();
69    let expr = expression::parse(operand)?;
70
71    if let Ok(val) = expr.eval() {
72        let max = BigInt::pow(&BigInt::from(2u32), (8 * size).try_into().unwrap());
73        if val >= max {
74            return error::ImmediateTooLarge.fail();
75        }
76    }
77
78    Ok(AbstractOp::Op(spec.with(expr).unwrap()))
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::ops::{
85        Expression, ExpressionMacroDefinition, ExpressionMacroInvocation, Imm,
86        InstructionMacroDefinition, InstructionMacroInvocation, Terminal,
87    };
88    use assert_matches::assert_matches;
89    use etk_ops::shanghai::*;
90    use hex_literal::hex;
91    use num_bigint::Sign;
92    use std::path::PathBuf;
93
94    macro_rules! nodes {
95        ($($x:expr),+ $(,)?) => (
96            vec![$(Node::from($x)),+]
97        );
98    }
99
100    #[test]
101    fn parse_ops() {
102        let asm = r#"
103            stop
104            pc
105            gas
106            xor
107            push0
108        "#;
109        let expected = nodes![
110            Op::from(Stop),
111            Op::from(GetPc),
112            Op::from(Gas),
113            Op::from(Xor),
114            Op::from(Push0)
115        ];
116        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
117    }
118
119    #[test]
120    fn parse_single_line() {
121        let asm = r#"
122            push1 0b0; push1 0b1
123        "#;
124        let expected = nodes![
125            Op::from(Push1(Imm::from([0]))),
126            Op::from(Push1(Imm::from([1])))
127        ];
128        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
129    }
130
131    #[test]
132    fn parse_mixed_lines() {
133        let asm = r#"
134            push1 0b0; push1 0b1
135            push1 0b1
136        "#;
137        let expected = nodes![
138            Op::from(Push1(Imm::from([0]))),
139            Op::from(Push1(Imm::from([1]))),
140            Op::from(Push1(Imm::from([1])))
141        ];
142        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
143    }
144
145    #[test]
146    fn parse_push_binary() {
147        let asm = r#"
148            # simple cases
149            push1 0b0
150            push1 0b1
151        "#;
152        let expected = nodes![
153            Op::from(Push1(Imm::from([0]))),
154            Op::from(Push1(Imm::from([1])))
155        ];
156        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
157    }
158
159    #[test]
160    fn parse_push_octal() {
161        let asm = r#"
162            # simple cases
163            push1 0o0
164            push1 0o7
165            push2 0o400
166        "#;
167        let expected = nodes![
168            Op::from(Push1(Imm::from([0]))),
169            Op::from(Push1(Imm::from([7]))),
170            Op::from(Push2(Imm::from([1, 0]))),
171        ];
172        println!("{:?}\n\n{:?}", parse_asm(asm), expected);
173        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
174    }
175
176    #[test]
177    fn parse_push_decimal() {
178        let asm = r#"
179            # simple cases
180            push1 0
181            push1 1
182
183            # left-pad values too small
184            push2 42
185
186            # barely enough for 2 bytes
187            push2 256
188
189            # just enough for 4 bytes
190            push4 4294967295
191        "#;
192        let expected = nodes![
193            Op::from(Push1(0u8.into())),
194            Op::from(Push1(Imm::from([1]))),
195            Op::from(Push2(Imm::from([0, 42]))),
196            Op::from(Push2(Imm::from(hex!("0100")))),
197            Op::from(Push4(Imm::from(hex!("ffffffff")))),
198        ];
199        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
200
201        let asm = "push1 256";
202        assert_matches!(parse_asm(asm), Err(ParseError::ImmediateTooLarge { .. }));
203    }
204
205    #[test]
206    fn parse_push_hex() {
207        let asm = r#"
208            push1 0x01 # comment
209            push1 0x42
210            push2 0x0102
211            push4 0x01020304
212            push8 0x0102030405060708
213            push16 0x0102030405060708090a0b0c0d0e0f10
214            push24 0x0102030405060708090a0b0c0d0e0f101112131415161718
215            push32 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
216        "#;
217        let expected = nodes![
218            Op::from(Push1(Imm::from(hex!("01")))),
219            Op::from(Push1(Imm::from(hex!("42")))),
220            Op::from(Push2(Imm::from(hex!("0102")))),
221            Op::from(Push4(Imm::from(hex!("01020304")))),
222            Op::from(Push8(Imm::from(hex!("0102030405060708")))),
223            Op::from(Push16(Imm::from(hex!("0102030405060708090a0b0c0d0e0f10")))),
224            Op::from(Push24(Imm::from(hex!(
225                "0102030405060708090a0b0c0d0e0f101112131415161718"
226            )))),
227            Op::from(Push32(Imm::from(hex!(
228                "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
229            )))),
230        ];
231        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
232
233        let asm = "push2 0x010203";
234        assert_matches!(parse_asm(asm), Err(ParseError::ImmediateTooLarge { .. }));
235    }
236
237    #[test]
238    fn parse_variable_ops() {
239        let asm = r#"
240            swap1
241            swap4
242            swap16
243            dup1
244            dup4
245            dup16
246            log0
247            log4
248        "#;
249        let expected = nodes![
250            Op::from(Swap1),
251            Op::from(Swap4),
252            Op::from(Swap16),
253            Op::from(Dup1),
254            Op::from(Dup4),
255            Op::from(Dup16),
256            Op::from(Log0),
257            Op::from(Log4),
258        ];
259        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
260    }
261
262    #[test]
263    fn parse_jumpdest_no_label() {
264        let asm = "jumpdest";
265        let expected = nodes![Op::from(JumpDest)];
266        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
267    }
268
269    #[test]
270    fn parse_jumpdest_label() {
271        let asm = "start:\njumpdest";
272        let expected = nodes![AbstractOp::Label("start".into()), Op::from(JumpDest),];
273        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
274    }
275
276    #[test]
277    fn parse_push_label() {
278        let asm = r#"
279            push2 snake_case
280            jumpi
281        "#;
282        let expected = nodes![
283            Op::from(Push2(Imm::with_label("snake_case"))),
284            Op::from(JumpI)
285        ];
286        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
287    }
288
289    #[test]
290    fn parse_push_op_as_label() {
291        let asm = r#"
292            push1:
293            push1 push1
294            jumpi
295        "#;
296        let expected = nodes![
297            AbstractOp::Label("push1".into()),
298            Op::from(Push1(Imm::with_label("push1"))),
299            Op::from(JumpI),
300        ];
301        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
302    }
303
304    #[test]
305    fn parse_selector() {
306        let asm = r#"
307            push4 selector("name()")
308            push4 selector("balanceOf(address)")
309            push4 selector("transfer(address,uint256)")
310            push4 selector("approve(address,uint256)")
311            push32 topic("transfer(address,uint256)")
312        "#;
313        let expected = nodes![
314            Op::from(Push4(Imm::from(hex!("06fdde03")))),
315            Op::from(Push4(Imm::from(hex!("70a08231")))),
316            Op::from(Push4(Imm::from(hex!("a9059cbb")))),
317            Op::from(Push4(Imm::from(hex!("095ea7b3")))),
318            Op::from(Push32(Imm::from(hex!(
319                "a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b"
320            )))),
321        ];
322        println!("{:?}\n\n{:?}", parse_asm(asm), expected);
323        assert_matches!(parse_asm(asm), Ok(e) if e == expected);
324    }
325
326    #[test]
327    fn parse_selector_with_spaces() {
328        let asm = r#"
329            push4 selector("name( )")
330        "#;
331        assert_matches!(parse_asm(asm), Err(ParseError::Lexer { .. }));
332    }
333
334    #[test]
335    fn parse_include() {
336        let asm = format!(
337            r#"
338            push1 1
339            %include("foo.asm")
340            push1 2
341            "#,
342        );
343        let expected = nodes![
344            Op::from(Push1(Imm::from(1u8))),
345            Node::Include(PathBuf::from("foo.asm")),
346            Op::from(Push1(Imm::from(2u8))),
347        ];
348        assert_matches!(parse_asm(&asm), Ok(e) if e == expected)
349    }
350
351    #[test]
352    fn parse_include_hex() {
353        let asm = format!(
354            r#"
355            push1 1
356            %include_hex("foo.hex")
357            push1 2
358            "#,
359        );
360        let expected = nodes![
361            Op::from(Push1(Imm::from(1u8))),
362            Node::IncludeHex(PathBuf::from("foo.hex")),
363            Op::from(Push1(Imm::from(2u8))),
364        ];
365        assert_matches!(parse_asm(&asm), Ok(e) if e == expected)
366    }
367
368    #[test]
369    fn parse_import() {
370        let asm = format!(
371            r#"
372            push1 1
373            %import("foo.asm")
374            push1 2
375            "#,
376        );
377        let expected = nodes![
378            Op::from(Push1(Imm::from(1u8))),
379            Node::Import(PathBuf::from("foo.asm")),
380            Op::from(Push1(Imm::from(2u8))),
381        ];
382        assert_matches!(parse_asm(&asm), Ok(e) if e == expected)
383    }
384
385    #[test]
386    fn parse_import_extra_argument() {
387        let asm = format!(
388            r#"
389            %import("foo.asm", "bar.asm")
390            "#,
391        );
392        assert!(matches!(
393            parse_asm(&asm),
394            Err(ParseError::ExtraArgument {
395                expected: 1,
396                backtrace: _
397            })
398        ))
399    }
400
401    #[test]
402    fn parse_import_missing_argument() {
403        let asm = format!(
404            r#"
405            %import()
406            "#,
407        );
408        assert!(matches!(
409            parse_asm(&asm),
410            Err(ParseError::MissingArgument {
411                got: 0,
412                expected: 1,
413                backtrace: _,
414            })
415        ))
416    }
417
418    #[test]
419    fn parse_import_argument_type() {
420        let asm = format!(
421            r#"
422            %import(0x44)
423            "#,
424        );
425        assert_matches!(parse_asm(&asm), Err(ParseError::ArgumentType { .. }))
426    }
427
428    #[test]
429    fn parse_import_spaces() {
430        let asm = format!(
431            r#"
432            push1 1
433            %import( "hello.asm" )
434            push1 2
435            "#,
436        );
437        let expected = nodes![
438            Op::from(Push1(Imm::from(1u8))),
439            Node::Import(PathBuf::from("hello.asm")),
440            Op::from(Push1(Imm::from(2u8))),
441        ];
442        assert_matches!(parse_asm(&asm), Ok(e) if e == expected)
443    }
444
445    #[test]
446    fn parse_push_macro_with_label() {
447        let asm = format!(
448            r#"
449            push1 1
450            %push( hello )
451            push1 2
452            "#,
453        );
454        let expected = nodes![
455            Op::from(Push1(Imm::from(1u8))),
456            AbstractOp::Push(Imm::with_label("hello")),
457            Op::from(Push1(Imm::from(2u8))),
458        ];
459        assert_matches!(parse_asm(&asm), Ok(e) if e == expected)
460    }
461
462    #[test]
463    fn parse_instruction_macro() {
464        let asm = format!(
465            r#"
466            %macro my_macro(foo, bar)
467                gasprice
468                pop
469                push1 $foo + $bar
470                %push(0x42)
471                %another_macro()
472            %end
473            %my_macro(0x42, 10)
474            "#,
475        );
476        let expected = nodes![
477            AbstractOp::MacroDefinition(
478                InstructionMacroDefinition {
479                    name: "my_macro".into(),
480                    parameters: vec!["foo".into(), "bar".into()],
481                    contents: vec![
482                        AbstractOp::new(GasPrice),
483                        AbstractOp::new(Pop),
484                        AbstractOp::new(Push1(
485                            Expression::Plus(
486                                Terminal::Variable("foo".to_string()).into(),
487                                Terminal::Variable("bar".to_string()).into()
488                            )
489                            .into()
490                        )),
491                        AbstractOp::Push(0x42u8.into()),
492                        AbstractOp::Macro(InstructionMacroInvocation {
493                            name: "another_macro".into(),
494                            parameters: vec![]
495                        })
496                    ]
497                }
498                .into()
499            ),
500            AbstractOp::Macro(InstructionMacroInvocation {
501                name: "my_macro".into(),
502                parameters: vec![
503                    BigInt::from_bytes_be(Sign::Plus, &vec![0x42]).into(),
504                    BigInt::from_bytes_be(
505                        Sign::Plus,
506                        &vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10]
507                    )
508                    .into()
509                ]
510            })
511        ];
512
513        assert_eq!(parse_asm(&asm).unwrap(), expected)
514    }
515
516    #[test]
517    fn parse_expression() {
518        let asm = format!(
519            r#"
520            push1 1+-1
521            push1 2*foo
522            push1 (1+(2*foo))-(bar/42)
523            push1 0x20+0o1+0b10
524            "#,
525        );
526        let expected = nodes![
527            Op::from(Push1(Imm::with_expression(Expression::Plus(
528                1.into(),
529                BigInt::from(-1).into(),
530            )))),
531            Op::from(Push1(Imm::with_expression(Expression::Times(
532                2.into(),
533                Terminal::Label("foo".into()).into()
534            )))),
535            Op::from(Push1(Imm::with_expression(Expression::Minus(
536                Box::new(Expression::Plus(
537                    1.into(),
538                    Box::new(Expression::Times(
539                        2.into(),
540                        Terminal::Label("foo".into()).into()
541                    ))
542                )),
543                Box::new(Expression::Divide(
544                    Terminal::Label("bar".into()).into(),
545                    42.into()
546                ))
547            )))),
548            Op::from(Push1(Imm::with_expression(Expression::Plus(
549                Box::new(Expression::Plus(
550                    Terminal::Number(0x20.into()).into(),
551                    1.into()
552                )),
553                2.into()
554            ))))
555        ];
556        assert_eq!(parse_asm(&asm).unwrap(), expected)
557    }
558
559    #[test]
560    fn parse_push_macro_with_expression() {
561        let asm = format!(
562            r#"
563            push1 1
564            %push( 1 + 1 )
565            push1 2
566            "#,
567        );
568        let expected = nodes![
569            Op::from(Push1(Imm::from(1u8))),
570            AbstractOp::Push(Imm::with_expression(Expression::Plus(1.into(), 1.into()))),
571            Op::from(Push1(Imm::from(2u8))),
572        ];
573        assert_matches!(parse_asm(&asm), Ok(e) if e == expected)
574    }
575
576    #[test]
577    fn parse_expression_macro() {
578        let asm = format!(
579            r#"
580            %def foobar()
581                1+2
582            %end
583            push1 foobar()
584            "#,
585        );
586        let expected = nodes![
587            ExpressionMacroDefinition {
588                name: "foobar".into(),
589                parameters: vec![],
590                content: Imm::with_expression(Expression::Plus(1.into(), 2.into())),
591            },
592            Op::from(Push1(Imm::with_macro(ExpressionMacroInvocation {
593                name: "foobar".into(),
594                parameters: vec![]
595            }))),
596        ];
597        assert_eq!(parse_asm(&asm).unwrap(), expected);
598    }
599}