Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve query inteface #1447

Merged
merged 3 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ impl QueryPath {
/// Identifiers can be enclosed by double quotes when they contain characters that aren't
/// allowed inside bare identifiers. The accepted grammar is the same as a sequence of record
/// accesses in Nickel, although string interpolation is forbidden.
///
/// # Post-conditions
///
/// If this function succeeds and returns `Ok(query_path)`, then `query_path.0` is non empty.
/// Indeed, there's no such thing as a valid empty field path. If `input` is empty, or consists
/// only of spaces, `parse` returns a parse error.
pub fn parse(cache: &mut Cache, input: String) -> Result<Self, ParseError> {
use crate::parser::{
grammar::FieldPathParser, lexer::Lexer, utils::FieldPathElem, ErrorTolerantParser,
Expand Down
22 changes: 2 additions & 20 deletions core/src/repl/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ impl CommandType {
pub enum Command {
Load(OsString),
Typecheck(String),
Query {
target: String,
path: Option<String>,
},
Query(String),
Print(String),
Help(Option<String>),
Exit,
Expand Down Expand Up @@ -130,22 +127,7 @@ impl FromStr for Command {
}
CommandType::Query => {
require_arg(cmd, &arg, None)?;
let mut args_iter = arg.chars();

let first_arg = args_iter.by_ref().take_while(|c| *c != ' ').collect();
let rest = args_iter.collect::<String>();
let rest = rest.trim();

let path = if !rest.is_empty() {
Some(String::from(rest))
} else {
None
};

Ok(Command::Query {
target: first_arg,
path,
})
Ok(Command::Query(arg))
}
CommandType::Print => {
require_arg(cmd, &arg, None)?;
Expand Down
25 changes: 17 additions & 8 deletions core/src/repl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub trait Repl {
/// Typecheck an expression and return its [apparent type][crate::typecheck::ApparentType].
fn typecheck(&mut self, exp: &str) -> Result<Types, Error>;
/// Query the metadata of an expression.
fn query(&mut self, target: String, path: Option<String>) -> Result<Field, Error>;
fn query(&mut self, path: String) -> Result<Field, Error>;
/// Required for error reporting on the frontend.
fn cache_mut(&mut self) -> &mut Cache;
}
Expand Down Expand Up @@ -285,15 +285,21 @@ impl<EC: EvalCache> Repl for ReplImpl<EC> {
.into())
}

fn query(&mut self, target: String, path: Option<String>) -> Result<Field, Error> {
fn query(&mut self, path: String) -> Result<Field, Error> {
use crate::program;

let mut query_path = QueryPath::parse(self.vm.import_resolver_mut(), path)?;

// remove(): this is safe because there is no such thing as an empty field path. If `path`
// is empty, the parser will error out. Hence, `QueryPath::parse` always returns a non-empty
// vector.
let target = query_path.0.remove(0);

let file_id = self
.vm
.import_resolver_mut()
.replace_string("<repl-query>", target);
.replace_string("<repl-query>", target.label().into());

let query_path = QueryPath::parse_opt(self.vm.import_resolver_mut(), path)?;
program::query(&mut self.vm, file_id, &self.env, query_path)
}

Expand Down Expand Up @@ -415,15 +421,18 @@ pub fn print_help(out: &mut impl Write, arg: Option<&str>) -> std::io::Result<()
)?;
}
Ok(c @ CommandType::Query) => {
writeln!(out, ":{c} <identifier> [field path]")?;
writeln!(out, ":{c} <field path>")?;
print_aliases(out, c)?;
writeln!(out, "Print the metadata attached to a field")?;
writeln!(
out,
"<identifier> is valid Nickel identifier representing the record to look into."
"<field path> is a dot-separated sequence of identifiers pointing to a field. \
Fields can be quoted if they contain special characters, \
just like in normal Nickel source code.\n"
)?;
writeln!(out, "<field path> is a dot-separated sequence of identifiers pointing to a field.\n")?;
writeln!(out, "Example: `:{c} mylib contracts.\"special#chars\".bar`")?;
writeln!(out, "Examples:")?;
writeln!(out, "- `:{c} std.array.any`")?;
writeln!(out, "- `:{c} mylib.contracts.\"special#chars.\".bar`")?;
}
Ok(c @ CommandType::Load) => {
writeln!(out, ":{c} <file>")?;
Expand Down
2 changes: 1 addition & 1 deletion core/src/repl/rustyline_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub fn repl(histfile: PathBuf, color_opt: ColorOpt) -> Result<(), InitError> {
Ok(Command::Typecheck(exp)) => {
repl.typecheck(&exp).map(|types| println!("Ok: {types}"))
}
Ok(Command::Query {target, path}) => repl.query(target, path).map(|field| {
Ok(Command::Query(path)) => repl.query(path).map(|field| {
query_print::write_query_result(
&mut stdout,
&field,
Expand Down
4 changes: 2 additions & 2 deletions core/src/repl/simple_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ pub fn input<R: Repl>(repl: &mut R, line: &str) -> Result<InputResult, InputErro
.typecheck(&exp)
.map(|types| InputResult::Success(format!("Ok: {types}")))
.map_err(InputError::from),
Ok(Command::Query { target, path }) => repl
.query(target, path)
Ok(Command::Query(path)) => repl
.query(path)
.map(|t| {
let mut buffer = Cursor::new(Vec::<u8>::new());
query_print::write_query_result(
Expand Down