Skip to content

Commit

Permalink
Add visualization of ASTs to CLI & REPL
Browse files Browse the repository at this point in the history
- Add actual arg parsing to CLI
- Make REPL a sub-mode initiated by a CLI command
- Add partial (lots of `todo!()`s) translation of AST into graphviz
- Add FFI to graphviz to layout & render
- Add `display` mode which prints image to console
  • Loading branch information
jpschorr committed Jul 25, 2022
1 parent 62a2159 commit d99cbde
Show file tree
Hide file tree
Showing 11 changed files with 951 additions and 96 deletions.
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 }
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 = [
"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
#[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);
}
}
}
}
File renamed without changes.
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
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(())
}
}
}
File renamed without changes.
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
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

0 comments on commit d99cbde

Please sign in to comment.