Skip to content

Commit e02c2da

Browse files
move call/encode as value, instead of command (#7)
* move call/encode as value, instead of command * fix * readme * decode Co-authored-by: chenyan-dfinity <[email protected]>
1 parent 88566b1 commit e02c2da

File tree

5 files changed

+191
-135
lines changed

5 files changed

+191
-135
lines changed

README.md

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,24 @@ ic-repl --replica [local|ic|url] --config <dhall config> [script file]
88

99
```
1010
<command> :=
11-
| import <id> = <text> ( : <text> )? // bind canister URI to <id>, with optional did file
12-
| call <name> . <name> ( <val>,* ) // call a canister method with candid arguments
13-
| encode <name> . <name> ( <val>,* ) // encode candid arguments with respect to a canister method signature
14-
| export <text> // export command history to a file that can be run in ic-repl as a script
15-
| load <text> // load and run a script file
16-
| config <text> // set config for random value generator in dhall format
17-
| let <id> = <val> // bind <val> to a variable <id>
18-
| <val> // show the value of <val>
19-
| assert <val> <binop> <val> // assertion
20-
| identity <id> <text>? // switch to identity <id>, with optional Ed25519 pem file
21-
22-
<val> :=
23-
| <candid val> // any candid value
24-
| <var> <selector>* // variable with optional selectors
25-
| file <text> // load external file as a blob value
26-
| encode ( <val),* ) // encode candid arguments as a blob value, use the `encode` command if you can
11+
| import <id> = <text> (as <text>)? // bind canister URI to <id>, with optional did file
12+
| export <text> // export command history to a file that can be run in ic-repl as a script
13+
| load <text> // load and run a script file
14+
| config <text> // set config for random value generator in dhall format
15+
| let <id> = <exp> // bind <exp> to a variable <id>
16+
| <exp> // show the value of <exp>
17+
| assert <exp> <binop> <exp> // assertion
18+
| identity <id> <text>? // switch to identity <id>, with optional Ed25519 pem file
19+
<exp> :=
20+
| <candid val> // any candid value
21+
| <var> <selector>* // variable with optional selectors
22+
| file <text> // load external file as a blob value
23+
| call <name> . <name> ( <exp>,* ) // call a canister method, and store the result as a single value
24+
| encode (<name> . <name>)? ( <exp>,* ) // encode candid arguments as a blob value
25+
| decode (as <name> . <name>)? <exp> // decode blob as candid values
2726
<var> :=
2827
| <id> // variable name
29-
| _ // previous call result is bind to `_`
28+
| _ // previous eval of exp is bind to `_`
3029
<selector> :=
3130
| ? // select opt value
3231
| . <name> // select field name from record or variant value
@@ -55,8 +54,7 @@ assert _ == result;
5554
install.sh
5655
```
5756
#!/usr/bin/ic-repl
58-
call "aaaaa-aa".provisional_create_canister_with_cycles(record { settings: null; amount: null });
59-
let id = _;
57+
let id = call "aaaaa-aa".provisional_create_canister_with_cycles(record { settings: null; amount: null });
6058
call "aaaaa-aa".install_code(
6159
record {
6260
arg = encode ();
@@ -73,7 +71,7 @@ call canister.greet("test");
7371
wallet.sh
7472
```
7573
#!/usr/bin/ic-repl
76-
import wallet = "rwlgt-iiaaa-aaaaa-aaaaa-cai" : "wallet.did";
74+
import wallet = "rwlgt-iiaaa-aaaaa-aaaaa-cai" as "wallet.did";
7775
identity default "~/.config/dfx/identity/default/identity.pem";
7876
call wallet.wallet_create_canister(
7977
record {
@@ -86,26 +84,25 @@ call wallet.wallet_create_canister(
8684
};
8785
},
8886
)
89-
let id = _.Ok;
90-
encode "aaaaa-aa".install_code(
87+
let id = _.Ok.canister_id;
88+
let msg = encode "aaaaa-aa".install_code(
9189
record {
9290
arg = encode ();
9391
wasm_module = file "your_wasm_file.wasm";
9492
mode = variant { install };
95-
canister_id = id.canister_id;
93+
canister_id = id;
9694
},
9795
);
98-
let msg = _;
99-
call wallet.wallet_call(
96+
let res = call wallet.wallet_call(
10097
record {
10198
args = msg;
10299
cycles = 0;
103100
method_name = "install_code";
104101
canister = principal "aaaaa-aa";
105102
},
106103
);
107-
let canister = id.canister_id;
108-
call canister.greet("test");
104+
decode as "aaaaa-aa".install_code res.Ok.return;
105+
call id.greet("test");
109106
```
110107

111108
## Notes for Rust canisters
@@ -131,9 +128,8 @@ If you are writing your own `.did` file, you can also supply the did file via th
131128
* Acess to service init type
132129
* `IDLValue::Blob` for efficient blob serialization
133130
* Tokenization for partial parser (variable needs a preceding space for autocompletion)
134-
* Autocompletion within Candid value
131+
* Rename `Value` module to `Exp`
132+
* Autocompletion within Candid value; autocompletion for decode method
135133
* Robust support for `~=`, requires inferring principal types
136-
* Bind multiple return values to `_`
137134
* Loop detection for `load`
138-
* Import external identity
139135
* Assert upgrade correctness

src/command.rs

Lines changed: 10 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ use super::helper::{did_to_canister_info, MyHelper};
33
use super::token::{ParserError, Tokenizer};
44
use super::value::Value;
55
use anyhow::{anyhow, Context};
6-
use candid::{
7-
parser::configs::Configs, parser::value::IDLValue, types::Function, IDLArgs, Principal, TypeEnv,
8-
};
6+
use candid::{parser::configs::Configs, parser::value::IDLValue, Principal, TypeEnv};
97
use ic_agent::Agent;
108
use pretty_assertions::{assert_eq, assert_ne};
119
use std::path::{Path, PathBuf};
@@ -16,12 +14,6 @@ use terminal_size::{terminal_size, Width};
1614
pub struct Commands(pub Vec<Command>);
1715
#[derive(Debug, Clone)]
1816
pub enum Command {
19-
Call {
20-
canister: String,
21-
method: String,
22-
args: Vec<Value>,
23-
encode_only: bool,
24-
},
2517
Config(String),
2618
Show(Value),
2719
Let(String, Value),
@@ -41,54 +33,6 @@ pub enum BinOp {
4133
impl Command {
4234
pub fn run(self, helper: &mut MyHelper) -> anyhow::Result<()> {
4335
match self {
44-
Command::Call {
45-
canister,
46-
method,
47-
args,
48-
encode_only,
49-
} => {
50-
let try_id = Principal::from_text(&canister);
51-
let canister_id = match try_id {
52-
Ok(ref id) => id,
53-
Err(_) => match helper.env.0.get(&canister) {
54-
Some(IDLValue::Principal(id)) => id,
55-
_ => return Err(anyhow!("{} is not a canister id", canister)),
56-
},
57-
};
58-
let agent = &helper.agent;
59-
let mut map = helper.canister_map.borrow_mut();
60-
let info = map.get(&agent, &canister_id)?;
61-
let func = info
62-
.methods
63-
.get(&method)
64-
.ok_or_else(|| anyhow!("no method {}", method))?;
65-
let mut values = Vec::new();
66-
for arg in args.into_iter() {
67-
values.push(arg.eval(&helper)?);
68-
}
69-
let args = IDLArgs { args: values };
70-
if encode_only {
71-
let bytes = args.to_bytes_with_types(&info.env, &func.args)?;
72-
let res = IDLValue::Vec(bytes.into_iter().map(IDLValue::Nat8).collect());
73-
println!("{}", res);
74-
helper.env.0.insert("_".to_string(), res);
75-
return Ok(());
76-
}
77-
let time = Instant::now();
78-
let res = call(&agent, &canister_id, &method, &args, &info.env, &func)?;
79-
let duration = time.elapsed();
80-
println!("{}", res);
81-
let width = if let Some((Width(w), _)) = terminal_size() {
82-
w as usize
83-
} else {
84-
80
85-
};
86-
println!("{:>width$}", format!("({:.2?})", duration), width = width);
87-
// TODO multiple values
88-
for arg in res.args.into_iter() {
89-
helper.env.0.insert("_".to_string(), arg);
90-
}
91-
}
9236
Command::Import(id, canister_id, did) => {
9337
if let Some(did) = &did {
9438
let path = resolve_path(&helper.base_path, did);
@@ -130,8 +74,17 @@ impl Command {
13074
}
13175
Command::Config(conf) => helper.config = Configs::from_dhall(&conf)?,
13276
Command::Show(val) => {
77+
let time = Instant::now();
13378
let v = val.eval(&helper)?;
79+
let duration = time.elapsed();
13480
println!("{}", v);
81+
helper.env.0.insert("_".to_string(), v);
82+
let width = if let Some((Width(w), _)) = terminal_size() {
83+
w as usize
84+
} else {
85+
80
86+
};
87+
println!("{:>width$}", format!("({:.2?})", duration), width = width);
13588
}
13689
Command::Identity(id, opt_pem) => {
13790
use ic_agent::Identity;
@@ -216,38 +169,6 @@ impl std::str::FromStr for Commands {
216169
}
217170
}
218171

219-
#[tokio::main]
220-
async fn call(
221-
agent: &Agent,
222-
canister_id: &Principal,
223-
method: &str,
224-
args: &IDLArgs,
225-
env: &TypeEnv,
226-
func: &Function,
227-
) -> anyhow::Result<IDLArgs> {
228-
let args = args.to_bytes_with_types(env, &func.args)?;
229-
let bytes = if func.is_query() {
230-
agent
231-
.query(canister_id, method)
232-
.with_arg(args)
233-
.with_effective_canister_id(canister_id.clone())
234-
.call()
235-
.await?
236-
} else {
237-
let waiter = delay::Delay::builder()
238-
.exponential_backoff(std::time::Duration::from_secs(1), 1.1)
239-
.timeout(std::time::Duration::from_secs(60 * 5))
240-
.build();
241-
agent
242-
.update(canister_id, method)
243-
.with_arg(args)
244-
.with_effective_canister_id(canister_id.clone())
245-
.call_and_wait(waiter)
246-
.await?
247-
};
248-
Ok(IDLArgs::from_bytes_with_types(&bytes, env, &func.rets)?)
249-
}
250-
251172
pub fn resolve_path(base: &Path, file: &str) -> PathBuf {
252173
let file = PathBuf::from(shellexpand::tilde(file).into_owned());
253174
if file.is_absolute() {

src/grammar.lalrpop

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::value::{Field, Value, Selector};
1+
use super::value::{Field, Value, Selector, Method};
22
use candid::parser::types::{IDLType, TypeField, PrimType, FuncType, FuncMode, Binding};
33
use candid::parser::typing::{TypeEnv, check_unique};
44
use super::token::{Token, error2, LexicalError, Span};
@@ -34,6 +34,8 @@ extern {
3434
"principal" => Token::Principal,
3535
"call" => Token::Call,
3636
"encode" => Token::Encode,
37+
"decode" => Token::Decode,
38+
"as" => Token::As,
3739
"config" => Token::Config,
3840
"assert" => Token::Assert,
3941
"let" => Token::Let,
@@ -63,19 +65,13 @@ pub Commands: Commands = SepBy<Command, ";"> => Commands(<>);
6365

6466
// Command
6567
pub Command: Command = {
66-
"call" <canister:Name> "." <method:Name> "(" <args: SepBy<Value, ",">> ")"? => {
67-
Command::Call{canister, method, args, encode_only: false}
68-
},
69-
"encode" <canister:Name> "." <method:Name> "(" <args: SepBy<Value, ",">> ")"? => {
70-
Command::Call{canister, method, args, encode_only: true}
71-
},
7268
"config" <Text> => Command::Config(<>),
73-
<Value> => Command::Show(<>),
69+
Value => Command::Show(<>),
7470
"assert" <left:Value> <op:BinOp> <right:Value> => Command::Assert(op, left, right),
7571
"let" <id:"id"> "=" <val:Value> => Command::Let(id, val),
7672
"export" <Text> => Command::Export(<>),
7773
"load" <Text> => Command::Load(<>),
78-
"import" <id:"id"> "=" <uri:Sp<Text>> <did:(":" <Text>)?> =>? {
74+
"import" <id:"id"> "=" <uri:Sp<Text>> <did:("as" <Text>)?> =>? {
7975
let principal = Principal::from_text(&uri.0).map_err(|e| error2(e, uri.1))?;
8076
Ok(Command::Import(id, principal, did))
8177
},
@@ -84,10 +80,13 @@ pub Command: Command = {
8480

8581
pub Value: Value = {
8682
Arg => <>,
87-
<v:"id"> <path:(<Selector>)*> => Value::Path(v, path),
83+
Variable => <>,
8884
"file" <Text> => Value::Blob(<>),
89-
"encode" <Values> => Value::Args(<>),
85+
"call" <method:Method> <args:Values> => Value::Method{method:Some(method), args, encode_only: false},
86+
"encode" <method:Method?> <args:Values> => Value::Method{method, args, encode_only: true},
87+
"decode" <method:("as" <Method>)?> <blob:Value> => Value::Decode{method, blob:Box::new(blob)},
9088
}
89+
Variable: Value = <v:"id"> <path:(<Selector>)*> => Value::Path(v, path);
9190
Selector: Selector = {
9291
"?" => Selector::Field("?".to_string()),
9392
"." <Name> => Selector::Field(<>),
@@ -96,6 +95,7 @@ Selector: Selector = {
9695
Ok(Selector::Index(idx))
9796
}
9897
}
98+
Method: Method = <canister:Name> "." <method:Name> => Method { canister, method };
9999

100100
BinOp: BinOp = {
101101
"==" => BinOp::Equal,

src/token.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ pub enum Token {
6464
Call,
6565
#[token("encode")]
6666
Encode,
67+
#[token("decode")]
68+
Decode,
69+
#[token("as")]
70+
As,
6771
#[token("config")]
6872
Config,
6973
#[token("let")]

0 commit comments

Comments
 (0)