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

Add visualization of ASTs to CLI & REPL #158

Merged
merged 2 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
41 changes: 36 additions & 5 deletions partiql-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,45 @@ supports-unicode = "1.0.2"
supports-hyperlinks = "1.2.0"
termbg = "0.4.1"
shellexpand = "2.1.0"
partiql-parser = { path = "../partiql-parser" }
partiql-source-map = { path = "../partiql-source-map" }
partiql-ast = { path = "../partiql-ast" }


thiserror = "1.0.31"
miette = { version ="4.7.1", features = ["fancy"] }
clap = { version = "3.2.13", features = ["derive"] }

# serde
serde = { version ="1.*", features = ["derive"], optional = true }
serde_json = { version ="1.*", optional = true }

### Dependencies for the `render` feature
viuer = { version ="0.6.1", features = ["sixel"], optional = true }
image = { version ="0.24.2", optional = true }
graphviz-sys = { version ="0.1.3", optional = true }
jpschorr marked this conversation as resolved.
Show resolved Hide resolved
resvg = { version ="0.23.0", optional = true }
usvg = { version ="0.23.0", optional = true }
tiny-skia = { version ="0.6.6", optional = true }
strum = { version ="0.24.1", features = ["derive"], optional = true }
dot-writer = { version = "~0.1.2", optional = true }

tui = "0.18.0"
crossterm = "0.23.2"


[features]
default = []
serde = [
"dep:serde",
"dep:serde_json",
"partiql-parser/serde",
"partiql-source-map/serde",
"partiql-ast/serde",
]
visualize = [
jpschorr marked this conversation as resolved.
Show resolved Hide resolved
"serde",
"dep:viuer",
"dep:image",
"dep:graphviz-sys",
"dep:resvg",
"dep:usvg",
"dep:tiny-skia",
"dep:strum",
"dep:dot-writer",
]
38 changes: 38 additions & 0 deletions partiql-cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use clap::{ArgEnum, Parser, Subcommand};

#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
pub struct Args {
#[clap(subcommand)]
pub command: Commands,
}

#[derive(Subcommand)]
pub enum Commands {
#[cfg(feature = "visualize")]
/// Dump the AST for a query
Ast {
#[clap(short = 'T', long = "format", value_enum)]
format: Format,

/// Query to parse
#[clap(value_parser)]
query: String,
},
/// interactive REPL (Read Eval Print Loop) shell
Repl,
}

#[derive(ArgEnum, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Format {
/// JSON
Json,
/// Graphviz dot
Dot,
/// Graphviz svg output
Svg,
/// Graphviz svg rendered to png
Png,
/// Display rendered output
Display,
}
82 changes: 82 additions & 0 deletions partiql-cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use miette::{Diagnostic, LabeledSpan, SourceCode};
use partiql_parser::ParseError;
use partiql_source_map::location::{BytePosition, Location};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum CLIError {
#[error("PartiQL syntax error:")]
SyntaxError {
src: String,
msg: String,
loc: Location<BytePosition>,
},
// TODO add github issue link
jpschorr marked this conversation as resolved.
Show resolved Hide resolved
#[error("Internal Compiler Error - please report this.")]
InternalCompilerError { src: String },
}

impl Diagnostic for CLIError {
fn source_code(&self) -> Option<&dyn SourceCode> {
match self {
CLIError::SyntaxError { src, .. } => Some(src),
CLIError::InternalCompilerError { src, .. } => Some(src),
}
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
match self {
CLIError::SyntaxError { msg, loc, .. } => {
Some(Box::new(std::iter::once(LabeledSpan::new(
Some(msg.to_string()),
loc.start.0 .0 as usize,
loc.end.0 .0 as usize - loc.start.0 .0 as usize,
))))
}
CLIError::InternalCompilerError { .. } => None,
}
}
}

impl CLIError {
pub fn from_parser_error(err: ParseError, source: &str) -> CLIError {
match err {
ParseError::SyntaxError(partiql_source_map::location::Located { inner, location }) => {
CLIError::SyntaxError {
src: source.to_string(),
msg: format!("Syntax error `{}`", inner),
loc: location,
}
}
ParseError::UnexpectedToken(partiql_source_map::location::Located {
inner,
location,
}) => CLIError::SyntaxError {
src: source.to_string(),
msg: format!("Unexpected token `{}`", inner.token),
loc: location,
},
ParseError::LexicalError(partiql_source_map::location::Located { inner, location }) => {
CLIError::SyntaxError {
src: source.to_string(),
msg: format!("Lexical error `{}`", inner),
loc: location,
}
}
ParseError::Unknown(location) => CLIError::SyntaxError {
src: source.to_string(),
msg: "Unknown parser error".to_string(),
loc: Location {
start: location,
end: location,
},
},
ParseError::IllegalState(_location) => CLIError::InternalCompilerError {
src: source.to_string(),
},
_ => {
todo!("Not yet handled {:?}", err);
}
}
}
}
7 changes: 7 additions & 0 deletions partiql-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod args;

pub mod error;
pub mod repl;

#[cfg(feature = "visualize")]
pub mod visualize;
43 changes: 43 additions & 0 deletions partiql-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#![deny(rustdoc::broken_intra_doc_links)]

use clap::Parser;
use partiql_cli::{args, repl};

use partiql_parser::Parsed;

#[allow(dead_code)]
fn parse(query: &str) -> miette::Result<Parsed> {
let res = partiql_parser::Parser::default().parse(query);
//TODO
jpschorr marked this conversation as resolved.
Show resolved Hide resolved
Ok(res.expect("parse failure"))
}

