Skip to content

Commit

Permalink
Define functions (#17)
Browse files Browse the repository at this point in the history
* define functions

* clone helper

* update examples

* bump version

Co-authored-by: Yan Chen <yan.chen@dfinity.org>
  • Loading branch information
chenyan2002 and chenyan-dfinity authored Oct 15, 2021
1 parent f9aa48e commit 9823a6b
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 162 deletions.
143 changes: 68 additions & 75 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[package]
name = "ic-repl"
version = "0.1.0"
version = "0.1.1"
authors = ["DFINITY Team"]
edition = "2018"

[build-dependencies]
lalrpop = "0.19"

[dependencies]
candid = { git = "https://github.com/dfinity/candid.git", branch = "master", features = ["random"] }
candid = { version = "0.7.8", features = ["random"] }
rustyline = "8.2"
rustyline-derive = "0.4"
ansi_term = "0.12"
Expand All @@ -18,7 +18,7 @@ codespan-reporting = "0.11"
pretty = "0.10.0"
pem = "0.8"
shellexpand = "2.1"
ic-agent = { git = "https://github.com/dfinity/agent-rs.git", branch = "main" }
ic-agent = "0.9.0"
tokio = { version = "1.6.0", features = ["full"] }
garcon = "0.2.3"
anyhow = "1.0"
Expand Down
76 changes: 53 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ ic-repl --replica [local|ic|url] --config <dhall config> [script file]

```
<command> :=
| import <id> = <text> (as <text>)? // bind canister URI to <id>, with optional did file
| export <text> // export current environment variables
| load <text> // load and run a script file
| config <text> // set config for random value generator in dhall format
| let <id> = <exp> // bind <exp> to a variable <id>
| <exp> // show the value of <exp>
| assert <exp> <binop> <exp> // assertion
| identity <id> <text>? // switch to identity <id>, with optional Ed25519 pem file
| import <id> = <text> (as <text>)? // bind canister URI to <id>, with optional did file
| export <text> // export current environment variables
| load <text> // load and run a script file
| config <text> // set config for random value generator in dhall format
| let <id> = <exp> // bind <exp> to a variable <id>
| <exp> // show the value of <exp>
| assert <exp> <binop> <exp> // assertion
| identity <id> <text>? // switch to identity <id>, with optional Ed25519 pem file
| function <id> ( <id>,* ) { <command>;* } // define a function
<exp> :=
| <candid val> // any candid value
| <var> <selector>* // variable with optional selectors
Expand All @@ -24,7 +25,7 @@ ic-repl --replica [local|ic|url] --config <dhall config> [script file]
| call (as <name>)? <name> . <name> ( <exp>,* ) // call a canister method, and store the result as a single value
| encode (<name> . <name>)? ( <exp>,* ) // encode candid arguments as a blob value
| decode (as <name> . <name>)? <exp> // decode blob as candid values
| <func> ( <exp>,* ) // call built-in function
| <id> ( <exp>,* ) // function application
<var> :=
| <id> // variable name
| _ // previous eval of exp is bind to `_`
Expand All @@ -36,12 +37,9 @@ ic-repl --replica [local|ic|url] --config <dhall config> [script file]
| == // structural equality
| ~= // equal under candid subtyping; for text value, we check if the right side is contained in the left side
| != // not equal
<func> :=
| account // convert principal to account id
| neuron_account // convert (principal, nonce) to account in the governance canister
```

## Example
## Examples

### test.sh
```
Expand All @@ -61,22 +59,45 @@ assert _ == result;
#!/usr/bin/ic-repl -r ic
// nns and ledger canisters are auto-imported if connected to the mainnet
call nns.get_pending_proposals()
call ledger.account_balance_dfx(record { account = "..." })
identity private "./private.pem";
call ledger.account_balance_dfx(record { account = account(private) });
function stake_neuron(amount, memo) {
call ledger.send_dfx(
record {
to = neuron_account(private, memo);
fee = record { e8s = 10_000 };
memo = memo;
from_subaccount = null;
created_at_time = null;
amount = record { e8s = amount };
},
);
call nns.claim_or_refresh_neuron_from_account(
record { controller = opt private; memo = memo }
);
_.result?.NeuronId
};
stake_neuron(100_000_000, 42);
```

### install.sh
```
#!/usr/bin/ic-repl
function deploy(wasm) {
let id = call ic.provisional_create_canister_with_cycles(record { settings = null; amount = null });
call ic.install_code(
record {
arg = encode ();
wasm_module = wasm;
mode = variant { install };
canister_id = id.canister_id;
},
);
id
};
identity alice;
let id = call "aaaaa-aa".provisional_create_canister_with_cycles(record { settings = null; amount = null });
call ic.install_code(
record {
arg = encode ();
wasm_module = file "greet.wasm";
mode = variant { install };
canister_id = id.canister_id;
},
);
let id = deploy(file "greet.wasm");
let status = call ic.canister_status(id);
assert status.settings ~= record { controllers = vec { alice } };
assert status.module_hash? == blob "...";
Expand Down Expand Up @@ -112,6 +133,15 @@ call as wallet ic.install_code(
call id.greet("test");
```

## Functions

Similar to most shell languages, functions in ic-repl is dynamically scoped and untyped.
You cannot define recursive functions, as there is no control flow in the language.

We also provide built-in functions for the ledger account:
* account(principal): convert principal to account id.
* neuron_account(principal, nonce): convert (principal, nonce) to account in the governance canister.

## Derived forms

* `call as proxy_canister target_canister.method(args)` is a shorthand for
Expand Down
27 changes: 16 additions & 11 deletions examples/install.sh
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
#!/ic-repl
function deploy(wasm) {
let id = call ic.provisional_create_canister_with_cycles(record { settings = null; amount = null });
call ic.canister_status(id);
assert _.module_hash == (null : opt blob);
call ic.install_code(
record {
arg = encode ();
wasm_module = wasm;
mode = variant { install };
canister_id = id.canister_id;
},
);
id
};

identity alice;
let id = call ic.provisional_create_canister_with_cycles(record { settings = null; amount = null });
call ic.canister_status(id);
assert _.module_hash == (null : opt blob);
call ic.install_code(
record {
arg = encode ();
wasm_module = file "greet.wasm";
mode = variant { install };
canister_id = id.canister_id;
},
);
let id = deploy(file "greet.wasm");
let status = call ic.canister_status(id);
assert status.settings ~= record { controllers = vec { alice } };
assert status.module_hash? == blob "\d8\d1\d3;\a3\a65\a6\a6\c8!\06\12\d2\da\9dZ\e4v\8d\27\bd\05\9d\cc\1a\df\cb \01u\dc";
Expand Down
56 changes: 31 additions & 25 deletions examples/neuron.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ identity private "../private.pem";
let amount = 100_000_000; // 1 ICP
let memo = 42; // memo determines neuron id

call ledger.send_dfx(
record {
to = neuron_account(private, memo);
fee = record { e8s = 10_000 };
memo = memo;
from_subaccount = null;
created_at_time = null;
amount = record { e8s = amount };
},
);
function stake(amount, memo) {
call ledger.send_dfx(
record {
to = neuron_account(private, memo);
fee = record { e8s = 10_000 };
memo = memo;
from_subaccount = null;
created_at_time = null;
amount = record { e8s = amount };
},
);
call nns.claim_or_refresh_neuron_from_account(
record { controller = opt private; memo = memo }
);
};

call nns.claim_or_refresh_neuron_from_account(
record { controller = opt private; memo = memo }
);
stake(amount, memo);

let neuron_id = 3543344363; // The neuron_id is the return of the previous method call

Expand All @@ -42,19 +45,21 @@ let add_hot_key = variant {
let remove_hot_key = variant {
RemoveHotKey = record { hot_key_to_remove = opt hot_key }
};
function config_neuron(neuron_id, operation) {
call nns.manage_neuron(
record {
id = opt record { id = neuron_id };
command = opt variant {
Configure = record {
operation = opt operation;
}
};
neuron_id_or_subaccount = null;
},
);
};

// Choose a specific operation above to execute
call nns.manage_neuron(
record {
id = opt record { id = neuron_id };
command = opt variant {
Configure = record {
operation = opt dissolve_delay;
}
};
neuron_id_or_subaccount = null;
},
);
config_neuron(neuron_id, dissolve_delay);

// Disburse
call nns.manage_neuron(
Expand All @@ -66,3 +71,4 @@ call nns.manage_neuron(
neuron_id_or_subaccount = null;
},
);

48 changes: 27 additions & 21 deletions examples/wallet.sh
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
#!/usr/bin/ic-repl
function deploy(wallet, wasm, cycle) {
identity default "~/.config/dfx/identity/default/identity.pem";
call wallet.wallet_create_canister(
record {
cycles = cycle;
settings = record {
controller = null;
freezing_threshold = null;
memory_allocation = null;
compute_allocation = null;
};
},
);
let id = _.Ok.canister_id;
call as wallet ic.install_code(
record {
arg = encode ();
wasm_module = wasm;
mode = variant { install };
canister_id = id;
},
);
id
};

import wallet = "${WALLET_ID:-rwlgt-iiaaa-aaaaa-aaaaa-cai}" as "wallet.did";
identity default "~/.config/dfx/identity/default/identity.pem";
call wallet.wallet_create_canister(
record {
cycles = ${CYCLE:-1_000_000};
settings = record {
controller = null;
freezing_threshold = null;
memory_allocation = null;
compute_allocation = null;
};
},
);
let id = _.Ok.canister_id;
call as wallet ic.install_code(
record {
arg = encode ();
wasm_module = file "${WASM_FILE}";
mode = variant { install };
canister_id = id;
},
);
let id = deploy(wallet, file "greet.wasm", 1_000_000);
call id.greet("test");
8 changes: 8 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ pub enum Command {
Import(String, Principal, Option<String>),
Load(String),
Identity(String, Option<String>),
Func {
name: String,
args: Vec<String>,
body: Vec<Command>,
},
}
#[derive(Debug, Clone)]
pub enum BinOp {
Expand All @@ -49,6 +54,9 @@ impl Command {
let v = val.eval(helper)?;
helper.env.0.insert(id, v);
}
Command::Func { name, args, body } => {
helper.func_env.0.insert(name, (args, body));
}
Command::Assert(op, left, right) => {
let left = left.eval(helper)?;
let right = right.eval(helper)?;
Expand Down
23 changes: 22 additions & 1 deletion src/exp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,28 @@ impl Exp {
let account = AccountIdentifier::new(nns, Some(subaccount));
IDLValue::Text(account.to_hex())
}
_ => return Err(anyhow!("Unknown function {}", func)),
func => match helper.func_env.0.get(func) {
None => return Err(anyhow!("Unknown function {}", func)),
Some((formal_args, body)) => {
if formal_args.len() != args.len() {
return Err(anyhow!(
"{} expects {} arguments, but {} is provided",
func,
formal_args.len(),
args.len()
));
}
let mut helper = helper.spawn();
for (id, v) in formal_args.iter().zip(args.into_iter()) {
helper.env.0.insert(id.to_string(), v);
}
for cmd in body.iter() {
cmd.clone().run(&mut helper)?;
}
let res = helper.env.0.get("_").unwrap_or(&IDLValue::Null).clone();
res
}
},
}
}
Exp::Decode { method, blob } => {
Expand Down
2 changes: 2 additions & 0 deletions src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ extern {
"fail" => Token::Fail,
"file" => Token::File,
"identity" => Token::Identity,
"function" => Token::Function,
"sign" => Token::Sign(<char>),
"=" => Token::Equals,
"==" => Token::TestEqual,
Expand Down Expand Up @@ -77,6 +78,7 @@ pub Command: Command = {
Ok(Command::Import(id, principal, did))
},
"identity" <id:"id"> <path:Text?> => Command::Identity(id, path),
"function" <name:"id"> "(" <args:SepBy<"id", ",">> ")" "{" <body:SepBy<Command, ";">> "}" => Command::Func {name,args,body},
}

pub Exp: Exp = {
Expand Down
Loading

0 comments on commit 9823a6b

Please sign in to comment.