main: expand the repl's capabilities to include debug + all syntactic positions

This commit is contained in:
2026-01-03 22:03:10 -05:00
parent e199e15804
commit 4d92954a5f
2 changed files with 119 additions and 108 deletions

View File

@@ -1,16 +1,11 @@
//! Tests the lexer
use doughlang::{
ast::{Anno, Pat},
parser::pat::Prec as PPrec,
};
#[allow(unused_imports)]
//! Tests the lexer\
use doughlang::{
ast::{
Expr,
Anno, Annotation, Bind, Expr, Literal, Pat, Path, Use,
macro_matcher::{Match, Subst},
},
lexer::{EOF, LexError, Lexer},
parser::{ParseError, Parser},
parser::{Parse, ParseError, Parser},
span::Span,
token::{TKind, Token},
};
@@ -18,125 +13,116 @@ use repline::prebaked::*;
use std::{
error::Error,
io::{IsTerminal, stdin},
marker::PhantomData,
};
fn clear() {
print!("\x1b[H\x1b[2J\x1b[3J");
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum Verbosity {
#[default]
Pretty,
Debug,
Quiet,
}
impl From<&str> for Verbosity {
fn from(value: &str) -> Self {
match value {
"quiet" | "false" | "0" | "no" => Verbosity::Quiet,
"debug" | "d" => Verbosity::Debug,
"pretty" => Verbosity::Pretty,
_ => Default::default(),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum ParseMode {
#[default]
Expr,
Pat,
Bind,
Path,
Literal,
Use,
Tokens,
}
impl From<&str> for ParseMode {
fn from(value: &str) -> Self {
match value {
"expr" => Self::Expr,
"pat" => Self::Pat,
"bind" => Self::Bind,
"path" => Self::Path,
"literal" => Self::Literal,
"use" => Self::Use,
"tokens" => Self::Tokens,
_ => Default::default(),
}
}
}
impl ParseMode {
fn with<'a>(&self) -> fn(&'a str, Verbosity) {
match self {
Self::Expr => parse::<'a, Expr>,
Self::Pat => parse::<'a, Pat>,
Self::Bind => parse::<'a, Bind>,
Self::Path => parse::<'a, Path>,
Self::Literal => parse::<'a, Literal>,
Self::Use => parse::<'a, Use>,
Self::Tokens => tokens::<'a, dyn Parse<'a, Prec = ()>>,
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
let mut verbose = Verbosity::from(std::env::var("DO_VERBOSE").as_deref().unwrap_or_default());
let mut parsing = ParseMode::from(std::env::var("DO_PARSING").as_deref().unwrap_or_default());
if stdin().is_terminal() {
read_and("\x1b[32m", ".>", " >", |line| match line.trim_end() {
"" => Ok(Response::Continue),
"exit" => Ok(Response::Break),
"help" => {
println!("Parsing: {parsing:?} (expr, pat, bind, path, literal, use, tokens)");
println!("Verbose: {verbose:?} (pretty, debug, quiet)");
Ok(Response::Deny)
}
"clear" => {
clear();
Ok(Response::Deny)
}
"lex" => {
lex()?;
Ok(Response::Deny)
}
"expr" => {
exprs()?;
Ok(Response::Deny)
}
"pat" => {
pats()?;
Ok(Response::Deny)
}
"macro" => {
if let Err(e) = subst() {
println!("\x1b[31m{e}\x1b[0m");
}
Ok(Response::Deny)
Ok(Response::Accept)
}
line @ ("tokens" | "expr" | "pat" | "bind" | "path" | "literal" | "use") => {
parsing = ParseMode::from(line);
println!("Parse mode set to '{parsing:?}'");
Ok(Response::Accept)
}
line @ ("quiet" | "debug" | "pretty") => {
verbose = Verbosity::from(line);
println!("Verbosity set to '{verbose:?}'");
Ok(Response::Accept)
}
_ if line.ends_with("\n\n") => {
parse(line);
parsing.with()(line, verbose);
Ok(Response::Accept)
}
_ => Ok(Response::Continue),
})?;
} else {
let doc = std::io::read_to_string(stdin())?;
// lex(&doc);
parse(&doc);
parsing.with()(&doc, verbose);
}
Ok(())
}
fn lex() -> Result<(), Box<dyn Error>> {
clear();
read_and("\x1b[93m", " >", "?>", |line| {
let mut lexer = Lexer::new(line);
if line.trim().is_empty() {
return Ok(Response::Break);
}
loop {
match lexer.scan() {
Err(LexError { res: EOF, .. }) => {
break Ok(Response::Accept);
}
Err(e) => {
println!("\x1b[31m{e}\x1b[0m");
break Ok(Response::Deny);
}
Ok(Token { lexeme, kind, span: Span { head, tail } }) => {
println!("{kind:?}\x1b[11G {head:<4} {tail:<4} {lexeme:?}")
}
}
}
})?;
Ok(())
}
fn exprs() -> Result<(), Box<dyn Error>> {
clear();
read_and("\x1b[93m", ".>", " >", |line| {
let mut parser = Parser::new(Lexer::new(line));
if line.trim().is_empty() {
return Ok(Response::Break);
}
for idx in 0.. {
match parser.parse::<Anno<Expr>>(0) {
Err(ParseError::FromLexer(LexError { res: EOF, .. })) => {
return Ok(Response::Accept);
}
Err(e) => {
println!("\x1b[31m{e}\x1b[0m");
return Ok(Response::Deny);
}
Ok(v) => println!("{idx}: {v}\n{v:#?}"),
}
}
Ok(Response::Accept)
})?;
Ok(())
}
fn pats() -> Result<(), Box<dyn Error>> {
clear();
read_and("\x1b[94m", " >", "?>", |line| {
let mut parser = Parser::new(Lexer::new(line));
if line.trim().is_empty() {
return Ok(Response::Break);
}
loop {
match parser.parse::<Pat>(PPrec::Min) {
Err(ParseError::FromLexer(LexError { res: EOF, .. })) => {
break Ok(Response::Accept);
}
Err(e) => {
println!("\x1b[31m{e}\x1b[0m");
break Ok(Response::Deny);
}
Ok(v) => println!("{v}\n{v:#?}"),
}
}
})?;
Ok(())
}
fn subst() -> Result<(), Box<dyn Error>> {
let mut rl = repline::Repline::new("\x1b[35mexp", " >", "?>");
let exp = rl.read()?;
@@ -191,15 +177,34 @@ fn plural(count: usize) -> &'static str {
}
}
fn parse(document: &str) {
fn tokens<'t, T: Parse<'t> + ?Sized>(document: &'t str, verbose: Verbosity) {
let _: PhantomData<T>; // for lifetime variance
let mut lexer = Lexer::new(document);
loop {
match (lexer.scan(), verbose) {
(Err(LexError { res: EOF, .. }), _) => {
break;
}
(Err(e), _) => {
println!("\x1b[31m{e}\x1b[0m");
break;
}
(Ok(Token { lexeme, kind, span: Span { head, tail } }), Verbosity::Pretty) => {
println!("{kind:?}\x1b[11G {head:<4} {tail:<4} {lexeme:?}")
}
(Ok(token), Verbosity::Debug) => {
println!("{token:?}")
}
_ => {}
}
}
}
fn parse<'t, T: Parse<'t> + Annotation>(document: &'t str, verbose: Verbosity) {
let mut parser = Parser::new(Lexer::new(document));
let verbose = !matches!(
std::env::var("DOUGHLANG_VERBOSE").as_deref(),
Ok("false" | "0" | "no")
);
for idx in 0.. {
match parser.parse::<Anno<Expr>>(0) {
Err(e @ ParseError::EOF(s)) if s.tail == document.len() as _ => {
match (parser.parse::<Anno<T, Span>>(T::Prec::default()), verbose) {
(Err(e @ ParseError::EOF(s)), _) if s.tail == document.len() as _ => {
println!(
"\x1b[92m{e} (total {} byte{}, {idx} expression{})\x1b[0m",
document.len(),
@@ -208,7 +213,7 @@ fn parse(document: &str) {
);
break;
}
Err(e @ ParseError::EOF(_)) => {
(Err(e @ ParseError::EOF(_)), _) => {
println!(
"\x1b[93m{e} (total {} byte{}, {idx} expression{})\x1b[0m",
document.len(),
@@ -217,13 +222,16 @@ fn parse(document: &str) {
);
break;
}
Err(e) => {
(Err(e), _) => {
println!("\x1b[91m{e}\x1b[0m");
break;
}
Ok(Anno(expr, span)) if verbose => {
(Ok(Anno(expr, span)), Verbosity::Pretty) => {
println!("\x1b[{}m{span}:\n{expr}", (idx + 5) % 6 + 31);
}
(Ok(Anno(expr, span)), Verbosity::Debug) => {
println!("\x1b[{}m{span}:\n{expr:#?}", (idx + 5) % 6 + 31);
}
_ => {}
}
}

View File

@@ -1,9 +1,12 @@
#!/usr/bin/env bash
export DOUGHLANG_VERBOSE="${DOUGHLANG_VERBOSE:-0}"
export DO_VERBOSE="${DO_VERBOSE:-0}"
export DO_PARSING="${DO_PARSING:-expr}"
cargo build --release
for file in $(find "$@" -type f); do
echo $file;
cat $file | cargo run -q $CARGO_FLAGS;
cat $file | cargo run --release -q $CARGO_FLAGS;
echo;
done