fn main() -> miette::Result<()> {
let args = args::Args::parse();

match &args.command {
args::Commands::Repl => repl::repl(),

#[cfg(feature = "visualize")]
args::Commands::Ast { format, query } => {
use partiql_cli::args::Format;
use partiql_cli::visualize::render::{display, to_dot, to_json, to_png, to_svg};
use std::io::Write;

let parsed = parse(&query)?;
match format {
Format::Json => println!("{}", to_json(&parsed.ast)),
Format::Dot => println!("{}", to_dot(&parsed.ast)),
Format::Svg => println!("{}", to_svg(&parsed.ast)),
Format::Png => {
std::io::stdout()
.write(&to_png(&parsed.ast))
.expect("png write");
}
Format::Display => display(&parsed.ast),
}

Ok(())
}
}
}
113 changes: 22 additions & 91 deletions partiql-cli/src/bin/partiql-cli.rs → partiql-cli/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustyline::hint::{Hinter, HistoryHinter};
use rustyline::validate::{ValidationContext, ValidationResult, Validator};
use rustyline::{ColorMode, Context, Helper};
use std::borrow::Cow;
use std::fs::{File, OpenOptions};
use std::fs::OpenOptions;

use std::path::Path;

Expand All @@ -18,10 +18,10 @@ use syntect::highlighting::{Style, ThemeSet};
use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder};
use syntect::util::as_24_bit_terminal_escaped;

use miette::{Diagnostic, LabeledSpan, Report, SourceCode};
use miette::Report;
use owo_colors::OwoColorize;
use partiql_source_map::location::{BytePosition, Location};
use thiserror::Error;

use crate::error::CLIError;

static ION_SYNTAX: &str = include_str!("ion.sublime-syntax");
static PARTIQL_SYNTAX: &str = include_str!("partiql.sublime-syntax");
Expand Down Expand Up @@ -83,6 +83,7 @@ impl Hinter for PartiqlHelper {
hinter.hint(line, pos, ctx)
}
}

impl Highlighter for PartiqlHelper {
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
let syntax = self
Expand All @@ -106,91 +107,27 @@ impl Highlighter for PartiqlHelper {
}
}

#[derive(Debug, Error)]
pub enum CLIError {
#[error("PartiQL syntax error:")]
SyntaxError {
src: String,
msg: String,
loc: Location<BytePosition>,
},
// TODO add github issue link
#[error("Internal Compiler Error - please report this.")]
InternalCompilerError { src: String },
}

impl Diagnostic for CLIError {
fn source_code(&self) -> Option<&dyn SourceCode> {
match self {
CLIError::SyntaxError { src, .. } => Some(src),
CLIError::InternalCompilerError { src, .. } => Some(src),
}
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
match self {
CLIError::SyntaxError { msg, loc, .. } => {
Some(Box::new(std::iter::once(LabeledSpan::new(
Some(msg.to_string()),
loc.start.0 .0 as usize,
loc.end.0 .0 as usize - loc.start.0 .0 as usize,
))))
}
CLIError::InternalCompilerError { .. } => None,
}
}
}

impl CLIError {
pub fn from_parser_error(err: ParseError, source: &str) -> CLIError {
match err {
ParseError::SyntaxError(partiql_source_map::location::Located { inner, location }) => {
CLIError::SyntaxError {
src: source.to_string(),
msg: format!("Syntax error `{}`", inner),
loc: location,
}
}
ParseError::UnexpectedToken(partiql_source_map::location::Located {
inner,
location,
}) => CLIError::SyntaxError {
src: source.to_string(),
msg: format!("Unexpected token `{}`", inner.token),
loc: location,
},
ParseError::LexicalError(partiql_source_map::location::Located { inner, location }) => {
CLIError::SyntaxError {
src: source.to_string(),
msg: format!("Lexical error `{}`", inner),
loc: location,
}
}
ParseError::Unknown(location) => CLIError::SyntaxError {
src: source.to_string(),
msg: "Unknown parser error".to_string(),
loc: Location {
start: location,
end: location,
},
},
ParseError::IllegalState(_location) => CLIError::InternalCompilerError {
src: source.to_string(),
},
_ => {
todo!("Not yet handled {:?}", err);
}
}
}
}

impl Validator for PartiqlHelper {
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
// TODO remove this command parsing hack do something better
jpschorr marked this conversation as resolved.
Show resolved Hide resolved
let mut source = ctx.input();
let flag_display = source.starts_with("\\ast");
if flag_display {
source = &source[4..];
}

let parser = partiql_parser::Parser::default();
let source = ctx.input();
let result = parser.parse(source);
match result {
Ok(_) => Ok(ValidationResult::Valid(None)),
Ok(_parsed) => {
#[cfg(feature = "visualize")]
if flag_display {
use crate::visualize::render::display;
display(&_parsed.ast);
}

Ok(ValidationResult::Valid(None))
}
Err(e) => {
if e.errors
.iter()
Expand All @@ -214,7 +151,7 @@ impl Validator for PartiqlHelper {
}
}

fn main() -> miette::Result<()> {
pub fn repl() -> miette::Result<()> {
let mut rl = rustyline::Editor::<PartiqlHelper>::new();
rl.set_color_mode(ColorMode::Forced);
rl.set_helper(Some(
Expand Down Expand Up @@ -252,9 +189,3 @@ fn main() -> miette::Result<()> {

Ok(())
}

#[cfg(test)]
mod tests {
#[test]
fn todo() {}
}
Loading