Repository: kevinmehall/rust-peg (language: rust)

Issue:
Bounds on trait objects and impl traits not parsing
The following will not parse:
```rust
rule operand() -> Box<dyn Operand + 'input>
```

The error:
```
error: expected one of "&", "(", "::", "<", "dyn", "impl"
  --> src\parse.rs:41:45
   |
41 |         rule operand() -> Box<dyn Operand + 'input>
   |                                             ^^^^^^
```

I believe the issue is in the `rust_type()` rule of grammar.rustpeg:

```
rule rust_type()
    = BRACKET_GROUP()
    / "&" LIFETIME()? "mut"? rust_type()
    / "dyn" rust_type() ++ "+" // here
    / "impl" rust_type() ++ "+" // and here
    / "(" (rust_type() ++ "," ","?)? ")"
    / ("<" rust_type() ("as" rust_ty_path())? ">")? rust_ty_path()
```

`rust_type()`s shouldn't follow "dyn" and "impl"; instead it should be something that parses type parameter bounds (which include rust types AND lifetimes) ([ref](https://doc.rust-lang.org/reference/types/trait-object.html)) ([ref](https://doc.rust-lang.org/reference/types/impl-trait.html)). I think the rules to parse that might already be written as part of `rust_where_clause()` and could perhaps be factored out, but I don't have time at the moment to verify that.


Discussion / hints:

Fix patch:
diff --git a/peg-macros/grammar.rs b/peg-macros/grammar.rs
index f7debea..0ea43e8 100644
--- a/peg-macros/grammar.rs
+++ b/peg-macros/grammar.rs
@@ -1388,7 +1388,7 @@ pub mod peg {
                                                     ::peg::RuleResult::Failed => break,
                                                 }
                                             };
