Add support for implicit semicolons in the repl

master
Nick Krichevsky 2024-05-22 17:32:11 -04:00
parent 893dab62c7
commit 3c348e92ca
6 changed files with 155 additions and 54 deletions

View File

@ -47,7 +47,7 @@ fn do_ast_codegen<W: Write>(mut output: W) {
),
("Grouping", "expr: Box<Expr>"),
("Unary", "expr: Box<Expr>, operator: Token"),
("Literal", "value: LiteralValue"),
("Literal", "value: LiteralValue, token: Token"),
("Variable", "name: Token"),
]);

View File

@ -70,7 +70,11 @@ impl<'a> InterpreterRunner<'a> {
}
impl ExprVisitor<Result<EvaluatedValue, ScriptError>> for InterpreterRunner<'_> {
fn visit_literal(&mut self, value: &LiteralValue) -> Result<EvaluatedValue, ScriptError> {
fn visit_literal(
&mut self,
value: &LiteralValue,
_token: &Token,
) -> Result<EvaluatedValue, ScriptError> {
Ok(value.into())
}

View File

@ -1,7 +1,4 @@
use std::{
borrow::BorrowMut,
collections::{hash_map::Entry, BTreeMap, HashMap},
};
use std::collections::{hash_map::Entry, BTreeMap, HashMap};
use super::value::EvaluatedValue;
@ -27,24 +24,11 @@ impl Default for Environment {
impl Environment {
const ROOT_KEY: u32 = 0;
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn root_scope(&self) -> &Scope {
self.scopes
.get(&Self::ROOT_KEY)
.expect("no root environment defined")
}
#[must_use]
pub fn root_scope_mut(&mut self) -> &mut Scope {
self.scopes
.get_mut(&Self::ROOT_KEY)
.expect("no root environment defined")
}
pub fn enter_scope(&mut self) {
let parent_key = self.current_scope_key();
let child_key = self.next_scope_key();

View File

@ -3,7 +3,9 @@
use crate::ast::{Expr, ExprVisitor};
use std::fmt::{self, Display, Formatter};
use ast::Stmt;
pub use eval::Interpreter;
use lex::Token;
mod ast;
mod eval;
@ -37,26 +39,54 @@ impl Display for ScriptErrors {
}
}
pub fn run(script: &str) -> Result<(), ScriptErrors> {
let mut interpreter = Interpreter::new();
run_on(&mut interpreter, script)
pub fn run_script(script: &str) -> Result<(), ScriptErrors> {
run_on(&mut Interpreter::new(), script, parse_script_tokens)
}
pub fn run_on(interpreter: &mut Interpreter, script: &str) -> Result<(), ScriptErrors> {
pub fn run_repl_input(interpreter: &mut Interpreter, script: &str) -> Result<(), ScriptErrors> {
run_on(interpreter, script, parse_repl_tokens)
}
fn run_on<F: FnOnce(Vec<Token>) -> Result<Vec<Stmt>, ScriptErrors>>(
interpreter: &mut Interpreter,
script: &str,
parse_tokens: F,
) -> Result<(), ScriptErrors> {
let tokens = lex_source(script)?;
let parsed = parse_tokens(tokens)?;
let eval_res = interpreter.evaluate(&parsed);
eval_res.map_err(|err| ScriptErrors(vec![err]))
}
fn lex_source(script: &str) -> Result<Vec<Token>, ScriptErrors> {
let mut errors = Vec::new();
let tokens = lex::scan_source(script, |err| errors.push(err));
if !errors.is_empty() {
return Err(errors.into());
}
Ok(tokens)
}
fn parse_script_tokens(tokens: Vec<Token>) -> Result<Vec<Stmt>, ScriptErrors> {
let mut errors = vec![];
let parsed = parse::parse_program(tokens.into_iter(), |err| errors.push(err));
if !errors.is_empty() {
return Err(errors.into());
}
let eval_res = interpreter.evaluate(&parsed);
Ok(parsed)
}
eval_res.map_err(|err| ScriptErrors(vec![err]))
fn parse_repl_tokens(tokens: Vec<Token>) -> Result<Vec<Stmt>, ScriptErrors> {
let mut errors = vec![];
let parsed = parse::parse_repl_line(tokens.into_iter(), |err| errors.push(err));
if !errors.is_empty() {
return Err(errors.into());
}
Ok(parsed)
}
struct ASTPrinter;
@ -92,7 +122,7 @@ impl ExprVisitor<String> for ASTPrinter {
self.parenthesize(operator.lexeme(), &[expr])
}
fn visit_literal(&mut self, value: &ast::LiteralValue) -> String {
fn visit_literal(&mut self, value: &ast::LiteralValue, _token: &lex::Token) -> String {
match value {
ast::LiteralValue::Nil => "nil".to_string(),
ast::LiteralValue::False => "false".to_string(),
@ -106,7 +136,7 @@ impl ExprVisitor<String> for ASTPrinter {
name.lexeme().to_owned()
}
fn visit_assign(&mut self, name: &lex::Token, value: &Expr) -> String {
fn visit_assign(&mut self, _name: &lex::Token, _value: &Expr) -> String {
todo!()
}
}
@ -125,10 +155,12 @@ mod tests {
let result = ASTPrinter.visit_expr(&Expr::Binary {
left: Box::new(Expr::Literal {
value: ast::LiteralValue::Number(123_f64),
token: Token::new(TokenKind::Number(456_f64), "456".to_string(), 10),
}),
operator: Token::new(TokenKind::Plus, "+".to_string(), 1),
right: Box::new(Expr::Literal {
value: ast::LiteralValue::Number(456_f64),
token: Token::new(TokenKind::Number(456_f64), "456".to_string(), 10),
}),
});
@ -141,6 +173,7 @@ mod tests {
left: Box::new(Expr::Unary {
expr: Box::new(Expr::Literal {
value: ast::LiteralValue::Number(123_f64),
token: Token::new(TokenKind::Number(123_f64), "123".to_string(), 10),
}),
operator: Token::new(TokenKind::Minus, "-".to_string(), 1),
}),
@ -148,6 +181,7 @@ mod tests {
right: Box::new(Expr::Grouping {
expr: Box::new(Expr::Literal {
value: ast::LiteralValue::Number(456.789_f64),
token: Token::new(TokenKind::Number(456.789_f64), "456.789".to_string(), 10),
}),
}),
});

View File

@ -33,7 +33,7 @@ fn run_prompt() -> anyhow::Result<()> {
print_prompt()?;
for line_res in stdin.lines() {
let line = line_res.with_context(|| "failed to read input line")?;
let run_res = jlox_rust::run_on(&mut interpreter, &line);
let run_res = jlox_rust::run_repl_input(&mut interpreter, &line);
if let Err(err) = run_res {
eprintln!("{err}");
}
@ -46,5 +46,5 @@ fn run_prompt() -> anyhow::Result<()> {
fn run_file(path: &str) -> anyhow::Result<()> {
let script = fs::read_to_string(path)?;
jlox_rust::run(&script).map_err(anyhow::Error::new)
jlox_rust::run_script(&script).map_err(anyhow::Error::new)
}

View File

@ -1,7 +1,5 @@
use std::iter::Peekable;
use itertools::Itertools;
use crate::{
ast::{Expr, LiteralValue, Stmt},
lex::{Token, TokenKind},
@ -15,10 +13,58 @@ struct ParseError {
line: Option<usize>,
}
enum ParsedStatement {
Stmt { stmt: Stmt },
ImplicitStmt { stmt: Stmt, line_number: usize },
}
pub fn parse_program<I: Iterator<Item = Token>, F: FnMut(ScriptError)>(
iter: I,
mut on_error: F,
) -> Vec<Stmt> {
let statements = parse(iter, &mut on_error);
ensure_parsed_statements_explicit(statements, on_error)
}
pub fn parse_repl_line<I: Iterator<Item = Token> + Clone, F: FnMut(ScriptError)>(
iter: I,
mut on_error: F,
) -> Vec<Stmt> {
let mut statements = parse(iter, &mut on_error);
if statements.len() == 1 {
if let ParsedStatement::ImplicitStmt { stmt, .. } = statements.remove(0) {
return vec![stmt];
}
}
ensure_parsed_statements_explicit(statements, on_error)
}
fn ensure_parsed_statements_explicit<F: FnMut(ScriptError)>(
statements: Vec<ParsedStatement>,
mut on_error: F,
) -> Vec<Stmt> {
statements
.into_iter()
.filter_map(|parsed| match ensure_explicit_statement(parsed) {
Ok(stmt) => Some(stmt),
Err(err) => {
on_error(ScriptError {
message: err.message.clone(),
// TODO: This sucks and we should make ScriptError handle optional line numbers somehow
line: err.line.unwrap_or_default(),
location: String::new(),
});
None
}
})
.collect()
}
fn parse<I: Iterator<Item = Token>, F: FnMut(ScriptError)>(
iter: I,
mut on_error: F,
) -> Vec<ParsedStatement> {
let peekable_iter = &mut iter.peekable();
let mut statements = vec![];
while let Some(peeked_token) = peekable_iter.peek() {
@ -74,7 +120,7 @@ fn synchronize_to_next_statement<I: Iterator<Item = Token>>(iter: &mut I) {
fn parse_declaration<I: Iterator<Item = Token>>(
iter: &mut Peekable<I>,
) -> Result<Stmt, ParseError> {
) -> Result<ParsedStatement, ParseError> {
let parse_res = if let Some(var_token) = match_next_token(iter, &TokenKind::Var) {
parse_var_declaration(iter).map_err(|err| ParseError {
line: Some(var_token.line()),
@ -84,7 +130,7 @@ fn parse_declaration<I: Iterator<Item = Token>>(
parse_statement(iter)
};
if parse_res.is_err() {
if parse_res.is_err() || matches!(parse_res, Ok(ParsedStatement::ImplicitStmt { .. })) {
synchronize_to_next_statement(iter);
}
@ -93,7 +139,7 @@ fn parse_declaration<I: Iterator<Item = Token>>(
fn parse_var_declaration<I: Iterator<Item = Token>>(
iter: &mut Peekable<I>,
) -> Result<Stmt, ParseError> {
) -> Result<ParsedStatement, ParseError> {
let name_token =
match_token_kind!(iter, TokenKind::Identifier(_name)).map_err(|maybe_token| {
ParseError {
@ -108,22 +154,25 @@ fn parse_var_declaration<I: Iterator<Item = Token>>(
None
};
match_token_kind!(iter, TokenKind::SemiColon).map_err(|maybe_token| ParseError {
message: "Expected ';' after variable declaration".to_string(),
line: maybe_token.map(Token::line),
})?;
Ok(Stmt::Var {
let line_number = name_token.line();
let stmt = Stmt::Var {
name: name_token,
initializer,
})
};
if match_token_kind!(iter, TokenKind::SemiColon).is_ok() {
Ok(ParsedStatement::Stmt { stmt })
} else {
Ok(ParsedStatement::ImplicitStmt { stmt, line_number })
}
}
fn parse_statement<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Stmt, ParseError> {
fn parse_statement<I: Iterator<Item = Token>>(
iter: &mut Peekable<I>,
) -> Result<ParsedStatement, ParseError> {
if match_token_kind!(iter, TokenKind::Print).is_ok() {
parse_statement_containing_expression(iter, |expression| Stmt::Print { expression })
} else if match_token_kind!(iter, TokenKind::LeftBrace).is_ok() {
parse_block_statement(iter)
parse_block_statement(iter).map(|stmt| ParsedStatement::Stmt { stmt })
} else {
parse_statement_containing_expression(iter, |expression| Stmt::Expression { expression })
}
@ -132,14 +181,16 @@ fn parse_statement<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<
fn parse_statement_containing_expression<I: Iterator<Item = Token>, F: Fn(Expr) -> Stmt>(
iter: &mut Peekable<I>,
make_statement: F,
) -> Result<Stmt, ParseError> {
) -> Result<ParsedStatement, ParseError> {
let expression = parse_expression(iter)?;
match_token_kind!(iter, TokenKind::SemiColon)
.map(|_semicolon| make_statement(expression))
.map_err(|maybe_token| ParseError {
message: "Expected a ';' after expression".to_string(),
line: maybe_token.map(Token::line),
})
let line_number = find_line_number(&expression);
let stmt = make_statement(expression);
if match_token_kind!(iter, TokenKind::SemiColon).is_ok() {
Ok(ParsedStatement::Stmt { stmt })
} else {
Ok(ParsedStatement::ImplicitStmt { stmt, line_number })
}
}
fn parse_block_statement<I: Iterator<Item = Token>>(
@ -148,12 +199,24 @@ fn parse_block_statement<I: Iterator<Item = Token>>(
let mut statements = vec![];
while match_token_kind!(iter, TokenKind::RightBrace).is_err() {
let statement = parse_declaration(iter)?;
statements.push(statement);
let explicit_statement = ensure_explicit_statement(statement)?;
statements.push(explicit_statement);
}
Ok(Stmt::Block { statements })
}
fn ensure_explicit_statement(parsed: ParsedStatement) -> Result<Stmt, ParseError> {
match parsed {
ParsedStatement::Stmt { stmt, .. } => Ok(stmt),
ParsedStatement::ImplicitStmt { line_number, .. } => Err(ParseError {
message: "Expected a ';' after expression".to_string(),
line: line_number.into(),
}),
}
}
fn parse_expression<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Expr, ParseError> {
parse_assignment(iter)
}
@ -252,21 +315,27 @@ fn parse_primary<I: Iterator<Item = Token>>(iter: &mut Peekable<I>) -> Result<Ex
match token.kind() {
TokenKind::False => Ok(Expr::Literal {
value: LiteralValue::False,
token,
}),
TokenKind::True => Ok(Expr::Literal {
value: LiteralValue::True,
token,
}),
TokenKind::Nil => Ok(Expr::Literal {
value: LiteralValue::Nil,
token,
}),
TokenKind::Number(number) => Ok(Expr::Literal {
value: LiteralValue::Number(*number),
token,
}),
TokenKind::String(_) => {
// TODO: we used to do this with into_kind, but we need to change Token to Rc<str> to fix cloning issues
// special case to avoid cloning
match token.into_kind() {
match token.kind() {
TokenKind::String(string) => Ok(Expr::Literal {
value: LiteralValue::String(string),
value: LiteralValue::String(string.clone()),
token,
}),
_ => unreachable!(),
}
@ -314,3 +383,13 @@ fn match_next_token<I: Iterator<Item = Token>>(
None | Some(_) => None,
}
}
fn find_line_number(expr: &Expr) -> usize {
match expr {
Expr::Assign { name, .. } | Expr::Variable { name } => name.line(),
Expr::Unary { operator, .. } => operator.line(),
Expr::Literal { token, .. } => token.line(),
Expr::Binary { left, .. } => find_line_number(left),
Expr::Grouping { expr } => find_line_number(expr),
}
}