Skip to content

Commit

Permalink
Add a help command to the repo (#662)
Browse files Browse the repository at this point in the history
* add help command to repl

* add help command to META_PACKAGE_SYMBOLS_NAMES

* chore: cargo fmt

* move meta commands in sub-module

* Apply suggestions from code review

Co-authored-by: Arthur Paulino <arthurleonardo.ap@gmail.com>

* remove redundant comments and strings

* cargo fmt

* xclippy suggestions

---------

Co-authored-by: François Garillot <francois@garillot.net>
Co-authored-by: Arthur Paulino <arthurleonardo.ap@gmail.com>
  • Loading branch information
3 people authored Sep 18, 2023
1 parent 62ab3e7 commit 351e894
Show file tree
Hide file tree
Showing 3 changed files with 638 additions and 283 deletions.
338 changes: 56 additions & 282 deletions src/cli/repl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod meta_cmd;

use std::cell::RefCell;
use std::fs::read_to_string;
use std::process;
use std::rc::Rc;
use std::sync::Arc;

Expand All @@ -24,9 +25,7 @@ use crate::{
Evaluator, Frame, Witness, IO,
},
field::LurkField,
lurk_sym_ptr,
package::{Package, SymbolRef},
parser,
lurk_sym_ptr, parser,
proof::{nova::NovaProver, Prover},
ptr::Ptr,
public_parameters::public_params,
Expand All @@ -41,6 +40,8 @@ use crate::{

use super::lurk_proof::{LurkProof, LurkProofMeta};

use meta_cmd::MetaCmd;

#[derive(Completer, Helper, Highlighter, Hinter)]
struct InputValidator {
brackets: MatchingBracketValidator,
Expand Down Expand Up @@ -68,6 +69,7 @@ pub struct Repl<F: LurkField> {
limit: usize,
backend: Backend,
evaluation: Option<Evaluation<F>>,
meta: std::collections::HashMap<&'static str, MetaCmd<F>>,
}

pub(crate) fn validate_non_zero(name: &str, x: usize) -> Result<()> {
Expand All @@ -85,6 +87,45 @@ fn pad(a: usize, m: usize) -> usize {
(a + m - 1) / m * m
}

impl<F: LurkField> Repl<F> {
fn peek1(&self, cmd: &str, args: &Ptr<F>) -> Result<Ptr<F>> {
let (first, rest) = self.store.car_cdr(args)?;
if !rest.is_nil() {
bail!("`{cmd}` accepts at most one argument")
}
Ok(first)
}

fn peek2(&self, cmd: &str, args: &Ptr<F>) -> Result<(Ptr<F>, Ptr<F>)> {
let (first, rest) = self.store.car_cdr(args)?;
let (second, rest) = self.store.car_cdr(&rest)?;
if !rest.is_nil() {
bail!("`{cmd}` accepts at most two arguments")
}
Ok((first, second))
}

fn get_string(&self, ptr: &Ptr<F>) -> Result<String> {
match self.store.fetch_string(ptr) {
None => bail!(
"Expected string. Got {}",
ptr.fmt_to_string(&self.store, &self.state.borrow())
),
Some(string) => Ok(string),
}
}

fn get_symbol(&self, ptr: &Ptr<F>) -> Result<Symbol> {
match self.store.fetch_symbol(ptr) {
None => bail!(
"Expected symbol. Got {}",
ptr.fmt_to_string(&self.store, &self.state.borrow())
),
Some(symbol) => Ok(symbol),
}
}
}

type F = pasta_curves::pallas::Scalar; // TODO: generalize this

impl Repl<F> {
Expand All @@ -104,6 +145,7 @@ impl Repl<F> {
limit,
backend,
evaluation: None,
meta: MetaCmd::cmds(),
}
}

Expand Down Expand Up @@ -329,23 +371,6 @@ impl Repl<F> {
Ok((last_output, iterations))
}

fn peek1(&self, cmd: &str, args: &Ptr<F>) -> Result<Ptr<F>> {
let (first, rest) = self.store.car_cdr(args)?;
if !rest.is_nil() {
bail!("`{cmd}` accepts at most one argument")
}
Ok(first)
}

fn peek2(&self, cmd: &str, args: &Ptr<F>) -> Result<(Ptr<F>, Ptr<F>)> {
let (first, rest) = self.store.car_cdr(args)?;
let (second, rest) = self.store.car_cdr(&rest)?;
if !rest.is_nil() {
bail!("`{cmd}` accepts at most two arguments")
}
Ok((first, second))
}

#[allow(dead_code)]
fn get_comm_hash(&mut self, cmd: &str, args: &Ptr<F>) -> Result<F> {
let first = self.peek1(cmd, args)?;
Expand All @@ -361,266 +386,6 @@ impl Repl<F> {
Ok(hash.into_scalar())
}

fn get_string(&self, ptr: &Ptr<F>) -> Result<String> {
match self.store.fetch_string(ptr) {
None => bail!(
"Expected string. Got {}",
ptr.fmt_to_string(&self.store, &self.state.borrow())
),
Some(string) => Ok(string),
}
}

fn get_symbol(&self, ptr: &Ptr<F>) -> Result<Symbol> {
match self.store.fetch_symbol(ptr) {
None => bail!(
"Expected symbol. Got {}",
ptr.fmt_to_string(&self.store, &self.state.borrow())
),
Some(symbol) => Ok(symbol),
}
}

fn handle_meta_cases(&mut self, cmd: &str, args: &Ptr<F>, pwd_path: &Utf8Path) -> Result<()> {
match cmd {
"def" => {
// Extends env with a non-recursive binding.
//
// This: !(:def foo (lambda () 123))
//
// Gets macroexpanded to this: (let ((foo (lambda () 123)))
// (current-env))
//
// And the state's env is set to the result.
let (first, second) = self.peek2(cmd, args)?;
let l = lurk_sym_ptr!(&self.store, let_);
let current_env = lurk_sym_ptr!(&self.store, current_env);
let binding = &self.store.list(&[first, second]);
let bindings = &self.store.list(&[*binding]);
let current_env_call = &self.store.list(&[current_env]);
let expanded = &self.store.list(&[l, *bindings, *current_env_call]);
let (expanded_io, ..) = self.eval_expr(*expanded)?;

self.env = expanded_io.expr;

let (new_binding, _) = &self.store.car_cdr(&expanded_io.expr)?;
let (new_name, _) = self.store.car_cdr(new_binding)?;
println!(
"{}",
new_name.fmt_to_string(&self.store, &self.state.borrow())
);
}
"defrec" => {
// Extends env with a recursive binding.
//
// This: !(:defrec foo (lambda () 123))
//
// Gets macroexpanded to this: (letrec ((foo (lambda () 123)))
// (current-env))
//
// And the state's env is set to the result.
let (first, second) = self.peek2(cmd, args)?;
let l = lurk_sym_ptr!(&self.store, letrec);
let current_env = lurk_sym_ptr!(&self.store, current_env);
let binding = &self.store.list(&[first, second]);
let bindings = &self.store.list(&[*binding]);
let current_env_call = &self.store.list(&[current_env]);
let expanded = &self.store.list(&[l, *bindings, *current_env_call]);
let (expanded_io, ..) = self.eval_expr(*expanded)?;

self.env = expanded_io.expr;

let (new_binding_outer, _) = &self.store.car_cdr(&expanded_io.expr)?;
let (new_binding_inner, _) = &self.store.car_cdr(new_binding_outer)?;
let (new_name, _) = self.store.car_cdr(new_binding_inner)?;
println!(
"{}",
new_name.fmt_to_string(&self.store, &self.state.borrow())
);
}
"load" => {
let first = self.peek1(cmd, args)?;
match self.store.fetch_string(&first) {
Some(path) => {
let joined = pwd_path.join(Utf8Path::new(&path));
self.load_file(&joined)?
}
_ => bail!("Argument of `load` must be a string."),
}
std::io::Write::flush(&mut std::io::stdout()).unwrap();
}
"assert" => {
let first = self.peek1(cmd, args)?;
let (first_io, ..) = self.eval_expr(first)?;
if first_io.expr.is_nil() {
eprintln!(
"`assert` failed. {} evaluates to nil",
first.fmt_to_string(&self.store, &self.state.borrow())
);
process::exit(1);
}
}
"assert-eq" => {
let (first, second) = self.peek2(cmd, args)?;
let (first_io, ..) = self
.eval_expr(first)
.with_context(|| "evaluating first arg")?;
let (second_io, ..) = self
.eval_expr(second)
.with_context(|| "evaluating second arg")?;
if !&self.store.ptr_eq(&first_io.expr, &second_io.expr)? {
eprintln!(
"`assert-eq` failed. Expected:\n {} = {}\nGot:\n {} ≠ {}",
first.fmt_to_string(&self.store, &self.state.borrow()),
second.fmt_to_string(&self.store, &self.state.borrow()),
first_io
.expr
.fmt_to_string(&self.store, &self.state.borrow()),
second_io
.expr
.fmt_to_string(&self.store, &self.state.borrow())
);
process::exit(1);
}
}
"assert-emitted" => {
let (first, second) = self.peek2(cmd, args)?;
let (first_io, ..) = self
.eval_expr(first)
.with_context(|| "evaluating first arg")?;
let (.., emitted) = self
.eval_expr(second)
.with_context(|| "evaluating second arg")?;
let (mut first_emitted, mut rest_emitted) = self.store.car_cdr(&first_io.expr)?;
for (i, elem) in emitted.iter().enumerate() {
if elem != &first_emitted {
eprintln!(
"`assert-emitted` failed at position {i}. Expected {}, but found {}.",
first_emitted.fmt_to_string(&self.store, &self.state.borrow()),
elem.fmt_to_string(&self.store, &self.state.borrow()),
);
process::exit(1);
}
(first_emitted, rest_emitted) = self.store.car_cdr(&rest_emitted)?;
}
}
"assert-error" => {
let first = self.peek1(cmd, args)?;
let (first_io, ..) = self.eval_expr_allowing_error_continuation(first)?;
if first_io.cont.tag != ContTag::Error {
eprintln!(
"`assert-error` failed. {} doesn't result on evaluation error.",
first.fmt_to_string(&self.store, &self.state.borrow())
);
process::exit(1);
}
}
"commit" => {
let first = self.peek1(cmd, args)?;
let (first_io, ..) = self.eval_expr(first)?;
self.hide(ff::Field::ZERO, first_io.expr)?;
}
"hide" => {
let (first, second) = self.peek2(cmd, args)?;
let (first_io, ..) = self
.eval_expr(first)
.with_context(|| "evaluating first arg")?;
let (second_io, ..) = self
.eval_expr(second)
.with_context(|| "evaluating second arg")?;
let Some(secret) = self.store.fetch_num(&first_io.expr) else {
bail!(
"Secret must be a number. Got {}",
first_io.expr.fmt_to_string(&self.store, &self.state.borrow())
)
};
self.hide(secret.into_scalar(), second_io.expr)?;
}
"fetch" => {
let hash = self.get_comm_hash(cmd, args)?;
self.fetch(&hash, false)?;
}
"open" => {
let hash = self.get_comm_hash(cmd, args)?;
self.fetch(&hash, true)?;
}
"clear" => self.env = lurk_sym_ptr!(&self.store, nil),
"set-env" => {
// The state's env is set to the result of evaluating the first argument.
let first = self.peek1(cmd, args)?;
let (first_io, ..) = self.eval_expr(first)?;
self.env = first_io.expr;
}
"prove" => {
if !args.is_nil() {
self.eval_expr_and_memoize(self.peek1(cmd, args)?)?;
}
self.prove_last_frames()?;
}
"verify" => {
let first = self.peek1(cmd, args)?;
let proof_id = self.get_string(&first)?;
LurkProof::verify_proof(&proof_id)?;
}
"defpackage" => {
// TODO: handle args
let (name, _args) = self.store.car_cdr(args)?;
let name = match name.tag {
ExprTag::Str => self.state.borrow_mut().intern(self.get_string(&name)?),
ExprTag::Sym => self.get_symbol(&name)?.into(),
_ => bail!("Package name must be a string or a symbol"),
};
println!("{}", self.state.borrow().fmt_to_string(&name));
let package = Package::new(name);
self.state.borrow_mut().add_package(package);
}
"import" => {
// TODO: handle pkg
let (mut symbols, _pkg) = self.store.car_cdr(args)?;
if symbols.tag == ExprTag::Sym {
let sym = SymbolRef::new(self.get_symbol(&symbols)?);
self.state.borrow_mut().import(&[sym])?;
} else {
let mut symbols_vec = vec![];
loop {
{
let (head, tail) = self.store.car_cdr(&symbols)?;
let sym = self.get_symbol(&head)?;
symbols_vec.push(SymbolRef::new(sym));
if tail.is_nil() {
break;
}
symbols = tail;
}
}
self.state.borrow_mut().import(&symbols_vec)?;
}
}
"in-package" => {
let first = self.peek1(cmd, args)?;
match first.tag {
ExprTag::Str => {
let name = self.get_string(&first)?;
let package_name = self.state.borrow_mut().intern(name);
self.state.borrow_mut().set_current_package(package_name)?;
}
ExprTag::Sym => {
let package_name = self.get_symbol(&first)?;
self.state
.borrow_mut()
.set_current_package(package_name.into())?;
}
_ => bail!(
"Expected string or symbol. Got {}",
first.fmt_to_string(&self.store, &self.state.borrow())
),
}
}
_ => bail!("Unsupported meta command: {cmd}"),
}
Ok(())
}

fn handle_non_meta(&mut self, expr_ptr: Ptr<F>) -> Result<()> {
self.eval_expr_and_memoize(expr_ptr)
.map(|(output, iterations)| {
Expand All @@ -643,7 +408,16 @@ impl Repl<F> {
fn handle_meta(&mut self, expr_ptr: Ptr<F>, pwd_path: &Utf8Path) -> Result<()> {
let (car, cdr) = self.store.car_cdr(&expr_ptr)?;
match &self.store.fetch_sym(&car) {
Some(symbol) => self.handle_meta_cases(symbol.name()?, &cdr, pwd_path)?,
Some(symbol) => {
let cmdstr = symbol.name()?;
match self.meta.get(cmdstr) {
Some(cmd) => match (cmd.run)(self, cmdstr, &cdr, pwd_path) {
Ok(()) => (),
Err(e) => println!("meta command failed with {}", e),
},
None => bail!("Unsupported meta command: {cmdstr}"),
}
}
None => bail!(
"Meta command must be a symbol. Found {}",
car.fmt_to_string(&self.store, &self.state.borrow())
Expand Down
Loading

0 comments on commit 351e894

Please sign in to comment.