-                                            let __step_res = match __parse_rust_type(
+                                            let __step_res = match __parse_rust_ty_param_bound(
                                                 __input,
                                                 __state,
                                                 __err_state,
@@ -1456,19 +1456,24 @@ pub mod peg {
                                                                 ::peg::RuleResult::Failed => break,
                                                             }
                                                         };
-                                                        let __step_res = match __parse_rust_type(
-                                                            __input,
-                                                            __state,
-                                                            __err_state,
-                                                            __pos,
-                                                        ) {
-                                                            ::peg::RuleResult::Matched(pos, _) => {
-                                                                ::peg::RuleResult::Matched(pos, ())
-                                                            }
-                                                            ::peg::RuleResult::Failed => {
-                                                                ::peg::RuleResult::Failed
-                                                            }
-                                                        };
+                                                        let __step_res =
+                                                            match __parse_rust_ty_param_bound(
+                                                                __input,
+                                                                __state,
+                                                                __err_state,
+                                                                __pos,
+                                                            ) {
+                                                                ::peg::RuleResult::Matched(
+                                                                    pos,
+                                                                    _,
+                                                                ) => ::peg::RuleResult::Matched(
+                                                                    pos,
+                                                                    (),
+                                                                ),
+                                                                ::peg::RuleResult::Failed => {
+                                                                    ::peg::RuleResult::Failed
+                                                                }
+                                                            };
                                                         match __step_res {
                                                             ::peg::RuleResult::Matched(
                                                                 __newpos,
@@ -2036,45 +2041,24 @@ pub mod peg {
                                     ::peg::RuleResult::Matched(__pos, __value)
                                 }
                                 ::peg::RuleResult::Failed => {
-                                    let __seq_res =
-                                        match match ::peg::ParseLiteral::parse_string_literal(
-                                            __input, __pos, "for",
-                                        ) {
-                                            ::peg::RuleResult::Matched(__pos, __val) => {
-                                                let __seq_res = match __parse_rust_ty_params(
-                                                    __input,
-                                                    __state,
-                                                    __err_state,
-                                                    __pos,
-                                                ) {
-                                                    ::peg::RuleResult::Matched(pos, _) => {
-                                                        ::peg::RuleResult::Matched(pos, ())
-                                                    }
-                                                    ::peg::RuleResult::Failed => {
-                                                        ::peg::RuleResult::Failed
-                                                    }
-                                                };
-                                                match __seq_res {
-                                                    ::peg::RuleResult::Matched(__pos, _) => {
-                                                        ::peg::RuleResult::Matched(__pos, ())
-                                                    }
-                                                    ::peg::RuleResult::Failed => {
-                                                        ::peg::RuleResult::Failed
-                                                    }
-                                                }
-                                            }
-                                            ::peg::RuleResult::Failed => {
-                                                __err_state.mark_failure(__pos, "\"for\"");
-                                                ::peg::RuleResult::Failed
-                                            }
-                                        } {
-                                            ::peg::RuleResult::Matched(__newpos, _) => {
-                                                ::peg::RuleResult::Matched(__newpos, ())
-                                            }
-                                            ::peg::RuleResult::Failed => {
-                                                ::peg::RuleResult::Matched(__pos, ())
-                                            }
-                                        };
+                                    let __seq_res = match match __parse_rust_for_lifetimes(
+                                        __input,
+                                        __state,
+                                        __err_state,
+                                        __pos,
+                                    ) {
+                                        ::peg::RuleResult::Matched(pos, _) => {
+                                            ::peg::RuleResult::Matched(pos, ())
+                                        }
+                                        ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                                    } {
+                                        ::peg::RuleResult::Matched(__newpos, _) => {
+                                            ::peg::RuleResult::Matched(__newpos, ())
+                                        }
+                                        ::peg::RuleResult::Failed => {
+                                            ::peg::RuleResult::Matched(__pos, ())
+                                        }
+                                    };
                                     match __seq_res {
                                         ::peg::RuleResult::Matched(__pos, _) => {
                                             let __seq_res = match __parse_rust_type(
@@ -2112,10 +2096,7 @@ pub mod peg {
                                                                         let __sep_res = match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "+") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , __val) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"+\"") ; :: peg :: RuleResult :: Failed } } ;
                                                                         match __sep_res { :: peg :: RuleResult :: Matched (__newpos , _) => { __newpos } , :: peg :: RuleResult :: Failed => break , }
                                                                     };
-                                                                    let __step_res = {
-                                                                        let __choice_res = match __parse_LIFETIME (__input , __state , __err_state , __pos) { :: peg :: RuleResult :: Matched (pos , _) => :: peg :: RuleResult :: Matched (pos , ()) , :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } ;
-                                                                        match __choice_res { :: peg :: RuleResult :: Matched (__pos , __value) => :: peg :: RuleResult :: Matched (__pos , __value) , :: peg :: RuleResult :: Failed => { let __seq_res = match match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "?") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , __val) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"?\"") ; :: peg :: RuleResult :: Failed } } { :: peg :: RuleResult :: Matched (__newpos , _) => { :: peg :: RuleResult :: Matched (__newpos , ()) } , :: peg :: RuleResult :: Failed => { :: peg :: RuleResult :: Matched (__pos , ()) } , } ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , _) => { { let __seq_res = match __parse_rust_ty_path (__input , __state , __err_state , __pos) { :: peg :: RuleResult :: Matched (pos , _) => :: peg :: RuleResult :: Matched (pos , ()) , :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } ; match __seq_res { :: peg :: RuleResult :: Matched (__pos , _) => { :: peg :: RuleResult :: Matched (__pos , ()) } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } } }
-                                                                    };
+                                                                    let __step_res = match __parse_rust_ty_param_bound (__input , __state , __err_state , __pos) { :: peg :: RuleResult :: Matched (pos , _) => :: peg :: RuleResult :: Matched (pos , ()) , :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } ;
                                                                     match __step_res { :: peg :: RuleResult :: Matched (__newpos , __value) => { __repeat_pos = __newpos ; __repeat_value . push (__value) ; } , :: peg :: RuleResult :: Failed => { break ; } }
                                                                 }
                                                                 if __repeat_value.len() >= 1 {
@@ -2347,52 +2328,17 @@ pub mod peg {
                                                     ::peg::RuleResult::Failed => break,
                                                 }
                                             };
