1use std::{borrow::BorrowMut, marker::PhantomData, str::FromStr};
6
7use anyhow::{Context, Result, bail};
8use iota_types::{
9 base_types::ObjectID,
10 transaction::{Argument, Command, ProgrammableMoveCall},
11};
12use move_core_types::{
13 account_address::AccountAddress,
14 identifier::Identifier,
15 parsing::{
16 parser::{Parser, Token},
17 types::{ParsedType, TypeToken},
18 },
19};
20
21use super::token::CommandToken;
22use crate::programmable_transaction_test_parser::token::{
23 GAS_COIN, INPUT, MAKE_MOVE_VEC, MERGE_COINS, NESTED_RESULT, PUBLISH, RESULT, SPLIT_COINS,
24 TRANSFER_OBJECTS, UPGRADE,
25};
26
27pub struct CommandParser<
30 'a,
31 I: Iterator<Item = (CommandToken, &'a str)>,
32 P: BorrowMut<Parser<'a, CommandToken, I>>,
33> {
34 inner: P,
35 _a: PhantomData<&'a ()>,
36 _i: PhantomData<I>,
37}
38
39#[derive(Debug, Clone)]
40pub struct ParsedMoveCall {
41 pub package: Identifier,
42 pub module: Identifier,
43 pub function: Identifier,
44 pub type_arguments: Vec<ParsedType>,
45 pub arguments: Vec<Argument>,
46}
47
48#[derive(Debug, Clone)]
49pub enum ParsedCommand {
50 MoveCall(Box<ParsedMoveCall>),
51 TransferObjects(Vec<Argument>, Argument),
52 SplitCoins(Argument, Vec<Argument>),
53 MergeCoins(Argument, Vec<Argument>),
54 MakeMoveVec(Option<ParsedType>, Vec<Argument>),
55 Publish(String, Vec<String>),
56 Upgrade(String, Vec<String>, String, Argument),
57}
58
59impl<'a, I: Iterator<Item = (CommandToken, &'a str)>>
60 CommandParser<'a, I, Parser<'a, CommandToken, I>>
61{
62 pub fn new<T: IntoIterator<Item = (CommandToken, &'a str), IntoIter = I>>(v: T) -> Self {
63 Self::from_parser(Parser::new(v))
64 }
65}
66
67impl<'a, I, P> CommandParser<'a, I, P>
68where
69 I: Iterator<Item = (CommandToken, &'a str)>,
70 P: BorrowMut<Parser<'a, CommandToken, I>>,
71{
72 pub fn from_parser(inner: P) -> Self {
73 Self {
74 inner,
75 _a: PhantomData,
76 _i: PhantomData,
77 }
78 }
79
80 pub fn parse_commands(&mut self) -> Result<Vec<ParsedCommand>> {
81 let commands = self.inner().parse_list(
82 |p| CommandParser::from_parser(p).parse_command_start(),
83 CommandToken::Semi,
84 CommandToken::Void,
86 true,
88 )?;
89 let commands = commands
90 .into_iter()
91 .enumerate()
92 .map(|(actual, (annotated, c))| {
93 if let Some(annotated) = annotated {
94 if actual != annotated {
95 anyhow::bail!(
96 "Actual command index of {actual} \
97 does not match annotated index {annotated}",
98 );
99 }
100 }
101 Ok(c)
102 })
103 .collect::<Result<_>>()?;
104 Ok(commands)
105 }
106
107 pub fn parse_command_start(&mut self) -> Result<(Option<usize>, ParsedCommand)> {
108 self.inner().advance(CommandToken::CommandStart)?;
109 let idx = if let Some(CommandToken::Number) = self.inner().peek_tok() {
110 let num = self.inner().advance(CommandToken::Number)?;
111 let idx = usize::from_str(num).context("Invalid command index annotation")?;
112 self.inner().advance(CommandToken::Colon)?;
113 Some(idx)
114 } else {
115 None
116 };
117 let cmd = self.parse_command()?;
118 Ok((idx, cmd))
119 }
120
121 pub fn parse_command(&mut self) -> Result<ParsedCommand> {
122 use super::token::CommandToken as Tok;
123 Ok(match self.inner().advance_any()? {
124 (Tok::Ident, TRANSFER_OBJECTS) => {
125 self.inner().advance(Tok::LParen)?;
126 let args = self.parse_command_args(Tok::LBracket, Tok::RBracket)?;
127 self.inner().advance(Tok::Comma)?;
128 let arg = self.parse_command_arg()?;
129 self.maybe_trailing_comma()?;
130 self.inner().advance(Tok::RParen)?;
131 ParsedCommand::TransferObjects(args, arg)
132 }
133 (Tok::Ident, SPLIT_COINS) => {
134 self.inner().advance(Tok::LParen)?;
135 let coin = self.parse_command_arg()?;
136 self.inner().advance(Tok::Comma)?;
137 let amts = self.parse_command_args(Tok::LBracket, Tok::RBracket)?;
138 self.maybe_trailing_comma()?;
139 self.inner().advance(Tok::RParen)?;
140 ParsedCommand::SplitCoins(coin, amts)
141 }
142 (Tok::Ident, MERGE_COINS) => {
143 self.inner().advance(Tok::LParen)?;
144 let target = self.parse_command_arg()?;
145 self.inner().advance(Tok::Comma)?;
146 let coins = self.parse_command_args(Tok::LBracket, Tok::RBracket)?;
147 self.maybe_trailing_comma()?;
148 self.inner().advance(Tok::RParen)?;
149 ParsedCommand::MergeCoins(target, coins)
150 }
151 (Tok::Ident, MAKE_MOVE_VEC) => {
152 let type_opt = self.parse_type_arg_opt()?;
153 self.inner().advance(Tok::LParen)?;
154 let args = self.parse_command_args(Tok::LBracket, Tok::RBracket)?;
155 self.maybe_trailing_comma()?;
156 self.inner().advance(Tok::RParen)?;
157 ParsedCommand::MakeMoveVec(type_opt, args)
158 }
159 (Tok::Ident, PUBLISH) => {
160 self.inner().advance(Tok::LParen)?;
161 let staged_package = self.inner().advance(Tok::Ident)?;
162 self.inner().advance(Tok::Comma)?;
163 self.inner().advance(Tok::LBracket)?;
164 let dependencies = self.inner().parse_list(
165 |p| Ok(p.advance(Tok::Ident)?.to_owned()),
166 CommandToken::Comma,
167 Tok::RBracket,
168 true,
170 )?;
171 self.inner().advance(Tok::RBracket)?;
172 self.maybe_trailing_comma()?;
173 self.inner().advance(Tok::RParen)?;
174 ParsedCommand::Publish(staged_package.to_owned(), dependencies)
175 }
176 (Tok::Ident, UPGRADE) => {
177 self.inner().advance(Tok::LParen)?;
178 let staged_package = self.inner().advance(Tok::Ident)?;
179 self.inner().advance(Tok::Comma)?;
180 self.inner().advance(Tok::LBracket)?;
181 let dependencies = self.inner().parse_list(
182 |p| Ok(p.advance(Tok::Ident)?.to_owned()),
183 CommandToken::Comma,
184 Tok::RBracket,
185 true,
187 )?;
188 self.inner().advance(Tok::RBracket)?;
189 self.inner().advance(Tok::Comma)?;
190 let upgraded_package = self.inner().advance(Tok::Ident)?;
191 self.inner().advance(Tok::Comma)?;
192 let upgrade_ticket = self.parse_command_arg()?;
193 self.maybe_trailing_comma()?;
194 self.inner().advance(Tok::RParen)?;
195 ParsedCommand::Upgrade(
196 staged_package.to_owned(),
197 dependencies,
198 upgraded_package.to_owned(),
199 upgrade_ticket,
200 )
201 }
202 (Tok::Ident, contents) => {
203 let package = Identifier::new(contents)?;
204 self.inner().advance(Tok::ColonColon)?;
205 let module = Identifier::new(self.inner().advance(Tok::Ident)?)?;
206 self.inner().advance(Tok::ColonColon)?;
207 let function = Identifier::new(self.inner().advance(Tok::Ident)?)?;
208 let type_arguments = self.parse_type_args_opt()?.unwrap_or_default();
209 let arguments = self.parse_command_args(Tok::LParen, Tok::RParen)?;
210 let call = ParsedMoveCall {
211 package,
212 module,
213 function,
214 type_arguments,
215 arguments,
216 };
217 ParsedCommand::MoveCall(Box::new(call))
218 }
219
220 (tok, _) => bail!("unexpected token {}, expected command identifier", tok),
221 })
222 }
223
224 pub fn maybe_trailing_comma(&mut self) -> Result<()> {
225 if let Some(CommandToken::Comma) = self.inner().peek_tok() {
226 self.inner().advance(CommandToken::Comma)?;
227 }
228 Ok(())
229 }
230
231 pub fn parse_command_args(
232 &mut self,
233 start: CommandToken,
234 end: CommandToken,
235 ) -> Result<Vec<Argument>> {
236 self.inner().advance(start)?;
237 let args = self.inner().parse_list(
238 |p| CommandParser::from_parser(p).parse_command_arg(),
239 CommandToken::Comma,
240 end,
241 true,
243 )?;
244 self.inner().advance(end)?;
245 Ok(args)
246 }
247
248 pub fn parse_command_arg(&mut self) -> Result<Argument> {
249 use super::token::CommandToken as Tok;
250 Ok(match self.inner().advance_any()? {
251 (Tok::Ident, GAS_COIN) => Argument::GasCoin,
252 (Tok::Ident, INPUT) => {
253 self.inner().advance(Tok::LParen)?;
254 let num = self.parse_u16()?;
255 self.maybe_trailing_comma()?;
256 self.inner().advance(Tok::RParen)?;
257 Argument::Input(num)
258 }
259 (Tok::Ident, RESULT) => {
260 self.inner().advance(Tok::LParen)?;
261 let num = self.parse_u16()?;
262 self.maybe_trailing_comma()?;
263 self.inner().advance(Tok::RParen)?;
264 Argument::Result(num)
265 }
266 (Tok::Ident, NESTED_RESULT) => {
267 self.inner().advance(Tok::LParen)?;
268 let i = self.parse_u16()?;
269 self.inner().advance(Tok::Comma)?;
270 let j = self.parse_u16()?;
271 self.maybe_trailing_comma()?;
272 self.inner().advance(Tok::RParen)?;
273 Argument::NestedResult(i, j)
274 }
275 (tok, _) => bail!("unexpected token {}, expected argument identifier", tok),
276 })
277 }
278
279 pub fn parse_u16(&mut self) -> Result<u16> {
280 let contents = self.inner().advance(CommandToken::Number)?;
281 u16::from_str(contents).context("Expected u16 for Argument")
282 }
283
284 pub fn parse_type_arg_opt(&mut self) -> Result<Option<ParsedType>> {
285 match self.parse_type_args_opt()? {
286 None => Ok(None),
287 Some(v) if v.len() != 1 => bail!(
288 "unexpected multiple type arguments. Expected 1 type argument but got {}",
289 v.len()
290 ),
291 Some(mut v) => Ok(Some(v.pop().unwrap())),
292 }
293 }
294
295 pub fn parse_type_args_opt(&mut self) -> Result<Option<Vec<ParsedType>>> {
296 if !matches!(self.inner().peek_tok(), Some(CommandToken::TypeArgString)) {
297 return Ok(None);
298 }
299 let contents = self.inner().advance(CommandToken::TypeArgString)?;
300 let type_tokens: Vec<_> = TypeToken::tokenize(contents)?
301 .into_iter()
302 .filter(|(tok, _)| !tok.is_whitespace())
303 .collect();
304 let mut parser = Parser::new(type_tokens);
305 parser.advance(TypeToken::Lt)?;
306 let res = parser.parse_list(|p| p.parse_type(), TypeToken::Comma, TypeToken::Gt, true)?;
307 parser.advance(TypeToken::Gt)?;
308 if let Ok((_, contents)) = parser.advance_any() {
309 bail!("Expected end of token stream. Got: {}", contents)
310 }
311 Ok(Some(res))
312 }
313
314 pub fn inner(&mut self) -> &mut Parser<'a, CommandToken, I> {
315 self.inner.borrow_mut()
316 }
317}
318
319impl ParsedCommand {
320 pub fn parse_vec(s: &str) -> Result<Vec<Self>> {
321 let tokens: Vec<_> = CommandToken::tokenize(s)?
322 .into_iter()
323 .filter(|(tok, _)| !tok.is_whitespace())
324 .collect();
325 let mut parser = CommandParser::new(tokens);
326 let res = parser.parse_commands()?;
327 if let Ok((_, contents)) = parser.inner().advance_any() {
328 bail!("Expected end of token stream. Got: {}", contents)
329 }
330 Ok(res)
331 }
332
333 pub fn into_command(
334 self,
335 staged_packages: &impl Fn(&str) -> Option<Vec<Vec<u8>>>,
336 address_mapping: &impl Fn(&str) -> Option<AccountAddress>,
337 ) -> Result<Command> {
338 Ok(match self {
339 ParsedCommand::MoveCall(c) => {
340 Command::MoveCall(Box::new(c.into_move_call(address_mapping)?))
341 }
342 ParsedCommand::TransferObjects(objs, recipient) => {
343 Command::TransferObjects(objs, recipient)
344 }
345 ParsedCommand::SplitCoins(coin, amts) => Command::SplitCoins(coin, amts),
346 ParsedCommand::MergeCoins(target, coins) => Command::MergeCoins(target, coins),
347 ParsedCommand::MakeMoveVec(ty_opt, args) => Command::MakeMoveVec(
348 ty_opt
349 .map(|t| t.into_type_tag(address_mapping))
350 .transpose()?,
351 args,
352 ),
353 ParsedCommand::Publish(staged_package, dependencies) => {
354 let Some(package_contents) = staged_packages(&staged_package) else {
355 bail!("No staged package '{staged_package}'");
356 };
357 let dependencies = dependencies
358 .into_iter()
359 .map(|d| match address_mapping(&d) {
360 Some(a) => Ok(a.into()),
361 None => bail!("Unbound dependency '{d}"),
362 })
363 .collect::<Result<Vec<ObjectID>>>()?;
364 Command::Publish(package_contents, dependencies)
365 }
366 ParsedCommand::Upgrade(staged_package, dependencies, upgraded_package, ticket) => {
367 let Some(package_contents) = staged_packages(&staged_package) else {
368 bail!("No staged package '{staged_package}'");
369 };
370 let dependencies = dependencies
371 .into_iter()
372 .map(|d| match address_mapping(&d) {
373 Some(a) => Ok(a.into()),
374 None => bail!("Unbound dependency '{d}"),
375 })
376 .collect::<Result<Vec<ObjectID>>>()?;
377 let Some(upgraded_package) = address_mapping(&upgraded_package) else {
378 bail!("Unbound upgraded package '{upgraded_package}'");
379 };
380 let upgraded_package = upgraded_package.into();
381 Command::Upgrade(package_contents, dependencies, upgraded_package, ticket)
382 }
383 })
384 }
385}
386
387impl ParsedMoveCall {
388 pub fn into_move_call(
389 self,
390 address_mapping: &impl Fn(&str) -> Option<AccountAddress>,
391 ) -> Result<ProgrammableMoveCall> {
392 let Self {
393 package,
394 module,
395 function,
396 type_arguments,
397 arguments,
398 } = self;
399 let Some(package) = address_mapping(package.as_str()) else {
400 bail!("Unable to resolve package {}", package)
401 };
402 let type_arguments = type_arguments
403 .into_iter()
404 .map(|t| t.into_type_tag(address_mapping))
405 .collect::<Result<_>>()?;
406 Ok(ProgrammableMoveCall {
407 package: package.into(),
408 module,
409 function,
410 type_arguments,
411 arguments,
412 })
413 }
414}