feat: add repl subcommand

This commit is contained in:
Daniel Flanagan 2025-02-21 14:41:50 -06:00
parent 13835d9511
commit 18a01a7f29
4 changed files with 61 additions and 11 deletions

View file

@ -14,3 +14,5 @@ pub enum Expression {
Integer(i64), Integer(i64),
Unit, Unit,
} }
impl Expression {}

View file

@ -1,4 +1,12 @@
use std::str::FromStr; use std::{
io::{self, Write},
str::FromStr,
};
use crate::{
lexer::{self, Lexer, Source, Token},
parser::Parser,
};
struct GlobalArgs { struct GlobalArgs {
subcommand: Option<Subcommand>, subcommand: Option<Subcommand>,
@ -44,7 +52,7 @@ fn usage(exit_code: i32) -> ! {
fn parse_global_args() -> Result<GlobalArgs, ParseArgsError> { fn parse_global_args() -> Result<GlobalArgs, ParseArgsError> {
let mut subcommand = None; let mut subcommand = None;
let mut args = std::env::args(); let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() { while let Some(arg) = args.next() {
match arg.as_str() { match arg.as_str() {
@ -61,6 +69,46 @@ fn parse_global_args() -> Result<GlobalArgs, ParseArgsError> {
return Ok(GlobalArgs { subcommand }); return Ok(GlobalArgs { subcommand });
} }
pub fn run() -> crate::Result<()> { fn repl() -> ! {
Ok(()) println!("lytlang repl (^D to stop)");
print!("> ");
std::io::stdout().flush().expect("failed to flush stdout");
let mut iter = std::io::stdin().lines().enumerate().into_iter();
while let Some((i, l)) = iter.next() {
let s = match &l {
Ok(s) => s.as_str(),
Err(e) => {
eprintln!("error reading line from stdin: {}", e);
continue;
}
};
let tokens = match Lexer::new(s, Source::Repl(i)).collect() {
Ok(tokens) => tokens,
Err(e) => {
eprintln!("error lexing input: {:?}", e);
continue;
}
};
match Parser::default().parse(tokens) {
Ok(expr) => println!("< {:?}", expr),
Err(e) => {
eprintln!("error parsing tokens: {:?}", e);
continue;
}
}
println!("> ");
std::io::stdout().flush().expect("failed to flush stdout");
}
std::process::exit(0);
}
pub fn run() -> crate::Result<()> {
match parse_global_args()?.subcommand {
Some(Subcommand::Repl) => repl(),
Some(Subcommand::Help) => usage(0),
None => {
println!("no subcommand specified");
usage(1)
}
}
} }

View file

@ -29,6 +29,8 @@ pub enum Source {
Unknown, Unknown,
File(Arc<Box<Path>>), File(Arc<Box<Path>>),
Repl(usize),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -166,16 +168,12 @@ impl<'a> Lexer<'a> {
} }
} }
dbg!(&self.collected);
Ok(self.produce(BareToken::Integer( Ok(self.produce(BareToken::Integer(
self.collected self.collected
.iter() .iter()
.collect::<String>() .collect::<String>()
.parse() .parse()
.map_err(|e: ParseIntError| { .map_err(|e: ParseIntError| self.produce_error(e.into()))?,
dbg!(&e);
self.produce_error(e.into())
})?,
))) )))
} }
} }
@ -188,6 +186,7 @@ impl<'a> Iterator for Lexer<'a> {
return None; return None;
} }
if self.done && !self.sent_eof { if self.done && !self.sent_eof {
self.sent_eof = true;
return Some(Ok(Token { return Some(Ok(Token {
location: None, location: None,
token: BareToken::EndOfFile, token: BareToken::EndOfFile,
@ -309,9 +308,10 @@ mod test {
t(pos(1), BareToken::Integer(3)), t(pos(1), BareToken::Integer(3)),
t(pos(3), BareToken::Plus), t(pos(3), BareToken::Plus),
t(pos(5), BareToken::Integer(9)), t(pos(5), BareToken::Integer(9)),
t(Option::None, BareToken::EndOfFile),
]), ]),
); );
assert_eq!(tokens?.len(), 3); assert_eq!(tokens?.len(), 4);
Ok(()) Ok(())
} }
} }

View file

@ -2,7 +2,7 @@ use std::{iter::Peekable, vec::IntoIter};
use crate::{ast::*, lexer}; use crate::{ast::*, lexer};
struct Parser { pub struct Parser {
num_tokens_parsed: usize, num_tokens_parsed: usize,
} }