-                                            let __step_res = {
-                                                let __choice_res = match __parse_LIFETIME(
-                                                    __input,
-                                                    __state,
-                                                    __err_state,
-                                                    __pos,
-                                                ) {
-                                                    ::peg::RuleResult::Matched(pos, _) => {
-                                                        ::peg::RuleResult::Matched(pos, ())
-                                                    }
-                                                    ::peg::RuleResult::Failed => {
-                                                        ::peg::RuleResult::Failed
-                                                    }
-                                                };
-                                                match __choice_res {
-                                                    ::peg::RuleResult::Matched(__pos, __value) => {
-                                                        ::peg::RuleResult::Matched(__pos, __value)
-                                                    }
-                                                    ::peg::RuleResult::Failed => {
-                                                        let __seq_res = match match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , "?") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , __val) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\"?\"") ; :: peg :: RuleResult :: Failed } } { :: peg :: RuleResult :: Matched (__newpos , _) => { :: peg :: RuleResult :: Matched (__newpos , ()) } , :: peg :: RuleResult :: Failed => { :: peg :: RuleResult :: Matched (__pos , ()) } , } ;
-                                                        match __seq_res {
-                                                            ::peg::RuleResult::Matched(
-                                                                __pos,
-                                                                _,
-                                                            ) => {
-                                                                let __seq_res = match __parse_rust_ty_path (__input , __state , __err_state , __pos) { :: peg :: RuleResult :: Matched (pos , _) => :: peg :: RuleResult :: Matched (pos , ()) , :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , } ;
-                                                                match __seq_res {
-                                                                    ::peg::RuleResult::Matched(
-                                                                        __pos,
-                                                                        _,
-                                                                    ) => {
-                                                                        ::peg::RuleResult::Matched(
-                                                                            __pos,
-                                                                            (),
-                                                                        )
-                                                                    }
-                                                                    ::peg::RuleResult::Failed => {
-                                                                        ::peg::RuleResult::Failed
-                                                                    }
-                                                                }
-                                                            }
-                                                            ::peg::RuleResult::Failed => {
-                                                                ::peg::RuleResult::Failed
-                                                            }
-                                                        }
-                                                    }
+                                            let __step_res = match __parse_rust_ty_param_bound(
+                                                __input,
+                                                __state,
+                                                __err_state,
+                                                __pos,
+                                            ) {
+                                                ::peg::RuleResult::Matched(pos, _) => {
+                                                    ::peg::RuleResult::Matched(pos, ())
+                                                }
+                                                ::peg::RuleResult::Failed => {
+                                                    ::peg::RuleResult::Failed
                                                 }
                                             };
                                             match __step_res {
@@ -2441,6 +2387,194 @@ pub mod peg {
             }
         }
     }
