use std::process::ExitCode;
use clap::{Parser, Subcommand, ValueEnum};
use regast::{Backend, Disambiguation, JsonShape, Regast};
#[derive(Debug, Parser)]
#[command(
name = "regast",
version,
about = "Inspect regular-expression syntax and derivations"
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Ast {
pattern: String,
#[arg(long, value_enum, default_value_t = Format::Json)]
format: Format,
#[arg(long, value_enum, default_value_t = Shape::Tree)]
shape: Shape,
},
Parse {
pattern: String,
input: String,
#[arg(long, value_enum, default_value_t = Format::Json)]
format: Format,
#[arg(long, conflicts_with = "greedy")]
posix: bool,
#[arg(long)]
greedy: bool,
#[arg(long, value_enum, default_value_t = BackendArg::Derivative)]
backend: BackendArg,
},
Find {
pattern: String,
input: String,
#[arg(long, value_enum, default_value_t = Format::Json)]
format: Format,
#[arg(long)]
greedy: bool,
#[arg(long, value_enum, default_value_t = BackendArg::Derivative)]
backend: BackendArg,
},
Check {
pattern: String,
input: String,
#[arg(long, value_enum, default_value_t = BackendArg::Derivative)]
backend: BackendArg,
},
Trace { pattern: String, input: String },
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
enum Format {
#[default]
Json,
Sexpr,
Dot,
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
enum Shape {
#[default]
Tree,
Arena,
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
enum BackendArg {
#[default]
Derivative,
Antimirov,
TaggedNfa,
}
impl From<BackendArg> for Backend {
fn from(value: BackendArg) -> Self {
match value {
BackendArg::Derivative => Self::Derivative,
BackendArg::Antimirov => Self::Antimirov,
BackendArg::TaggedNfa => Self::TaggedNfa,
}
}
}
fn main() -> ExitCode {
match run(Cli::parse()) {
Ok(code) => code,
Err(error) => {
eprintln!("{error}");
ExitCode::from(2)
}
}
}
fn run(cli: Cli) -> Result<ExitCode, Box<dyn std::error::Error>> {
match cli.command {
Command::Ast {
pattern,
format,
shape,
} => {
let regex = Regast::new(&pattern)?;
let output = match format {
Format::Json => serde_json::to_string_pretty(®ex.ast().to_json(match shape {
Shape::Tree => JsonShape::Tree,
Shape::Arena => JsonShape::Arena,
}))?,
Format::Sexpr => regex.ast().to_sexpr(),
Format::Dot => regex.ast().to_dot(),
};
println!("{output}");
Ok(ExitCode::SUCCESS)
}
Command::Parse {
pattern,
input,
format,
posix: _,
greedy,
backend,
} => {
let disambiguation = if greedy {
Disambiguation::Greedy
} else {
Disambiguation::Posix
};
let regex = Regast::builder(&pattern)
.disambiguation(disambiguation)
.backend(backend.into())
.build()?;
let tree = regex.parse(&input)?;
let output = match format {
Format::Json => serde_json::to_string_pretty(&tree.to_json())?,
Format::Sexpr => tree.to_sexpr(),
Format::Dot => tree.to_dot(),
};
println!("{output}");
Ok(ExitCode::SUCCESS)
}
Command::Check {
pattern,
input,
backend,
} => {
let regex = Regast::builder(&pattern).backend(backend.into()).build()?;
Ok(if regex.is_match(&input) {
ExitCode::SUCCESS
} else {
ExitCode::from(1)
})
}
Command::Find {
pattern,
input,
format,
greedy,
backend,
} => {
let regex = Regast::builder(&pattern)
.disambiguation(if greedy {
Disambiguation::Greedy
} else {
Disambiguation::Posix
})
.backend(backend.into())
.build()?;
let Some(tree) = regex.find_parse(&input)? else {
return Ok(ExitCode::from(1));
};
let output = match format {
Format::Json => serde_json::to_string_pretty(&tree.to_json())?,
Format::Sexpr => tree.to_sexpr(),
Format::Dot => tree.to_dot(),
};
println!("{output}");
Ok(ExitCode::SUCCESS)
}
Command::Trace { pattern, input } => {
let regex = Regast::new(&pattern)?;
for step in regex.trace(&input) {
println!(
"{} {:?}: {} -> {}",
step.char_index, step.c, step.before, step.after
);
}
Ok(ExitCode::SUCCESS)
}
}
}