mirror of
https://github.com/teo3300/rust-mal.git
synced 2026-01-13 01:35:31 +01:00
Error handling and small editor
- Error handling to signal what is preventing evaluation instead of
program crash
- Small editor feature (allow edit new
line of code to complete the previous ones instead of destroying the
input, if an empty line inserted return the error preventing evaluation
- Auto indentation on multiline input based on depth
Still does not parse multi-statement code
- This is a problem when dealing with macros: does not allow
expressions like `'()` since the atomic `'` hides the list ().
Need to chose between:
- Replace `'...` with `(quote ... )` during tokenization (may be
hard to implement macros later)
- Allows multi-statement code (this also allows to execute multiple
statements when reading a file)
Will probably delete auto-indentation since it breaks code's uniformity
too much
This commit is contained in:
29
src/main.rs
29
src/main.rs
@ -15,9 +15,34 @@ fn main() -> io::Result<()> {
|
|||||||
let _ = io::stdout().flush();
|
let _ = io::stdout().flush();
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
loop {
|
||||||
|
// Read line to compose program inpug
|
||||||
|
let mut line = String::new();
|
||||||
|
io::stdin().read_line(&mut line)?;
|
||||||
|
|
||||||
io::stdin().read_line(&mut input)?;
|
// Append line to input
|
||||||
|
input.push_str(&line);
|
||||||
|
|
||||||
println!("{}", rep(&input.replace("\n", " ")));
|
// If there is nothing to evaluate skip rep
|
||||||
|
if input == "\n" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform rep on whole available input
|
||||||
|
match rep(&input) {
|
||||||
|
Ok(output) => println!("{}", output),
|
||||||
|
Err((err, depth)) => {
|
||||||
|
if line == "\n" {
|
||||||
|
println!("ERROR: {}", err);
|
||||||
|
} else {
|
||||||
|
print!("user> {}", " ".repeat(depth));
|
||||||
|
// Flush the prompt to appear before command
|
||||||
|
let _ = io::stdout().flush();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,39 @@
|
|||||||
|
use crate::types::escape_str;
|
||||||
use crate::types::MalType;
|
use crate::types::MalType;
|
||||||
|
use crate::types::MalType::*;
|
||||||
|
|
||||||
pub fn pr_str(ast: &MalType) -> String {
|
pub fn pr_str(ast: &MalType, print_readably: bool) -> String {
|
||||||
match ast {
|
match ast {
|
||||||
MalType::Nil => "nil".to_string(),
|
Nil => "nil".to_string(),
|
||||||
MalType::Symbol(sym) => sym.to_string(),
|
Symbol(sym) | Keyword(sym) => sym.to_string(),
|
||||||
MalType::Integer(val) => val.to_string(),
|
Int(val) => val.to_string(),
|
||||||
MalType::Bool(val) => val.to_string(),
|
Bool(val) => val.to_string(),
|
||||||
MalType::List(el) => format!(
|
Str(str) => {
|
||||||
|
if print_readably {
|
||||||
|
escape_str(str)
|
||||||
|
} else {
|
||||||
|
str.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List(el) => format!(
|
||||||
"({})",
|
"({})",
|
||||||
el.iter()
|
el.iter()
|
||||||
.map(|sub| pr_str(sub))
|
.map(|e| pr_str(e, print_readably))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
),
|
),
|
||||||
// This is truly horrible
|
// This is truly horrible
|
||||||
MalType::Vector(el) => format!(
|
Vector(el) => format!(
|
||||||
"[{}]",
|
"[{}]",
|
||||||
el.iter()
|
el.iter()
|
||||||
.map(|sub| pr_str(sub))
|
.map(|e| pr_str(e, print_readably))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
),
|
),
|
||||||
MalType::Map(el) => format!(
|
Map(el) => format!(
|
||||||
"{{{}}}",
|
"{{{}}}",
|
||||||
el.iter()
|
el.iter()
|
||||||
.map(|sub| vec![pr_str(sub.0), pr_str(sub.1)].join(" "))
|
.map(|sub| vec![sub.0.to_string(), pr_str(sub.1, print_readably)].join(" "))
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(" ")
|
.join(" ")
|
||||||
),
|
),
|
||||||
|
|||||||
172
src/reader.rs
172
src/reader.rs
@ -1,38 +1,43 @@
|
|||||||
use std::collections::{BTreeMap, VecDeque};
|
// Specyfy components in "types"
|
||||||
|
use crate::types::*;
|
||||||
use crate::types::MalType;
|
// By specifying enum variants it's possible to omit namespace
|
||||||
|
use crate::types::MalType::*;
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
pub struct Reader {
|
pub struct Reader {
|
||||||
tokens: VecDeque<String>,
|
tokens: Vec<String>,
|
||||||
|
ptr: usize,
|
||||||
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: instead of panic on missing ")" try implementing a multi line parsing
|
// TODO: instead of panic on missing ")" try implementing a multi line parsing
|
||||||
// Status on return should always be The last element of the last opened lists
|
// Status on return should always be The last element of the last opened lists
|
||||||
// (append to the "last" list) while traversing
|
// (append to the "last" list) while traversing
|
||||||
const PAREN_ERROR: &str =
|
|
||||||
"Looks like you reached a dead end, did you perhaps miss any \")\" or left some extra \"(\"?";
|
|
||||||
|
|
||||||
impl Reader {
|
impl Reader {
|
||||||
fn new(tokens: VecDeque<String>) -> Reader {
|
fn new(tokens: Vec<String>) -> Reader {
|
||||||
Reader { tokens }
|
Reader {
|
||||||
|
tokens,
|
||||||
|
ptr: 0,
|
||||||
|
depth: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the token at the current positioni
|
/// Returns the token at the current position
|
||||||
fn peek(&self) -> &str {
|
fn peek(&self) -> Result<String, MalErr> {
|
||||||
match self.tokens.get(0) {
|
match self.tokens.get(self.ptr) {
|
||||||
Some(token) => token,
|
Some(token) => Ok(token.to_string()),
|
||||||
None => panic!("{}", PAREN_ERROR),
|
None => Err("Unexpected EOF, Missing parenthesis?".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the token at current position and increment current position
|
/// Returns the token at current position and increment current position
|
||||||
// TODO: PLEASE USE THE PEEK FUNCTION
|
// TODO: PLEASE USE THE PEEK FUNCTION
|
||||||
fn next(&mut self) -> String {
|
fn next(&mut self) -> Result<String, MalErr> {
|
||||||
match self.tokens.pop_front() {
|
self.ptr += 1;
|
||||||
Some(token) => token,
|
match self.tokens.get(self.ptr - 1) {
|
||||||
None => panic!("{}", PAREN_ERROR),
|
Some(token) => Ok(token.to_string()),
|
||||||
|
None => Err("Unexpected EOF, Missing parenthesis?".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,35 +46,54 @@ impl Reader {
|
|||||||
/// Accumulates results into a MalList
|
/// Accumulates results into a MalList
|
||||||
/// NOTE: `read_list` calls `read_form` -> enable recursion
|
/// NOTE: `read_list` calls `read_form` -> enable recursion
|
||||||
/// (lists can contains other lists)
|
/// (lists can contains other lists)
|
||||||
fn read_list(&mut self, terminator: &str) -> Vec<MalType> {
|
fn read_list(&mut self, terminator: &str) -> MalRet {
|
||||||
std::iter::from_fn(|| match self.peek() {
|
self.depth += 1;
|
||||||
")" | "]" | "}" => {
|
|
||||||
if terminator != self.peek() {
|
self.next()?;
|
||||||
panic!("Unexpected token: {}", self.peek())
|
|
||||||
|
let mut vector = Vec::new();
|
||||||
|
loop {
|
||||||
|
let token = self.peek()?;
|
||||||
|
if token == terminator {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
self.next();
|
vector.push(self.read_form()?)
|
||||||
None
|
|
||||||
}
|
}
|
||||||
_ => Some(self.read_form()),
|
self.next()?;
|
||||||
})
|
let ret = match terminator {
|
||||||
.collect()
|
")" => Ok(List(vector)),
|
||||||
|
"]" => Ok(Vector(vector)),
|
||||||
|
"}" => make_map(vector),
|
||||||
|
_ => Err(format!("Unknown collection terminator: {}", terminator)),
|
||||||
|
};
|
||||||
|
self.depth -= 1;
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read atomic token and return appropriate scalar ()
|
/// Read atomic token and return appropriate scalar ()
|
||||||
fn read_atom(&mut self) -> MalType {
|
fn read_atom(&mut self) -> MalRet {
|
||||||
let token = self.next();
|
let token = self.next()?;
|
||||||
// parse the token as an integer
|
let re_digits = Regex::new(r"^-?[0-9]+$").unwrap();
|
||||||
match token.parse::<i32>() {
|
match token.as_str() {
|
||||||
// On success assign the value
|
")" | "]" | "}" => Err(format!("Lone parenthesis {}", token)),
|
||||||
Ok(value) => MalType::Integer(value),
|
"false" => Ok(Bool(false)),
|
||||||
// Otherwise assign the symbol
|
"true" => Ok(Bool(true)),
|
||||||
Err(_) => match token.as_str() {
|
"nil" => Ok(Nil),
|
||||||
")" | "]" | "}" => panic!("Lone parenthesis {}", token),
|
_ => {
|
||||||
"false" => MalType::Bool(false),
|
if re_digits.is_match(&token) {
|
||||||
"true" => MalType::Bool(true),
|
Ok(Int(token.parse::<isize>().unwrap()))
|
||||||
"nil" => MalType::Nil,
|
} else if token.starts_with('\"') {
|
||||||
_ => MalType::Symbol(token),
|
if token.ends_with('\"') {
|
||||||
},
|
Ok(Str(unescape_str(&token)))
|
||||||
|
} else {
|
||||||
|
Err("Unterminated string, expected \"".to_string())
|
||||||
|
}
|
||||||
|
} else if token.starts_with(':') {
|
||||||
|
Ok(Keyword(token))
|
||||||
|
} else {
|
||||||
|
Ok(Symbol(token))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,31 +102,14 @@ impl Reader {
|
|||||||
/// Switch on the first character
|
/// Switch on the first character
|
||||||
/// "(" -> call `read_list`
|
/// "(" -> call `read_list`
|
||||||
/// otherwise -> call `read_atom`
|
/// otherwise -> call `read_atom`
|
||||||
fn read_form(&mut self) -> MalType {
|
fn read_form(&mut self) -> MalRet {
|
||||||
match self.peek() {
|
let token = self.peek()?;
|
||||||
|
// String slice containing the whole string
|
||||||
|
match &token[..] {
|
||||||
// Consume "(" and parse list
|
// Consume "(" and parse list
|
||||||
"(" => {
|
"(" => self.read_list(")"),
|
||||||
self.next();
|
"[" => self.read_list("]"),
|
||||||
MalType::List(self.read_list(")"))
|
"{" => self.read_list("}"),
|
||||||
}
|
|
||||||
"[" => {
|
|
||||||
self.next();
|
|
||||||
MalType::Vector(self.read_list("]"))
|
|
||||||
}
|
|
||||||
"{" => {
|
|
||||||
self.next();
|
|
||||||
// fallback to C mode for now 😎
|
|
||||||
let list = self.read_list("}");
|
|
||||||
if list.len() % 2 != 0 {
|
|
||||||
panic!("Missing Map element")
|
|
||||||
}
|
|
||||||
let mut map = BTreeMap::new();
|
|
||||||
for i in (0..list.len()).step_by(2) {
|
|
||||||
map.insert(list[i].clone(), list[i + 1].clone());
|
|
||||||
}
|
|
||||||
MalType::Map(map)
|
|
||||||
}
|
|
||||||
// read atomically
|
|
||||||
_ => self.read_atom(),
|
_ => self.read_atom(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,23 +119,36 @@ impl Reader {
|
|||||||
/// Create anew Reader with the tokens
|
/// Create anew Reader with the tokens
|
||||||
/// Call read_from with the reader instance
|
/// Call read_from with the reader instance
|
||||||
/// TODO: catch errors
|
/// TODO: catch errors
|
||||||
pub fn read_str(input: &str) -> MalType {
|
pub fn read_str(input: &str) -> Result<MalType, (MalErr, usize)> {
|
||||||
let ast = Reader::new(tokenize(input)).read_form();
|
let tokens = tokenize(input);
|
||||||
// pretty_print(&ast, 0);
|
match tokens.len() {
|
||||||
ast
|
0 => Ok(Nil),
|
||||||
|
_ => {
|
||||||
|
let mut reader = Reader::new(tokens);
|
||||||
|
match reader.read_form() {
|
||||||
|
Err(err) => Err((err, reader.depth)),
|
||||||
|
Ok(any) => Ok(any),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a string and return a list of tokens in it (following regex in README)
|
/// Read a string and return a list of tokens in it (following regex in README)
|
||||||
// Add error handling for strings that are not terminated
|
// Add error handling for strings that are not terminated
|
||||||
fn tokenize(input: &str) -> VecDeque<String> {
|
fn tokenize(input: &str) -> Vec<String> {
|
||||||
let mut tokens = VecDeque::new();
|
let mut tokens = Vec::new();
|
||||||
|
|
||||||
let re =
|
let re = Regex::new(
|
||||||
Regex::new(r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)"###)
|
r###"[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*\n|[^\s\[\]{}('"`,;)]*)"###,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for match_str in re.captures_iter(input) {
|
for match_str in re.captures_iter(input) {
|
||||||
if match_str[1].len() > 0 {
|
if !match_str[1].is_empty() {
|
||||||
tokens.push_back(match_str[1].to_string());
|
// Drop comments
|
||||||
|
if match_str[1].starts_with(';') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tokens.push(match_str[1].to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,15 @@
|
|||||||
|
|
||||||
use crate::printer::pr_str;
|
use crate::printer::pr_str;
|
||||||
use crate::reader::read_str;
|
use crate::reader::read_str;
|
||||||
use crate::types::MalType;
|
use crate::types::{MalErr, MalType};
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
/// Read input and generate an ast
|
/// Read input and generate an ast
|
||||||
fn READ(input: &str) -> MalType {
|
fn READ(input: &str) -> Result<MalType, (MalErr, usize)> {
|
||||||
read_str(input)
|
match read_str(input) {
|
||||||
|
Ok(ast) => Ok(ast),
|
||||||
|
Err((err, depth)) => Err((format!("Unexpected error during READ: {}", err), depth)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
@ -24,11 +27,11 @@ fn EVAL(ast: MalType) -> MalType {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
/// Print out the result of the evaluation
|
/// Print out the result of the evaluation
|
||||||
fn PRINT(input: MalType) -> String {
|
fn PRINT(input: MalType) -> String {
|
||||||
pr_str(&input)
|
pr_str(&input, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rep(input: &str) -> String {
|
pub fn rep(input: &str) -> Result<String, (MalErr, usize)> {
|
||||||
let ast = READ(input);
|
let ast = READ(input)?;
|
||||||
let out = EVAL(ast);
|
let out = EVAL(ast);
|
||||||
PRINT(out /*&result*/)
|
Ok(PRINT(out))
|
||||||
}
|
}
|
||||||
|
|||||||
63
src/types.rs
63
src/types.rs
@ -1,16 +1,69 @@
|
|||||||
// TODO: use enums for MalTypes
|
// TODO: use enums for MalTypes
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// All Mal types should inherit from this
|
// All Mal types should inherit from this
|
||||||
#[derive(Debug, Ord, Eq, PartialEq, PartialOrd, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MalType {
|
pub enum MalType {
|
||||||
List(Vec<MalType>),
|
List(Vec<MalType>),
|
||||||
Vector(Vec<MalType>),
|
Vector(Vec<MalType>),
|
||||||
// HashMap cannot implement Hash
|
Map(HashMap<String, MalType>),
|
||||||
Map(BTreeMap<MalType, MalType>),
|
|
||||||
Symbol(String),
|
Symbol(String),
|
||||||
Integer(i32),
|
Keyword(String),
|
||||||
|
Str(String),
|
||||||
|
Int(isize),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
Nil,
|
Nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stolen, but this way it's easier to handle errors
|
||||||
|
|
||||||
|
/*
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MalErr {
|
||||||
|
Str(String), // Messages to the user
|
||||||
|
// Val(MalType),
|
||||||
|
// Messages to the program
|
||||||
|
} TEMP TEMP */
|
||||||
|
pub type MalErr = String;
|
||||||
|
|
||||||
|
pub type MalArgs = Vec<MalType>;
|
||||||
|
pub type MalRet = Result<MalType, MalErr>;
|
||||||
|
|
||||||
|
use MalType::{Map, Symbol};
|
||||||
|
|
||||||
|
pub fn make_map(list: MalArgs) -> MalRet {
|
||||||
|
if list.len() % 2 != 0 {
|
||||||
|
return Err("Map length is odd: missing value".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
for i in (0..list.len()).step_by(2) {
|
||||||
|
match &list[i] {
|
||||||
|
Symbol(k) => {
|
||||||
|
let v = list[i + 1].clone();
|
||||||
|
map.insert(k.to_string(), v);
|
||||||
|
}
|
||||||
|
_ => return Err(format!("Map key not valid: {:?}", list[i])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Map(map))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn escape_str(s: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"\"{}\"",
|
||||||
|
String::from(s)
|
||||||
|
.replace('\\', "\\\\")
|
||||||
|
.replace('\n', "\\n")
|
||||||
|
.replace('\"', "\\\"")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unescape_str(s: &str) -> String {
|
||||||
|
String::from(&s[1..s.len() - 1])
|
||||||
|
.replace("\\\\", "\\")
|
||||||
|
.replace("\\n", "\n")
|
||||||
|
.replace("\\\"", "\"")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user