+    fn __parse_rust_for_lifetimes<'input>(
+        __input: &'input Input,
+        __state: &mut ParseState<'input>,
+        __err_state: &mut ::peg::error::ErrorState,
+        __pos: usize,
+    ) -> ::peg::RuleResult<()> {
+        #![allow(non_snake_case, unused, clippy::redundant_closure_call)]
+        match ::peg::ParseLiteral::parse_string_literal(__input, __pos, "for") {
+            ::peg::RuleResult::Matched(__pos, __val) => {
+                let __seq_res = match __parse_rust_ty_params(__input, __state, __err_state, __pos) {
+                    ::peg::RuleResult::Matched(pos, _) => ::peg::RuleResult::Matched(pos, ()),
+                    ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                };
+                match __seq_res {
+                    ::peg::RuleResult::Matched(__pos, _) => ::peg::RuleResult::Matched(__pos, ()),
+                    ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                }
+            }
+            ::peg::RuleResult::Failed => {
+                __err_state.mark_failure(__pos, "\"for\"");
+                ::peg::RuleResult::Failed
+            }
+        }
+    }
+    fn __parse_rust_ty_param_bound<'input>(
+        __input: &'input Input,
+        __state: &mut ParseState<'input>,
+        __err_state: &mut ::peg::error::ErrorState,
+        __pos: usize,
+    ) -> ::peg::RuleResult<()> {
+        #![allow(non_snake_case, unused, clippy::redundant_closure_call)]
+        {
+            let __choice_res = match __parse_LIFETIME(__input, __state, __err_state, __pos) {
+                ::peg::RuleResult::Matched(pos, _) => ::peg::RuleResult::Matched(pos, ()),
+                ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+            };
+            match __choice_res {
+                ::peg::RuleResult::Matched(__pos, __value) => {
+                    ::peg::RuleResult::Matched(__pos, __value)
+                }
+                ::peg::RuleResult::Failed => {
+                    let __choice_res = {
+                        let __seq_res = match match ::peg::ParseLiteral::parse_string_literal(
+                            __input, __pos, "?",
+                        ) {
+                            ::peg::RuleResult::Matched(__pos, __val) => {
+                                ::peg::RuleResult::Matched(__pos, __val)
+                            }
+                            ::peg::RuleResult::Failed => {
+                                __err_state.mark_failure(__pos, "\"?\"");
+                                ::peg::RuleResult::Failed
+                            }
+                        } {
+                            ::peg::RuleResult::Matched(__newpos, _) => {
+                                ::peg::RuleResult::Matched(__newpos, ())
+                            }
+                            ::peg::RuleResult::Failed => ::peg::RuleResult::Matched(__pos, ()),
+                        };
+                        match __seq_res {
+                            ::peg::RuleResult::Matched(__pos, _) => {
+                                let __seq_res = match match __parse_rust_for_lifetimes(
+                                    __input,
+                                    __state,
+                                    __err_state,
+                                    __pos,
+                                ) {
+                                    ::peg::RuleResult::Matched(pos, _) => {
+                                        ::peg::RuleResult::Matched(pos, ())
+                                    }
+                                    ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                                } {
+                                    ::peg::RuleResult::Matched(__newpos, _) => {
+                                        ::peg::RuleResult::Matched(__newpos, ())
+                                    }
+                                    ::peg::RuleResult::Failed => {
+                                        ::peg::RuleResult::Matched(__pos, ())
+                                    }
+                                };
+                                match __seq_res {
+                                    ::peg::RuleResult::Matched(__pos, _) => {
+                                        let __seq_res = match __parse_rust_ty_path(
+                                            __input,
+                                            __state,
+                                            __err_state,
+                                            __pos,
+                                        ) {
+                                            ::peg::RuleResult::Matched(pos, _) => {
+                                                ::peg::RuleResult::Matched(pos, ())
+                                            }
+                                            ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                                        };
+                                        match __seq_res {
+                                            ::peg::RuleResult::Matched(__pos, _) => {
+                                                ::peg::RuleResult::Matched(__pos, ())
+                                            }
+                                            ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                                        }
+                                    }
+                                    ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                                }
+                            }
+                            ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                        }
+                    };
+                    match __choice_res {
+                        ::peg::RuleResult::Matched(__pos, __value) => {
+                            ::peg::RuleResult::Matched(__pos, __value)
+                        }
+                        ::peg::RuleResult::Failed => {
+                            match ::peg::ParseLiteral::parse_string_literal(__input, __pos, "(") {
+                                ::peg::RuleResult::Matched(__pos, __val) => {
+                                    let __seq_res =
+                                        match match ::peg::ParseLiteral::parse_string_literal(
+                                            __input, __pos, "?",
+                                        ) {
+                                            ::peg::RuleResult::Matched(__pos, __val) => {
+                                                ::peg::RuleResult::Matched(__pos, __val)
+                                            }
+                                            ::peg::RuleResult::Failed => {
+                                                __err_state.mark_failure(__pos, "\"?\"");
+                                                ::peg::RuleResult::Failed
+                                            }
+                                        } {
+                                            ::peg::RuleResult::Matched(__newpos, _) => {
+                                                ::peg::RuleResult::Matched(__newpos, ())
+                                            }
+                                            ::peg::RuleResult::Failed => {
+                                                ::peg::RuleResult::Matched(__pos, ())
+                                            }
+                                        };
+                                    match __seq_res {
+                                        ::peg::RuleResult::Matched(__pos, _) => {
+                                            let __seq_res = match match __parse_rust_for_lifetimes(
+                                                __input,
+                                                __state,
+                                                __err_state,
+                                                __pos,
+                                            ) {
+                                                ::peg::RuleResult::Matched(pos, _) => {
+                                                    ::peg::RuleResult::Matched(pos, ())
+                                                }
+                                                ::peg::RuleResult::Failed => {
+                                                    ::peg::RuleResult::Failed
+                                                }
+                                            } {
+                                                ::peg::RuleResult::Matched(__newpos, _) => {
+                                                    ::peg::RuleResult::Matched(__newpos, ())
+                                                }
+                                                ::peg::RuleResult::Failed => {
+                                                    ::peg::RuleResult::Matched(__pos, ())
+                                                }
+                                            };
+                                            match __seq_res {
+                                                ::peg::RuleResult::Matched(__pos, _) => {
+                                                    let __seq_res = match __parse_rust_ty_path(
+                                                        __input,
+                                                        __state,
+                                                        __err_state,
+                                                        __pos,
+                                                    ) {
+                                                        ::peg::RuleResult::Matched(pos, _) => {
+                                                            ::peg::RuleResult::Matched(pos, ())
+                                                        }
+                                                        ::peg::RuleResult::Failed => {
+                                                            ::peg::RuleResult::Failed
+                                                        }
+                                                    };
+                                                    match __seq_res { :: peg :: RuleResult :: Matched (__pos , _) => { match :: peg :: ParseLiteral :: parse_string_literal (__input , __pos , ")") { :: peg :: RuleResult :: Matched (__pos , __val) => { :: peg :: RuleResult :: Matched (__pos , ()) } :: peg :: RuleResult :: Failed => { __err_state . mark_failure (__pos , "\")\"") ; :: peg :: RuleResult :: Failed } } } :: peg :: RuleResult :: Failed => :: peg :: RuleResult :: Failed , }
+                                                }
+                                                ::peg::RuleResult::Failed => {
+                                                    ::peg::RuleResult::Failed
+                                                }
+                                            }
+                                        }
+                                        ::peg::RuleResult::Failed => ::peg::RuleResult::Failed,
+                                    }
+                                }
+                                ::peg::RuleResult::Failed => {
+                                    __err_state.mark_failure(__pos, "\"(\"");
+                                    ::peg::RuleResult::Failed
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
     fn __parse_expression<'input>(
         __input: &'input Input,
         __state: &mut ParseState<'input>,
diff --git a/peg-macros/grammar.rustpeg b/peg-macros/grammar.rustpeg
index 7fdafdb..a534d04 100644
--- a/peg-macros/grammar.rustpeg
+++ b/peg-macros/grammar.rustpeg
@@ -59,8 +59,8 @@ rule rust_path()
 rule rust_type()
     = BRACKET_GROUP()
     / "&" LIFETIME()? "mut"? rust_type()
-    / "dyn" rust_type() ++ "+"
-    / "impl" rust_type() ++ "+"
+    / "dyn" rust_ty_param_bound() ++ "+"
+    / "impl" rust_ty_param_bound() ++ "+"
     / "(" (rust_type() ++ "," ","?)? ")"
     / ("<" rust_type() ("as" rust_ty_path())? ">")? rust_ty_path()
 
@@ -73,12 +73,20 @@ rule rust_ty_params() -> Vec<TokenStream>
 rule rust_where_clause()
     = "where" (
         LIFETIME() (":" LIFETIME() ++ "+")?
-        / ("for" rust_ty_params())? rust_type() ":" (LIFETIME() / "?"? rust_ty_path()) ++ "+"
+        / rust_for_lifetimes()? rust_type() ":" rust_ty_param_bound() ++ "+"
     ) ** "," ","?
 
 rule rust_generic_param()
     = LIFETIME() (":" LIFETIME() ++ "+")?
-    / IDENT() (":"(LIFETIME() / "?"? rust_ty_path()) ++ "+")?
+    / IDENT() (":" rust_ty_param_bound() ++ "+")?
+
+rule rust_for_lifetimes()
+    = "for" rust_ty_params()
+
+rule rust_ty_param_bound()
+    = LIFETIME() 
+    / "?"? rust_for_lifetimes()? rust_ty_path()
+    / "(" "?"? rust_for_lifetimes()? rust_ty_path() ")"
 
 rule expression() -> SpannedExpr = choice()
 


Test patch:
diff --git a/tests/run-pass/assembly_ast_dyn_type_param_bounds.rs b/tests/run-pass/assembly_ast_dyn_type_param_bounds.rs
new file mode 100644
index 0000000..8116666
--- /dev/null
+++ b/tests/run-pass/assembly_ast_dyn_type_param_bounds.rs
@@ -0,0 +1,122 @@
+extern crate peg;
+use peg::parser;
+
+// C++ in Rust
+trait Operation<'a>: std::fmt::Debug {}
+trait Operand<'a>: std::fmt::Debug + AsDynOperand<'a> {}
+trait Location<'a>: Operand<'a> {}
+impl<'a, T: ?Sized + Location<'a>> Operand<'a> for T {}
+
+// Thanks to quinedot for their comprehensive write-up on dyn Traits.
+// https://quinedot.github.io/rust-learning/dyn-trait-combining.html#manual-supertrait-upcasting
+trait AsDynOperand<'a> {
+    fn as_dyn_operand(self: Box<Self>) -> Box<dyn Operand<'a> + 'a>;
+}
+
+impl<'a, T: /* Sized + */ Operand<'a> + 'a> AsDynOperand<'a> for T {
+    fn as_dyn_operand(self: Box<Self>) -> Box<dyn Operand<'a> + 'a> {
+        self
+    }
+}
+
+
+
+#[derive(Debug)]
+pub struct Program<'a>(Vec<Box<dyn Operation<'a> + 'a>>);
+
+#[derive(Debug)]
+struct Add<'a> {
+    result: Box<dyn Location<'a> + 'a>,
+    lhs: Box<dyn Operand<'a> + 'a>,
+    rhs: Box<dyn Operand<'a> + 'a>,
+}
+impl<'a> Operation<'a> for Add<'a> {}
+
+#[derive(Debug)]
+struct Sub<'a> {
+    result: Box<dyn Location<'a> + 'a>,
+    lhs: Box<dyn Operand<'a> + 'a>,
+    rhs: Box<dyn Operand<'a> + 'a>,
+}
+impl<'a> Operation<'a> for Sub<'a> {}
+
+#[derive(Debug)]
+struct Register<'a>(&'a str);
+impl<'a> Location<'a> for Register<'a> {}
+
+#[derive(Debug)]
+struct Global<'a>(&'a str);
+impl<'a> Location<'a> for Global<'a> {}
+
+#[derive(Debug)]
+struct Literal(i32);
+impl<'a> Operand<'a> for Literal {}
+
+parser!{
+grammar assembly() for str {
+    pub rule program() -> Program<'input>
+        = op:operation() ** "\n" { Program(op) }
+
+    rule _ = [' ']*
+
+    rule operation() -> Box<dyn Operation<'input> + 'input>
+        = a:add() {a} / s:sub() {s}
+
+    rule add() -> Box<Add<'input>>
+        = result:location() _ "=" _ "add" _ lhs:operand() _ rhs:operand() { Box::new(Add{ result, lhs, rhs }) }
+
+    rule sub() -> Box<Sub<'input>>
+        = result:location() _ "=" _ "sub" _ lhs:operand() _ rhs:operand() { Box::new(Sub{ result, lhs, rhs }) }
+
+    rule location() -> Box<dyn Location<'input> + 'input>
+        = r:register() {r} / g:global() {g}
+
+    rule register() -> Box<Register<'input>>
+        = "%" _ id:identifier() { Box::new(Register(id)) }
+
+    rule global() -> Box<Global<'input>>
+        = "@" _ id:identifier() { Box::new(Global(id)) }
+
+    rule identifier() -> &'input str
+        = $(['a'..='z' | 'A'..='Z']+)
+
+    rule operand() -> Box<dyn Operand<'input> + 'input>
+        = l:location() {l.as_dyn_operand()} / l:literal() {Box::new(l)}
+    
+    rule literal() -> Literal
+        = n:$(['0'..='9']+) {?
+            let n = n.parse::<i32>().map_err(|_| "invalid int literal")?;
+            Ok(Literal(n))
+        }
+
+}}
+
+fn main() {
+    let parsed = assembly::program("%apple = add 1 @g
+@b = add 2 %a
+%c = sub 82 @b
+@dog = sub @b 12").unwrap();
+    let expected = Program(vec![
+        Box::new(Add{
+            result: Box::new(Register("apple")),
+            lhs: Box::new(Literal(1)),
+            rhs: Box::new(Global("g"))
+        }),
+        Box::new(Add{
+            result: Box::new(Global("b")),
+            lhs: Box::new(Literal(2)),
+            rhs: Box::new(Register("a"))
+        }),
+        Box::new(Sub{
+            result: Box::new(Register("c")),
+            lhs: Box::new(Literal(82)),
+            rhs: Box::new(Global("b"))
+        }),
+        Box::new(Sub{
+            result: Box::new(Global("dog")),
+            lhs: Box::new(Global("b")),
+            rhs: Box::new(Literal(12))
+        }),
+    ]);
+    assert_eq!(format!("{parsed:?}"), format!("{expected:?}"));
+}