From 5425a67e16e1dcb301f98bf58b3aa2af3085f561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Tue, 13 Jun 2023 14:38:34 +0200 Subject: [PATCH] feat: evaluator (#2816) --- prql-compiler/prqlc/src/cli.rs | 64 +- .../snapshots/test__shell_completion-2.snap | 52 +- .../snapshots/test__shell_completion-3.snap | 61 +- .../snapshots/test__shell_completion-4.snap | 170 +++++- .../snapshots/test__shell_completion.snap | 205 ++++++- prql-compiler/prqlc/tests/test.rs | 3 +- prql-compiler/src/semantic/eval/mod.rs | 558 ++++++++++++++++++ prql-compiler/src/semantic/mod.rs | 2 + 8 files changed, 1037 insertions(+), 78 deletions(-) create mode 100644 prql-compiler/src/semantic/eval/mod.rs diff --git a/prql-compiler/prqlc/src/cli.rs b/prql-compiler/prqlc/src/cli.rs index 34f504c4a7a9..d466b0d0b96e 100644 --- a/prql-compiler/prqlc/src/cli.rs +++ b/prql-compiler/prqlc/src/cli.rs @@ -5,6 +5,7 @@ use ariadne::Source; use clap::{CommandFactory, Parser, Subcommand, ValueHint}; use clio::Output; use itertools::Itertools; +use prql_compiler::ast::pl::StmtKind; use std::io::Write; use std::ops::Range; use std::path::PathBuf; @@ -75,11 +76,8 @@ enum Command { input: clio_extended::Input, }, - /// Parse, resolve & combine source with comments annotating relation type - Annotate(IoArgs), - - /// Parse & resolve, but don't lower into RQ - Debug(IoArgs), + #[command(subcommand)] + Debug(DebugCommand), /// Parse, resolve & lower into RQ Resolve { @@ -133,6 +131,22 @@ enum Command { }, } +/// Commands for meant for debugging, prone to change +#[derive(Subcommand, Debug, Clone)] +pub enum DebugCommand { + /// Parse & resolve, but don't lower into RQ + Semantics(IoArgs), + + /// Parse & evaluate expression down to a value + /// + /// Cannot contain references to tables or any other outside sources. + /// Meant as a playground for testing out language design decisions. + Eval(IoArgs), + + /// Parse, resolve & combine source with comments annotating relation type + Annotate(IoArgs), +} + #[derive(clap::Args, Default, Debug, Clone)] pub struct IoArgs { #[arg(value_parser, default_value = "-", value_hint(ValueHint::AnyPath))] @@ -233,7 +247,7 @@ impl Command { Format::Yaml => serde_yaml::to_string(&ast)?.into_bytes(), } } - Command::Debug(_) => { + Command::Debug(DebugCommand::Semantics(_)) => { semantic::load_std_lib(sources); let stmts = prql_to_pl_tree(sources)?; @@ -250,7 +264,7 @@ impl Command { out.extend(format!("\n{context:#?}\n").into_bytes()); out } - Command::Annotate(_) => { + Command::Debug(DebugCommand::Annotate(_)) => { let (_, source) = sources.sources.clone().into_iter().exactly_one().or_else( |_| bail!( "Currently `annotate` only works with a single source, but found multiple sources: {:?}", @@ -280,6 +294,29 @@ impl Command { // combine with source combine_prql_and_frames(&source, frames).as_bytes().to_vec() } + Command::Debug(DebugCommand::Eval(_)) => { + let stmts = prql_to_pl_tree(sources)?; + + let mut res = String::new(); + + for (path, stmts) in stmts.sources { + res += &format!("# {}\n\n", path.to_str().unwrap()); + + for stmt in stmts { + if let StmtKind::VarDef(def) = stmt.kind { + res += &format!("## {}\n", stmt.name); + + let val = semantic::eval(*def.value) + .map_err(downcast) + .map_err(|e| e.composed(sources))?; + res += &val.to_string(); + res += "\n\n"; + } + } + } + + res.into_bytes() + } Command::Resolve { format, .. } => { semantic::load_std_lib(sources); @@ -352,8 +389,9 @@ impl Command { | SQLCompile { io_args, .. } | SQLPreprocess(io_args) | SQLAnchor { io_args, .. } - | Debug(io_args) - | Annotate(io_args) => io_args, + | Debug(DebugCommand::Semantics(io_args)) + | Debug(DebugCommand::Annotate(io_args)) + | Debug(DebugCommand::Eval(io_args)) => io_args, _ => unreachable!(), }; let input = &mut io_args.input; @@ -382,8 +420,10 @@ impl Command { | Resolve { io_args, .. } | SQLCompile { io_args, .. } | SQLAnchor { io_args, .. } - | SQLPreprocess(io_args) => io_args.output.to_owned(), - Debug(io) | Annotate(io) => io.output.to_owned(), + | SQLPreprocess(io_args) + | Debug(DebugCommand::Semantics(io_args)) + | Debug(DebugCommand::Annotate(io_args)) + | Debug(DebugCommand::Eval(io_args)) => io_args.output.to_owned(), _ => unreachable!(), }; output.write_all(data) @@ -602,7 +642,7 @@ mod tests { #[test] fn layouts() { let output = Command::execute( - &Command::Annotate(IoArgs::default()), + &Command::Debug(DebugCommand::Annotate(IoArgs::default())), &mut r#" from initial_table select {f = first_name, l = last_name, gender} diff --git a/prql-compiler/prqlc/tests/snapshots/test__shell_completion-2.snap b/prql-compiler/prqlc/tests/snapshots/test__shell_completion-2.snap index b6c5507f0a32..ba93c43413f5 100644 --- a/prql-compiler/prqlc/tests/snapshots/test__shell_completion-2.snap +++ b/prql-compiler/prqlc/tests/snapshots/test__shell_completion-2.snap @@ -3,8 +3,11 @@ source: prql-compiler/prqlc/tests/test.rs info: program: prqlc args: + - "--color=never" - shell-completion - fish + env: + CLICOLOR_FORCE: "" --- success: true exit_code: 0 @@ -14,8 +17,7 @@ complete -c prqlc -n "__fish_use_subcommand" -s h -l help -d 'Print help' complete -c prqlc -n "__fish_use_subcommand" -s V -l version -d 'Print version' complete -c prqlc -n "__fish_use_subcommand" -f -a "parse" -d 'Parse into PL AST' complete -c prqlc -n "__fish_use_subcommand" -f -a "fmt" -d 'Parse & generate PRQL code back' -complete -c prqlc -n "__fish_use_subcommand" -f -a "annotate" -d 'Parse, resolve & combine source with comments annotating relation type' -complete -c prqlc -n "__fish_use_subcommand" -f -a "debug" -d 'Parse & resolve, but don\'t lower into RQ' +complete -c prqlc -n "__fish_use_subcommand" -f -a "debug" -d 'Commands for meant for debugging, prone to change' complete -c prqlc -n "__fish_use_subcommand" -f -a "resolve" -d 'Parse, resolve & lower into RQ' complete -c prqlc -n "__fish_use_subcommand" -f -a "sql:preprocess" -d 'Parse, resolve, lower into RQ & preprocess SRQ' complete -c prqlc -n "__fish_use_subcommand" -f -a "sql:anchor" -d 'Parse, resolve, lower into RQ & preprocess & anchor SRQ' @@ -29,10 +31,22 @@ complete -c prqlc -n "__fish_seen_subcommand_from parse" -l color -d 'Controls w complete -c prqlc -n "__fish_seen_subcommand_from parse" -s h -l help -d 'Print help' complete -c prqlc -n "__fish_seen_subcommand_from fmt" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" complete -c prqlc -n "__fish_seen_subcommand_from fmt" -s h -l help -d 'Print help' -complete -c prqlc -n "__fish_seen_subcommand_from annotate" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" -complete -c prqlc -n "__fish_seen_subcommand_from annotate" -s h -l help -d 'Print help' -complete -c prqlc -n "__fish_seen_subcommand_from debug" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" -complete -c prqlc -n "__fish_seen_subcommand_from debug" -s h -l help -d 'Print help' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" +complete -c prqlc -n "__fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -s h -l help -d 'Print help' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "semantics" -d 'Parse & resolve, but don\'t lower into RQ' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "eval" -d 'Parse & evaluate expression down to a value' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "annotate" -d 'Parse, resolve & combine source with comments annotating relation type' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from semantics" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from semantics" -s h -l help -d 'Print help' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from eval" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from eval" -s h -l help -d 'Print help (see more with \'--help\')' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from annotate" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from annotate" -s h -l help -d 'Print help' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "semantics" -d 'Parse & resolve, but don\'t lower into RQ' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "eval" -d 'Parse & evaluate expression down to a value' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "annotate" -d 'Parse, resolve & combine source with comments annotating relation type' +complete -c prqlc -n "__fish_seen_subcommand_from debug; and __fish_seen_subcommand_from help; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' complete -c prqlc -n "__fish_seen_subcommand_from resolve" -l format -r -f -a "{json ,yaml }" complete -c prqlc -n "__fish_seen_subcommand_from resolve" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" complete -c prqlc -n "__fish_seen_subcommand_from resolve" -s h -l help -d 'Print help' @@ -53,18 +67,20 @@ complete -c prqlc -n "__fish_seen_subcommand_from list-targets" -l color -d 'Con complete -c prqlc -n "__fish_seen_subcommand_from list-targets" -s h -l help -d 'Print help' complete -c prqlc -n "__fish_seen_subcommand_from shell-completion" -l color -d 'Controls when to use color' -r -f -a "{auto ,always ,never }" complete -c prqlc -n "__fish_seen_subcommand_from shell-completion" -s h -l help -d 'Print help' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "parse" -d 'Parse into PL AST' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "fmt" -d 'Parse & generate PRQL code back' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "annotate" -d 'Parse, resolve & combine source with comments annotating relation type' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "debug" -d 'Parse & resolve, but don\'t lower into RQ' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "resolve" -d 'Parse, resolve & lower into RQ' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "sql:preprocess" -d 'Parse, resolve, lower into RQ & preprocess SRQ' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "sql:anchor" -d 'Parse, resolve, lower into RQ & preprocess & anchor SRQ' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "compile" -d 'Parse, resolve, lower into RQ & compile to SQL' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "watch" -d 'Watch a directory and compile .prql files to .sql files' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "list-targets" -d 'Show available compile target names' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "shell-completion" -d 'Print a shell completion for supported shells' -complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from annotate; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "parse" -d 'Parse into PL AST' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "fmt" -d 'Parse & generate PRQL code back' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "debug" -d 'Commands for meant for debugging, prone to change' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "resolve" -d 'Parse, resolve & lower into RQ' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "sql:preprocess" -d 'Parse, resolve, lower into RQ & preprocess SRQ' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "sql:anchor" -d 'Parse, resolve, lower into RQ & preprocess & anchor SRQ' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "compile" -d 'Parse, resolve, lower into RQ & compile to SQL' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "watch" -d 'Watch a directory and compile .prql files to .sql files' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "list-targets" -d 'Show available compile target names' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "shell-completion" -d 'Print a shell completion for supported shells' +complete -c prqlc -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from parse; and not __fish_seen_subcommand_from fmt; and not __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from resolve; and not __fish_seen_subcommand_from sql:preprocess; and not __fish_seen_subcommand_from sql:anchor; and not __fish_seen_subcommand_from compile; and not __fish_seen_subcommand_from watch; and not __fish_seen_subcommand_from list-targets; and not __fish_seen_subcommand_from shell-completion; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' +complete -c prqlc -n "__fish_seen_subcommand_from help; and __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate" -f -a "semantics" -d 'Parse & resolve, but don\'t lower into RQ' +complete -c prqlc -n "__fish_seen_subcommand_from help; and __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate" -f -a "eval" -d 'Parse & evaluate expression down to a value' +complete -c prqlc -n "__fish_seen_subcommand_from help; and __fish_seen_subcommand_from debug; and not __fish_seen_subcommand_from semantics; and not __fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from annotate" -f -a "annotate" -d 'Parse, resolve & combine source with comments annotating relation type' ----- stderr ----- diff --git a/prql-compiler/prqlc/tests/snapshots/test__shell_completion-3.snap b/prql-compiler/prqlc/tests/snapshots/test__shell_completion-3.snap index d41cdf3666ab..dadab31fa9c1 100644 --- a/prql-compiler/prqlc/tests/snapshots/test__shell_completion-3.snap +++ b/prql-compiler/prqlc/tests/snapshots/test__shell_completion-3.snap @@ -3,8 +3,11 @@ source: prql-compiler/prqlc/tests/test.rs info: program: prqlc args: + - "--color=never" - shell-completion - powershell + env: + CLICOLOR_FORCE: "" --- success: true exit_code: 0 @@ -39,8 +42,7 @@ Register-ArgumentCompleter -Native -CommandName 'prqlc' -ScriptBlock { [CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version') [CompletionResult]::new('parse', 'parse', [CompletionResultType]::ParameterValue, 'Parse into PL AST') [CompletionResult]::new('fmt', 'fmt', [CompletionResultType]::ParameterValue, 'Parse & generate PRQL code back') - [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type') - [CompletionResult]::new('debug', 'debug', [CompletionResultType]::ParameterValue, 'Parse & resolve, but don''t lower into RQ') + [CompletionResult]::new('debug', 'debug', [CompletionResultType]::ParameterValue, 'Commands for meant for debugging, prone to change') [CompletionResult]::new('resolve', 'resolve', [CompletionResultType]::ParameterValue, 'Parse, resolve & lower into RQ') [CompletionResult]::new('sql:preprocess', 'sql:preprocess', [CompletionResultType]::ParameterValue, 'Parse, resolve, lower into RQ & preprocess SRQ') [CompletionResult]::new('sql:anchor', 'sql:anchor', [CompletionResultType]::ParameterValue, 'Parse, resolve, lower into RQ & preprocess & anchor SRQ') @@ -64,18 +66,53 @@ Register-ArgumentCompleter -Native -CommandName 'prqlc' -ScriptBlock { [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') break } - 'prqlc;annotate' { + 'prqlc;debug' { [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Controls when to use color') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('semantics', 'semantics', [CompletionResultType]::ParameterValue, 'Parse & resolve, but don''t lower into RQ') + [CompletionResult]::new('eval', 'eval', [CompletionResultType]::ParameterValue, 'Parse & evaluate expression down to a value') + [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type') + [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)') break } - 'prqlc;debug' { + 'prqlc;debug;semantics' { + [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Controls when to use color') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') + break + } + 'prqlc;debug;eval' { + [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Controls when to use color') + [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') + [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') + break + } + 'prqlc;debug;annotate' { [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Controls when to use color') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') break } + 'prqlc;debug;help' { + [CompletionResult]::new('semantics', 'semantics', [CompletionResultType]::ParameterValue, 'Parse & resolve, but don''t lower into RQ') + [CompletionResult]::new('eval', 'eval', [CompletionResultType]::ParameterValue, 'Parse & evaluate expression down to a value') + [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type') + [CompletionResult]::new('help', 'help', [CompletionResultType]::ParameterValue, 'Print this message or the help of the given subcommand(s)') + break + } + 'prqlc;debug;help;semantics' { + break + } + 'prqlc;debug;help;eval' { + break + } + 'prqlc;debug;help;annotate' { + break + } + 'prqlc;debug;help;help' { + break + } 'prqlc;resolve' { [CompletionResult]::new('--format', 'format', [CompletionResultType]::ParameterName, 'format') [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'Controls when to use color') @@ -128,8 +165,7 @@ Register-ArgumentCompleter -Native -CommandName 'prqlc' -ScriptBlock { 'prqlc;help' { [CompletionResult]::new('parse', 'parse', [CompletionResultType]::ParameterValue, 'Parse into PL AST') [CompletionResult]::new('fmt', 'fmt', [CompletionResultType]::ParameterValue, 'Parse & generate PRQL code back') - [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type') - [CompletionResult]::new('debug', 'debug', [CompletionResultType]::ParameterValue, 'Parse & resolve, but don''t lower into RQ') + [CompletionResult]::new('debug', 'debug', [CompletionResultType]::ParameterValue, 'Commands for meant for debugging, prone to change') [CompletionResult]::new('resolve', 'resolve', [CompletionResultType]::ParameterValue, 'Parse, resolve & lower into RQ') [CompletionResult]::new('sql:preprocess', 'sql:preprocess', [CompletionResultType]::ParameterValue, 'Parse, resolve, lower into RQ & preprocess SRQ') [CompletionResult]::new('sql:anchor', 'sql:anchor', [CompletionResultType]::ParameterValue, 'Parse, resolve, lower into RQ & preprocess & anchor SRQ') @@ -146,10 +182,19 @@ Register-ArgumentCompleter -Native -CommandName 'prqlc' -ScriptBlock { 'prqlc;help;fmt' { break } - 'prqlc;help;annotate' { + 'prqlc;help;debug' { + [CompletionResult]::new('semantics', 'semantics', [CompletionResultType]::ParameterValue, 'Parse & resolve, but don''t lower into RQ') + [CompletionResult]::new('eval', 'eval', [CompletionResultType]::ParameterValue, 'Parse & evaluate expression down to a value') + [CompletionResult]::new('annotate', 'annotate', [CompletionResultType]::ParameterValue, 'Parse, resolve & combine source with comments annotating relation type') break } - 'prqlc;help;debug' { + 'prqlc;help;debug;semantics' { + break + } + 'prqlc;help;debug;eval' { + break + } + 'prqlc;help;debug;annotate' { break } 'prqlc;help;resolve' { diff --git a/prql-compiler/prqlc/tests/snapshots/test__shell_completion-4.snap b/prql-compiler/prqlc/tests/snapshots/test__shell_completion-4.snap index 1dc350e96130..aea557585941 100644 --- a/prql-compiler/prqlc/tests/snapshots/test__shell_completion-4.snap +++ b/prql-compiler/prqlc/tests/snapshots/test__shell_completion-4.snap @@ -62,7 +62,22 @@ _arguments "${_arguments_options[@]}" \ '::input:_files' \ && ret=0 ;; -(annotate) +(debug) +_arguments "${_arguments_options[@]}" \ +'--color=[Controls when to use color]:WHEN:(auto always never)' \ +'-h[Print help]' \ +'--help[Print help]' \ +":: :_prqlc__debug_commands" \ +"*::: :->debug" \ +&& ret=0 + + case $state in + (debug) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:prqlc-debug-command-$line[1]:" + case $line[1] in + (semantics) _arguments "${_arguments_options[@]}" \ '--color=[Controls when to use color]:WHEN:(auto always never)' \ '-h[Print help]' \ @@ -72,7 +87,17 @@ _arguments "${_arguments_options[@]}" \ '::main_path -- Identifier of the main pipeline:' \ && ret=0 ;; -(debug) +(eval) +_arguments "${_arguments_options[@]}" \ +'--color=[Controls when to use color]:WHEN:(auto always never)' \ +'-h[Print help (see more with '\''--help'\'')]' \ +'--help[Print help (see more with '\''--help'\'')]' \ +'::input:_files' \ +'::output:_files' \ +'::main_path -- Identifier of the main pipeline:' \ +&& ret=0 +;; +(annotate) _arguments "${_arguments_options[@]}" \ '--color=[Controls when to use color]:WHEN:(auto always never)' \ '-h[Print help]' \ @@ -82,6 +107,42 @@ _arguments "${_arguments_options[@]}" \ '::main_path -- Identifier of the main pipeline:' \ && ret=0 ;; +(help) +_arguments "${_arguments_options[@]}" \ +":: :_prqlc__debug__help_commands" \ +"*::: :->help" \ +&& ret=0 + + case $state in + (help) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:prqlc-debug-help-command-$line[1]:" + case $line[1] in + (semantics) +_arguments "${_arguments_options[@]}" \ +&& ret=0 +;; +(eval) +_arguments "${_arguments_options[@]}" \ +&& ret=0 +;; +(annotate) +_arguments "${_arguments_options[@]}" \ +&& ret=0 +;; +(help) +_arguments "${_arguments_options[@]}" \ +&& ret=0 +;; + esac + ;; +esac +;; + esac + ;; +esac +;; (resolve) _arguments "${_arguments_options[@]}" \ '--format=[]:FORMAT:(json yaml)' \ @@ -172,13 +233,33 @@ _arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \ && ret=0 ;; -(annotate) +(debug) +_arguments "${_arguments_options[@]}" \ +":: :_prqlc__help__debug_commands" \ +"*::: :->debug" \ +&& ret=0 + + case $state in + (debug) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:prqlc-help-debug-command-$line[1]:" + case $line[1] in + (semantics) _arguments "${_arguments_options[@]}" \ && ret=0 ;; -(debug) +(eval) +_arguments "${_arguments_options[@]}" \ +&& ret=0 +;; +(annotate) _arguments "${_arguments_options[@]}" \ && ret=0 +;; + esac + ;; +esac ;; (resolve) _arguments "${_arguments_options[@]}" \ @@ -226,8 +307,7 @@ _prqlc_commands() { local commands; commands=( 'parse:Parse into PL AST' \ 'fmt:Parse & generate PRQL code back' \ -'annotate:Parse, resolve & combine source with comments annotating relation type' \ -'debug:Parse & resolve, but don'\''t lower into RQ' \ +'debug:Commands for meant for debugging, prone to change' \ 'resolve:Parse, resolve & lower into RQ' \ 'sql:preprocess:Parse, resolve, lower into RQ & preprocess SRQ' \ 'sql:anchor:Parse, resolve, lower into RQ & preprocess & anchor SRQ' \ @@ -239,15 +319,20 @@ _prqlc_commands() { ) _describe -t commands 'prqlc commands' commands "$@" } -(( $+functions[_prqlc__annotate_commands] )) || -_prqlc__annotate_commands() { +(( $+functions[_prqlc__debug__annotate_commands] )) || +_prqlc__debug__annotate_commands() { + local commands; commands=() + _describe -t commands 'prqlc debug annotate commands' commands "$@" +} +(( $+functions[_prqlc__debug__help__annotate_commands] )) || +_prqlc__debug__help__annotate_commands() { local commands; commands=() - _describe -t commands 'prqlc annotate commands' commands "$@" + _describe -t commands 'prqlc debug help annotate commands' commands "$@" } -(( $+functions[_prqlc__help__annotate_commands] )) || -_prqlc__help__annotate_commands() { +(( $+functions[_prqlc__help__debug__annotate_commands] )) || +_prqlc__help__debug__annotate_commands() { local commands; commands=() - _describe -t commands 'prqlc help annotate commands' commands "$@" + _describe -t commands 'prqlc help debug annotate commands' commands "$@" } (( $+functions[_prqlc__compile_commands] )) || _prqlc__compile_commands() { @@ -261,14 +346,38 @@ _prqlc__help__compile_commands() { } (( $+functions[_prqlc__debug_commands] )) || _prqlc__debug_commands() { - local commands; commands=() + local commands; commands=( +'semantics:Parse & resolve, but don'\''t lower into RQ' \ +'eval:Parse & evaluate expression down to a value' \ +'annotate:Parse, resolve & combine source with comments annotating relation type' \ +'help:Print this message or the help of the given subcommand(s)' \ + ) _describe -t commands 'prqlc debug commands' commands "$@" } (( $+functions[_prqlc__help__debug_commands] )) || _prqlc__help__debug_commands() { - local commands; commands=() + local commands; commands=( +'semantics:Parse & resolve, but don'\''t lower into RQ' \ +'eval:Parse & evaluate expression down to a value' \ +'annotate:Parse, resolve & combine source with comments annotating relation type' \ + ) _describe -t commands 'prqlc help debug commands' commands "$@" } +(( $+functions[_prqlc__debug__eval_commands] )) || +_prqlc__debug__eval_commands() { + local commands; commands=() + _describe -t commands 'prqlc debug eval commands' commands "$@" +} +(( $+functions[_prqlc__debug__help__eval_commands] )) || +_prqlc__debug__help__eval_commands() { + local commands; commands=() + _describe -t commands 'prqlc debug help eval commands' commands "$@" +} +(( $+functions[_prqlc__help__debug__eval_commands] )) || +_prqlc__help__debug__eval_commands() { + local commands; commands=() + _describe -t commands 'prqlc help debug eval commands' commands "$@" +} (( $+functions[_prqlc__fmt_commands] )) || _prqlc__fmt_commands() { local commands; commands=() @@ -279,13 +388,27 @@ _prqlc__help__fmt_commands() { local commands; commands=() _describe -t commands 'prqlc help fmt commands' commands "$@" } +(( $+functions[_prqlc__debug__help_commands] )) || +_prqlc__debug__help_commands() { + local commands; commands=( +'semantics:Parse & resolve, but don'\''t lower into RQ' \ +'eval:Parse & evaluate expression down to a value' \ +'annotate:Parse, resolve & combine source with comments annotating relation type' \ +'help:Print this message or the help of the given subcommand(s)' \ + ) + _describe -t commands 'prqlc debug help commands' commands "$@" +} +(( $+functions[_prqlc__debug__help__help_commands] )) || +_prqlc__debug__help__help_commands() { + local commands; commands=() + _describe -t commands 'prqlc debug help help commands' commands "$@" +} (( $+functions[_prqlc__help_commands] )) || _prqlc__help_commands() { local commands; commands=( 'parse:Parse into PL AST' \ 'fmt:Parse & generate PRQL code back' \ -'annotate:Parse, resolve & combine source with comments annotating relation type' \ -'debug:Parse & resolve, but don'\''t lower into RQ' \ +'debug:Commands for meant for debugging, prone to change' \ 'resolve:Parse, resolve & lower into RQ' \ 'sql:preprocess:Parse, resolve, lower into RQ & preprocess SRQ' \ 'sql:anchor:Parse, resolve, lower into RQ & preprocess & anchor SRQ' \ @@ -332,6 +455,21 @@ _prqlc__resolve_commands() { local commands; commands=() _describe -t commands 'prqlc resolve commands' commands "$@" } +(( $+functions[_prqlc__debug__help__semantics_commands] )) || +_prqlc__debug__help__semantics_commands() { + local commands; commands=() + _describe -t commands 'prqlc debug help semantics commands' commands "$@" +} +(( $+functions[_prqlc__debug__semantics_commands] )) || +_prqlc__debug__semantics_commands() { + local commands; commands=() + _describe -t commands 'prqlc debug semantics commands' commands "$@" +} +(( $+functions[_prqlc__help__debug__semantics_commands] )) || +_prqlc__help__debug__semantics_commands() { + local commands; commands=() + _describe -t commands 'prqlc help debug semantics commands' commands "$@" +} (( $+functions[_prqlc__help__shell-completion_commands] )) || _prqlc__help__shell-completion_commands() { local commands; commands=() diff --git a/prql-compiler/prqlc/tests/snapshots/test__shell_completion.snap b/prql-compiler/prqlc/tests/snapshots/test__shell_completion.snap index 2da2e350b0d7..e2c53b910b53 100644 --- a/prql-compiler/prqlc/tests/snapshots/test__shell_completion.snap +++ b/prql-compiler/prqlc/tests/snapshots/test__shell_completion.snap @@ -26,9 +26,6 @@ _prqlc() { ",$1") cmd="prqlc" ;; - prqlc,annotate) - cmd="prqlc__annotate" - ;; prqlc,compile) cmd="prqlc__compile" ;; @@ -62,8 +59,29 @@ _prqlc() { prqlc,watch) cmd="prqlc__watch" ;; - prqlc__help,annotate) - cmd="prqlc__help__annotate" + prqlc__debug,annotate) + cmd="prqlc__debug__annotate" + ;; + prqlc__debug,eval) + cmd="prqlc__debug__eval" + ;; + prqlc__debug,help) + cmd="prqlc__debug__help" + ;; + prqlc__debug,semantics) + cmd="prqlc__debug__semantics" + ;; + prqlc__debug__help,annotate) + cmd="prqlc__debug__help__annotate" + ;; + prqlc__debug__help,eval) + cmd="prqlc__debug__help__eval" + ;; + prqlc__debug__help,help) + cmd="prqlc__debug__help__help" + ;; + prqlc__debug__help,semantics) + cmd="prqlc__debug__help__semantics" ;; prqlc__help,compile) cmd="prqlc__help__compile" @@ -98,6 +116,15 @@ _prqlc() { prqlc__help,watch) cmd="prqlc__help__watch" ;; + prqlc__help__debug,annotate) + cmd="prqlc__help__debug__annotate" + ;; + prqlc__help__debug,eval) + cmd="prqlc__help__debug__eval" + ;; + prqlc__help__debug,semantics) + cmd="prqlc__help__debug__semantics" + ;; *) ;; esac @@ -105,7 +132,7 @@ _prqlc() { case "${cmd}" in prqlc) - opts="-h -V --color --help --version parse fmt annotate debug resolve sql:preprocess sql:anchor compile watch list-targets shell-completion help" + opts="-h -V --color --help --version parse fmt debug resolve sql:preprocess sql:anchor compile watch list-targets shell-completion help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -122,13 +149,21 @@ _prqlc() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - prqlc__annotate) - opts="-h --color --help [INPUT] [OUTPUT] [MAIN_PATH]" + prqlc__compile) + opts="-t -h --hide-signature-comment --target --color --help [INPUT] [OUTPUT] [MAIN_PATH]" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --target) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -t) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --color) COMPREPLY=($(compgen -W "auto always never" -- "${cur}")) return 0 @@ -140,21 +175,49 @@ _prqlc() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - prqlc__compile) - opts="-t -h --hide-signature-comment --target --color --help [INPUT] [OUTPUT] [MAIN_PATH]" + prqlc__debug) + opts="-h --color --help semantics eval annotate help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in - --target) - COMPREPLY=($(compgen -f "${cur}")) + --color) + COMPREPLY=($(compgen -W "auto always never" -- "${cur}")) return 0 ;; - -t) - COMPREPLY=($(compgen -f "${cur}")) + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__debug__annotate) + opts="-h --color --help [INPUT] [OUTPUT] [MAIN_PATH]" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --color) + COMPREPLY=($(compgen -W "auto always never" -- "${cur}")) return 0 ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__debug__eval) + opts="-h --color --help [INPUT] [OUTPUT] [MAIN_PATH]" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in --color) COMPREPLY=($(compgen -W "auto always never" -- "${cur}")) return 0 @@ -166,9 +229,79 @@ _prqlc() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - prqlc__debug) + prqlc__debug__help) + opts="semantics eval annotate help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__debug__help__annotate) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__debug__help__eval) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__debug__help__help) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__debug__help__semantics) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__debug__semantics) opts="-h --color --help [INPUT] [OUTPUT] [MAIN_PATH]" - if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi @@ -203,7 +336,7 @@ _prqlc() { return 0 ;; prqlc__help) - opts="parse fmt annotate debug resolve sql:preprocess sql:anchor compile watch list-targets shell-completion help" + opts="parse fmt debug resolve sql:preprocess sql:anchor compile watch list-targets shell-completion help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -216,7 +349,7 @@ _prqlc() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - prqlc__help__annotate) + prqlc__help__compile) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -230,8 +363,8 @@ _prqlc() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - prqlc__help__compile) - opts="" + prqlc__help__debug) + opts="semantics eval annotate" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -244,9 +377,37 @@ _prqlc() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - prqlc__help__debug) + prqlc__help__debug__annotate) opts="" - if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__help__debug__eval) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + prqlc__help__debug__semantics) + opts="" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi diff --git a/prql-compiler/prqlc/tests/test.rs b/prql-compiler/prqlc/tests/test.rs index 244644c91f2f..f8a10ba6dca6 100644 --- a/prql-compiler/prqlc/tests/test.rs +++ b/prql-compiler/prqlc/tests/test.rs @@ -20,8 +20,7 @@ fn test_help() { Commands: parse Parse into PL AST fmt Parse & generate PRQL code back - annotate Parse, resolve & combine source with comments annotating relation type - debug Parse & resolve, but don't lower into RQ + debug Commands for meant for debugging, prone to change resolve Parse, resolve & lower into RQ sql:preprocess Parse, resolve, lower into RQ & preprocess SRQ sql:anchor Parse, resolve, lower into RQ & preprocess & anchor SRQ diff --git a/prql-compiler/src/semantic/eval/mod.rs b/prql-compiler/src/semantic/eval/mod.rs new file mode 100644 index 000000000000..ba81166aa9b0 --- /dev/null +++ b/prql-compiler/src/semantic/eval/mod.rs @@ -0,0 +1,558 @@ +use std::iter::zip; + +use anyhow::Result; +use itertools::Itertools; + +use crate::ast::pl::{fold::AstFold, Expr, ExprKind, Func, FuncCall, FuncParam, Ident, Literal}; +use crate::error::{Error, Span, WithErrorInfo}; + +use super::resolver::{binary_to_func_call, unary_to_func_call}; + +pub fn eval(expr: Expr) -> Result { + Evaluator::new().fold_expr(expr) +} + +/// Converts an expression to a value +/// +/// Serves as a working draft of PRQL semantics definition. +struct Evaluator { + context: Option, +} + +impl Evaluator { + fn new() -> Self { + Evaluator { context: None } + } +} + +impl AstFold for Evaluator { + fn fold_expr(&mut self, expr: Expr) -> Result { + let mut expr = super::static_analysis::static_analysis(expr); + + expr.kind = match expr.kind { + // these are values already + ExprKind::Literal(l) => ExprKind::Literal(l), + + // these are values, iff their contents are values too + ExprKind::Array(_) | ExprKind::Tuple(_) | ExprKind::Range(_) => { + self.fold_expr_kind(expr.kind)? + } + + // functions are values + ExprKind::Func(f) => ExprKind::Func(f), + + // convert to function calls and then evaluate to a value + ExprKind::Binary(binary) => { + let func_call = binary_to_func_call(binary, expr.span); + self.fold_expr(func_call)?.kind + } + ExprKind::Unary(unary) => { + let func_call = unary_to_func_call(unary, expr.span); + self.fold_expr(func_call)?.kind + } + + // ident are not values + ExprKind::Ident(ident) => { + // here we'd have to implement the whole name resolution, but for now, + // let's do something simple + + // this is very crude, but for simple cases, it's enough + let mut ident = ident; + let mut base = self.context.clone(); + loop { + let (first, remaining) = ident.pop_front(); + let res = lookup(base.as_ref(), &first).with_span(expr.span)?; + + if let Some(remaining) = remaining { + ident = remaining; + base = Some(res); + } else { + return Ok(res); + } + } + } + + // the beef happens here + ExprKind::FuncCall(func_call) => { + let func = self.fold_expr(*func_call.name)?; + let mut func = func.try_cast(|x| x.into_func(), Some("func call"), "function")?; + + func.args.extend(func_call.args); + + if func.args.len() < func.params.len() { + ExprKind::Func(func) + } else { + self.eval_function(*func, expr.span)? + } + } + ExprKind::Pipeline(mut pipeline) => { + let mut res = self.fold_expr(pipeline.exprs.remove(0))?; + for expr in pipeline.exprs { + let func_call = + Expr::from(ExprKind::FuncCall(FuncCall::new_simple(expr, vec![res]))); + + res = self.fold_expr(func_call)?; + } + + return Ok(res); + } + + ExprKind::All { .. } + | ExprKind::TransformCall(_) + | ExprKind::SString(_) + | ExprKind::FString(_) + | ExprKind::Case(_) + | ExprKind::RqOperator { .. } + | ExprKind::Type(_) + | ExprKind::Param(_) + | ExprKind::Internal(_) => { + return Err(Error::new_simple("not a value").with_span(expr.span).into()) + } + }; + Ok(expr) + } +} + +fn lookup(base: Option<&Expr>, name: &str) -> Result { + if let Some(base) = base { + if let ExprKind::Tuple(items) = &base.kind { + if let Some(item) = items.iter().find(|i| i.alias.as_deref() == Some(name)) { + return Ok(item.clone()); + } + } + } + if name == "std" { + return Ok(std_module()); + } + + Err(Error::new_simple(format!("cannot find `{}` in {:?}", name, base)).into()) +} + +impl Evaluator { + fn eval_function(&mut self, func: Func, span: Option) -> Result { + let func_name = func.name_hint.unwrap().to_string(); + + // eval args + let closure = (func.params.iter()).find_position(|x| x.name == "closure"); + + let args = if let Some((closure_position, _)) = closure { + let mut args = Vec::new(); + + for (pos, arg) in func.args.into_iter().enumerate() { + if pos == closure_position { + // no evaluation + args.push(arg); + } else { + // eval + args.push(self.fold_expr(arg)?); + } + } + args + } else { + self.fold_exprs(func.args)? + }; + + // eval body + use Literal::*; + Ok(match func_name.as_str() { + "std.add" => { + let [l, r]: [_; 2] = args.try_into().unwrap(); + + let l = l.kind.into_literal().unwrap(); + let r = r.kind.into_literal().unwrap(); + + let res = match (l, r) { + (Integer(l), Integer(r)) => (l + r) as f64, + (Float(l), Integer(r)) => l + (r as f64), + (Integer(l), Float(r)) => (l as f64) + r, + (Float(l), Float(r)) => l + r, + + _ => return Err(Error::new_simple("bad arg types").with_span(span).into()), + }; + + ExprKind::Literal(Float(res)) + } + + "std.floor" => { + let [x]: [_; 1] = args.try_into().unwrap(); + + let res = match x.kind { + ExprKind::Literal(Integer(i)) => i, + ExprKind::Literal(Float(f)) => f.floor() as i64, + _ => return Err(Error::new_simple("bad arg types").with_span(x.span).into()), + }; + + ExprKind::Literal(Integer(res)) + } + + "std.neg" => { + let [x]: [_; 1] = args.try_into().unwrap(); + + match x.kind { + ExprKind::Literal(Integer(i)) => ExprKind::Literal(Integer(-i)), + ExprKind::Literal(Float(f)) => ExprKind::Literal(Float(-f)), + _ => return Err(Error::new_simple("bad arg types").with_span(x.span).into()), + } + } + + "std.select" => { + let [tuple_closure, relation]: [_; 2] = args.try_into().unwrap(); + + self.eval_for_each_row(relation, tuple_closure)?.kind + } + + "std.derive" => { + let [tuple_closure, relation]: [_; 2] = args.try_into().unwrap(); + + let new = self.eval_for_each_row(relation.clone(), tuple_closure)?; + + zip_relations(relation, new) + } + + "std.filter" => { + let [condition_closure, relation]: [_; 2] = args.try_into().unwrap(); + + let condition = self.eval_for_each_row(relation.clone(), condition_closure)?; + + let condition = condition.kind.into_array().unwrap(); + let relation = relation.kind.into_array().unwrap(); + + let mut res = Vec::new(); + for (cond, tuple) in zip(condition, relation) { + let f = cond.kind.into_literal().unwrap().into_boolean().unwrap(); + + if f { + res.push(tuple); + } + } + + ExprKind::Array(res) + } + + "std.aggregate" => { + let [tuple_closure, relation]: [_; 2] = args.try_into().unwrap(); + + let relation = rows_to_cols(relation)?; + let tuple = self.eval_within_context(tuple_closure, relation)?; + + ExprKind::Array(vec![tuple]) + } + + "std.window" => { + let [tuple_closure, relation]: [_; 2] = args.try_into().unwrap(); + let relation_size = relation.kind.as_array().unwrap().len(); + let relation = rows_to_cols(relation)?; + + let mut res = Vec::new(); + + const FRAME_ROWS: std::ops::Range = -1..1; + + for row_index in 0..relation_size { + let rel = windowed(relation.clone(), row_index, FRAME_ROWS, relation_size); + + let row_value = self.eval_within_context(tuple_closure.clone(), rel)?; + + res.push(row_value); + } + + ExprKind::Array(res) + } + + "std.columnar" => { + let [relation_closure, relation]: [_; 2] = args.try_into().unwrap(); + let relation = rows_to_cols(relation)?; + + let res = self.eval_within_context(relation_closure, relation)?; + + cols_to_rows(res)?.kind + } + + "std.sum" => { + let [array]: [_; 1] = args.try_into().unwrap(); + + let mut sum = 0.0; + for item in array.kind.into_array().unwrap() { + let lit = item.kind.into_literal().unwrap(); + match lit { + Integer(x) => sum += x as f64, + Float(x) => sum += x, + _ => panic!("bad type"), + } + } + + ExprKind::Literal(Float(sum)) + } + + "std.lag" => { + let [array]: [_; 1] = args.try_into().unwrap(); + + let mut array = array.try_cast(|x| x.into_array(), Some("lag"), "an array")?; + + if !array.is_empty() { + array.pop(); + array.insert(0, Expr::null()); + } + + ExprKind::Array(array) + } + + _ => { + return Err(Error::new_simple(format!("unknown function {func_name}")) + .with_span(span) + .into()) + } + }) + } + + fn eval_for_each_row(&mut self, relation: Expr, closure: Expr) -> Result { + // save relation from outer calls + let prev_relation = self.context.take(); + + let relation_rows = relation.try_cast(|x| x.into_array(), None, "an array")?; + + // for every item in relation array, evaluate args + let mut output_array = Vec::new(); + for relation_row in relation_rows { + let row_value = self.eval_within_context(closure.clone(), relation_row)?; + output_array.push(row_value); + } + + // restore relation for outer calls + self.context = prev_relation; + + Ok(Expr::from(ExprKind::Array(output_array))) + } + + fn eval_within_context(&mut self, expr: Expr, context: Expr) -> Result { + // save relation from outer calls + let prev_relation = self.context.take(); + + self.context = Some(context); + let res = self.fold_expr(expr)?; + + // restore relation for outer calls + self.context = prev_relation; + + Ok(res) + } +} + +fn windowed( + mut relation: Expr, + row_index: usize, + frame: std::ops::Range, + relation_size: usize, +) -> Expr { + let row = row_index as i64; + let end = (row + frame.end).clamp(0, relation_size as i64) as usize; + let start = (row + frame.start).clamp(0, end as i64) as usize; + + for field in relation.kind.as_tuple_mut().unwrap() { + let column = field.kind.as_array_mut().unwrap(); + + column.drain(end..); + column.drain(0..start); + } + relation +} + +/// Converts `[{a = 1, b = false}, {a = 2, b = true}]` +/// into `{a = [1, 2], b = [false, true]}` +fn rows_to_cols(expr: Expr) -> Result { + let relation_rows = expr.try_cast(|x| x.into_array(), None, "an array")?; + + // prepare output + let mut arg_tuple = Vec::new(); + for field in relation_rows.first().unwrap().kind.as_tuple().unwrap() { + arg_tuple.push(Expr { + alias: field.alias.clone(), + ..Expr::from(ExprKind::Array(Vec::new())) + }); + } + + // place entries + for relation_row in relation_rows { + let fields = relation_row.try_cast(|x| x.into_tuple(), None, "a tuple")?; + + for (index, field) in fields.into_iter().enumerate() { + arg_tuple[index].kind.as_array_mut().unwrap().push(field); + } + } + Ok(Expr::from(ExprKind::Tuple(arg_tuple))) +} + +/// Converts `{a = [1, 2], b = [false, true]}` +/// into `[{a = 1, b = false}, {a = 2, b = true}]` +fn cols_to_rows(expr: Expr) -> Result { + let fields = expr.try_cast(|x| x.into_tuple(), None, "an tuple")?; + + let len = fields.first().unwrap().kind.as_array().unwrap().len(); + + let mut rows = Vec::new(); + for index in 0..len { + let mut row = Vec::new(); + for field in &fields { + row.push(Expr { + alias: field.alias.clone(), + ..field.kind.as_array().unwrap()[index].clone() + }) + } + + rows.push(Expr::from(ExprKind::Tuple(row))); + } + + Ok(Expr::from(ExprKind::Array(rows))) +} + +fn std_module() -> Expr { + Expr::from(ExprKind::Tuple( + [ + new_func("floor", &["x"]), + new_func("add", &["x", "y"]), + new_func("neg", &["x"]), + new_func("select", &["closure", "relation"]), + new_func("derive", &["closure", "relation"]), + new_func("filter", &["closure", "relation"]), + new_func("aggregate", &["closure", "relation"]), + new_func("window", &["closure", "relation"]), + new_func("columnar", &["closure", "relation"]), + new_func("sum", &["x"]), + new_func("lag", &["x"]), + ] + .to_vec(), + )) +} + +fn new_func(name: &str, params: &[&str]) -> Expr { + let params = params + .iter() + .map(|name| FuncParam { + name: name.to_string(), + default_value: None, + ty: None, + }) + .collect(); + + let kind = ExprKind::Func(Box::new(Func { + name_hint: Some(Ident { + path: vec!["std".to_string()], + name: name.to_string(), + }), + + // these don't matter + return_ty: Default::default(), + body: Box::new(Expr::null()), + params, + named_params: Default::default(), + args: Default::default(), + env: Default::default(), + })); + Expr { + alias: Some(name.to_string()), + ..Expr::from(kind) + } +} + +fn zip_relations(l: Expr, r: Expr) -> ExprKind { + let l = l.kind.into_array().unwrap(); + let r = r.kind.into_array().unwrap(); + + let mut res = Vec::new(); + for (l, r) in zip(l, r) { + let l_fields = l.kind.into_tuple().unwrap(); + let r_fields = r.kind.into_tuple().unwrap(); + + res.push(Expr::from(ExprKind::Tuple([l_fields, r_fields].concat()))); + } + + ExprKind::Array(res) +} + +#[cfg(test)] +mod test { + + use insta::assert_display_snapshot; + use itertools::Itertools; + + use super::*; + + fn eval(source: &str) -> Result { + let stmts = crate::prql_to_pl(source)?.into_iter().exactly_one()?; + let expr = stmts.kind.into_var_def()?.value; + + let value = super::eval(*expr)?; + + Ok(value.to_string()) + } + + #[test] + fn basic() { + assert_display_snapshot!(eval(r" + [std.floor (3.5 + 2.9) + 3, 3] + ").unwrap(), + @"[9, 3]" + ); + } + + #[test] + fn tuples() { + assert_display_snapshot!(eval(r" + {{a_a = 4, a_b = false}, b = 2.1 + 3.6, c = [false, true, false]} + ").unwrap(), + @"{{a_a = 4, a_b = false}, b = 5.7, c = [false, true, false]}" + ); + } + + #[test] + fn pipelines() { + assert_display_snapshot!(eval(r" + (4.5 | std.floor | std.neg) + ").unwrap(), + @"-4" + ); + } + + #[test] + fn transforms() { + assert_display_snapshot!(eval(r" + [ + { b = 4, c = false }, + { b = 5, c = true }, + { b = 12, c = true }, + ] + std.select {c, b + 2} + std.derive {d = 42} + std.filter c + ").unwrap(), + @"[{c = true, 7, d = 42}, {c = true, 14, d = 42}]" + ); + } + + #[test] + fn window() { + assert_display_snapshot!(eval(r" + [ + { b = 4, c = false }, + { b = 5, c = true }, + { b = 12, c = true }, + ] + std.window {d = std.sum b} + ").unwrap(), + @"[{d = 4}, {d = 9}, {d = 17}]" + ); + } + + #[test] + fn columnar() { + assert_display_snapshot!(eval(r" + [ + { b = 4, c = false }, + { b = 5, c = true }, + { b = 12, c = true }, + ] + std.columnar {g = std.lag b} + ").unwrap(), + @"[{g = null}, {g = 4}, {g = 5}]" + ); + } +} diff --git a/prql-compiler/src/semantic/mod.rs b/prql-compiler/src/semantic/mod.rs index 2a4ea901fd2e..c9f71083ddec 100644 --- a/prql-compiler/src/semantic/mod.rs +++ b/prql-compiler/src/semantic/mod.rs @@ -1,6 +1,7 @@ //! Semantic resolver (name resolution, type checking and lowering to RQ) mod context; +mod eval; mod lowering; mod module; pub mod reporting; @@ -17,6 +18,7 @@ pub use self::context::Context; pub use self::module::Module; use self::resolver::Resolver; pub use self::resolver::ResolverOptions; +pub use eval::eval; pub use lowering::lower_to_ir; use crate::ast::pl::{Lineage, LineageColumn, Stmt};