This commit is contained in:
2025-09-07 03:48:48 +10:00
parent 2f3f853134
commit 8d03316b1b
63 changed files with 1941 additions and 420 deletions

View File

@@ -1,5 +1,7 @@
use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use crate::evaluator::*;
#[derive(Clone)]
@@ -49,23 +51,24 @@ impl Cell {
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct CellRef {
pub row: i64,
pub col: i64,
pub row: usize,
pub col: usize,
}
impl CellRef {
// Zero indexed
pub fn new(s: String) -> Result<CellRef, String> {
let s = s.trim();
let mut col: i64 = 0;
let mut col: usize = 0;
let mut i = 0;
// consume leading letters for the column
for (idx, ch) in s.char_indices() {
if ch.is_ascii_alphabetic() {
let u = ch.to_ascii_uppercase() as u8;
let val = (u - b'A' + 1) as i64; // A->1 ... Z->26
let val = (u - b'A' + 1) as usize; // A->1 ... Z->26
col = col * 26 + val;
i = idx + ch.len_utf8();
} else {
@@ -90,8 +93,11 @@ impl CellRef {
));
}
if let Ok(row) = row_part.parse::<i64>() {
Ok(CellRef { row, col })
if let Ok(row) = row_part.parse::<usize>() {
Ok(CellRef {
row: row - 1,
col: col - 1,
})
} else {
Err(format!("Parse error: invalid row number."))
}

0
backend/src/common.rs Normal file
View File

View File

@@ -1,7 +1,8 @@
use crate::cell::{Cell, CellRef};
use crate::cell::CellRef;
use crate::grid::Grid;
use crate::parser::*;
use crate::tokenizer::Literal;
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use std::fmt;
#[derive(Debug, PartialEq, Clone)]
@@ -17,115 +18,58 @@ impl fmt::Display for Eval {
}
}
pub struct Evaluator {
cells: HashMap<CellRef, Cell>,
pub fn evaluate(str: String, grid: Option<&Grid>) -> Result<(Eval, HashSet<CellRef>), String> {
let (expr, deps) = parse(&str)?;
match evaluate_expr(&expr, grid) {
Ok(it) => Ok((it, deps)),
Err(it) => Err(it),
}
}
impl Evaluator {
pub fn new() -> Evaluator {
return Evaluator {
cells: HashMap::new(),
};
}
pub fn set_cell(&mut self, cell_ref: CellRef, raw_val: String) -> Result<Eval, String> {
if self.cells.contains_key(&cell_ref) && self.cells[&cell_ref].raw() == raw_val {
return self.get_cell(cell_ref);
}
let eval: Eval;
let deps: HashSet<CellRef>;
if let Some(c) = raw_val.chars().nth(0)
&& c == '='
{
(eval, deps) = self.evaluate(raw_val[1..].to_owned())?;
// for dep in deps {}
} else {
match self.evaluate(raw_val.to_owned()) {
Ok(e) => {
(eval, deps) = e;
}
Err(_) => eval = Eval::Literal(Literal::String(raw_val.to_owned())),
fn evaluate_expr(expr: &Expr, grid: Option<&Grid>) -> Result<Eval, String> {
let res = match expr {
Expr::Literal(lit) => Eval::Literal(lit.clone()),
Expr::CellRef(re) => {
if let Some(g) = grid {
g.get_cell(re.to_owned())?
} else {
return Err("Evaluation error: Found cell reference but no grid.".into());
}
}
Expr::Infix { op, lhs, rhs } => {
let lval = evaluate_expr(lhs, grid)?;
let rval = evaluate_expr(rhs, grid)?;
self.cells
.insert(cell_ref, Cell::new(eval.clone(), raw_val));
Ok(eval)
}
// pub fn get_cell(&mut self, cell_ref: CellRef) -> Result<(String, Eval), String> {
pub fn get_cell(&mut self, cell_ref: CellRef) -> Result<Eval, String> {
if !self.cells.contains_key(&cell_ref) {
return Err(format!("Cell at {:?} not found.", cell_ref));
}
let cell = &self.cells[&cell_ref];
// Ok((cell.raw(), cell.eval()))
Ok(cell.eval())
}
pub fn add_cell_dep(&mut self, cell_ref: CellRef, dep_ref: CellRef) -> Result<(), String> {
if !self.cells.contains_key(&cell_ref) {
return Err(format!("Cell at {:?} not found.", cell_ref));
}
if let Some(cell) = self.cells.get_mut(&cell_ref) {
cell.add_i_dep(dep_ref);
}
Ok(())
}
pub fn evaluate(&mut self, str: String) -> Result<(Eval, HashSet<CellRef>), String> {
let (expr, deps) = parse(&str)?;
match self.evaluate_expr(&expr) {
Ok(it) => Ok((it, deps)),
Err(it) => Err(it),
}
}
fn evaluate_expr(&mut self, expr: &Expr) -> Result<Eval, String> {
let res = match expr {
Expr::Literal(lit) => Eval::Literal(lit.clone()),
Expr::CellRef(re) => self.get_cell(re.to_owned())?,
Expr::Infix { op, lhs, rhs } => {
let lval = self.evaluate_expr(lhs)?;
let rval = self.evaluate_expr(rhs)?;
match op {
InfixOp::ADD => eval_add(&lval, &rval)?,
InfixOp::SUB => eval_sub(&lval, &rval)?,
InfixOp::MUL => eval_mul(&lval, &rval)?,
InfixOp::DIV => eval_div(&lval, &rval)?,
_ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)),
}
match op {
InfixOp::ADD => eval_add(&lval, &rval)?,
InfixOp::SUB => eval_sub(&lval, &rval)?,
InfixOp::MUL => eval_mul(&lval, &rval)?,
InfixOp::DIV => eval_div(&lval, &rval)?,
_ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)),
}
Expr::Prefix { op, expr } => {
let val = self.evaluate_expr(expr)?;
}
Expr::Prefix { op, expr } => {
let val = evaluate_expr(expr, grid)?;
match op {
PrefixOp::POS => eval_pos(&val)?,
PrefixOp::NEG => eval_neg(&val)?,
PrefixOp::NOT => eval_not(&val)?,
_ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)),
}
match op {
PrefixOp::POS => eval_pos(&val)?,
PrefixOp::NEG => eval_neg(&val)?,
PrefixOp::NOT => eval_not(&val)?,
// _ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)),
}
Expr::Group(g) => self.evaluate_expr(g)?,
it => return Err(format!("Evaluation error: Unsupported expression {:?}", it)),
};
}
Expr::Group(g) => evaluate_expr(g, grid)?,
it => return Err(format!("Evaluation error: Unsupported expression {:?}", it)),
};
Ok(res)
}
Ok(res)
}
fn eval_add(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
match (lval, rval) {
(Eval::Literal(a), Eval::Literal(b)) => {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x + y, |x, y| x + y) {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x + y) {
return Ok(Eval::Literal(res));
}
@@ -144,7 +88,7 @@ fn eval_add(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
fn eval_sub(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
match (lval, rval) {
(Eval::Literal(a), Eval::Literal(b)) => {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x - y, |x, y| x - y) {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x - y) {
return Ok(Eval::Literal(res));
}
@@ -155,7 +99,7 @@ fn eval_sub(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
fn eval_mul(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
match (lval, rval) {
(Eval::Literal(a), Eval::Literal(b)) => {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x * y, |x, y| x * y) {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x * y) {
return Ok(Eval::Literal(res));
}
@@ -166,15 +110,15 @@ fn eval_mul(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
fn eval_div(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
match (lval, rval) {
(Eval::Literal(a), Eval::Literal(b)) => {
if let (Literal::Integer(_), Literal::Integer(y)) = (a, b) {
if *y == 0 {
if let (Literal::Number(_), Literal::Number(y)) = (a, b) {
if *y == 0f64 {
return Err(
"Evaluation error: integers attempted to divide by zero.".to_string()
);
}
}
if let Some(res) = eval_numeric_infix(a, b, |x, y| x / y, |x, y| x / y) {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x / y) {
return Ok(Eval::Literal(res));
}
@@ -183,41 +127,23 @@ fn eval_div(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
}
}
fn eval_numeric_infix<FInt, FDouble>(
lhs: &Literal,
rhs: &Literal,
int_op: FInt,
double_op: FDouble,
) -> Option<Literal>
where
FInt: Fn(i64, i64) -> i64,
FDouble: Fn(f64, f64) -> f64,
{
fn eval_numeric_infix(lhs: &Literal, rhs: &Literal, op: fn(f64, f64) -> f64) -> Option<Literal> {
match (lhs, rhs) {
(Literal::Integer(a), Literal::Integer(b)) => Some(Literal::Integer(int_op(*a, *b))),
(Literal::Double(a), Literal::Double(b)) => Some(Literal::Double(double_op(*a, *b))),
(Literal::Integer(a), Literal::Double(b)) => {
Some(Literal::Double(double_op(*a as f64, *b)))
}
(Literal::Double(a), Literal::Integer(b)) => {
Some(Literal::Double(double_op(*a, *b as f64)))
}
(Literal::Number(a), Literal::Number(b)) => Some(Literal::Number(op(*a, *b))),
_ => None,
}
}
fn eval_pos(val: &Eval) -> Result<Eval, String> {
match val {
Eval::Literal(Literal::Integer(it)) => Ok(Eval::Literal(Literal::Integer(*it))),
Eval::Literal(Literal::Double(it)) => Ok(Eval::Literal(Literal::Double(*it))),
Eval::Literal(Literal::Number(it)) => Ok(Eval::Literal(Literal::Number(*it))),
_ => Err("Evaluation error: expected numeric type for POS function.".to_string()),
}
}
fn eval_neg(val: &Eval) -> Result<Eval, String> {
match val {
Eval::Literal(Literal::Integer(it)) => Ok(Eval::Literal(Literal::Integer(-it))),
Eval::Literal(Literal::Double(it)) => Ok(Eval::Literal(Literal::Double(-it))),
Eval::Literal(Literal::Number(it)) => Ok(Eval::Literal(Literal::Number(-it))),
_ => Err("Evaluation error: expected numeric type for NEG function.".to_string()),
}
}

72
backend/src/grid.rs Normal file
View File

@@ -0,0 +1,72 @@
use std::collections::{HashMap, HashSet};
use crate::{
cell::{Cell, CellRef},
evaluator::{Eval, evaluate},
tokenizer::Literal,
};
pub struct Grid {
cells: HashMap<CellRef, Cell>,
}
impl Grid {
pub fn new() -> Grid {
return Grid {
cells: HashMap::new(),
};
}
}
impl Grid {
pub fn set_cell(&mut self, cell_ref: CellRef, raw_val: String) -> Result<Eval, String> {
if self.cells.contains_key(&cell_ref) && self.cells[&cell_ref].raw() == raw_val {
return self.get_cell(cell_ref);
}
let eval: Eval;
let deps: HashSet<CellRef>;
if let Some(c) = raw_val.chars().nth(0)
&& c == '='
{
(eval, deps) = evaluate(raw_val[1..].to_owned(), Some(&self))?;
// for dep in deps {}
} else {
match evaluate(raw_val.to_owned(), Some(&self)) {
Ok(e) => {
(eval, deps) = e;
}
Err(_) => eval = Eval::Literal(Literal::String(raw_val.to_owned())),
}
}
self.cells
.insert(cell_ref, Cell::new(eval.clone(), raw_val));
Ok(eval)
}
// pub fn get_cell(&mut self, cell_ref: CellRef) -> Result<(String, Eval), String> {
pub fn get_cell(&self, cell_ref: CellRef) -> Result<Eval, String> {
if !self.cells.contains_key(&cell_ref) {
return Err(format!("Cell at {:?} not found.", cell_ref));
}
let cell = &self.cells[&cell_ref];
// Ok((cell.raw(), cell.eval()))
Ok(cell.eval())
}
pub fn add_cell_dep(&mut self, cell_ref: CellRef, dep_ref: CellRef) -> Result<(), String> {
if !self.cells.contains_key(&cell_ref) {
return Err(format!("Cell at {:?} not found.", cell_ref));
}
if let Some(cell) = self.cells.get_mut(&cell_ref) {
cell.add_i_dep(dep_ref);
}
Ok(())
}
}

View File

@@ -1,28 +1,24 @@
mod cell;
mod evaluator;
mod grid;
mod messages;
mod parser;
mod tokenizer;
use futures_util::{SinkExt, StreamExt, TryStreamExt, future};
use futures_util::{SinkExt, StreamExt, TryStreamExt};
use log::info;
use std::{env, io::Error};
use tokio::net::{TcpListener, TcpStream};
use crate::{cell::CellRef, evaluator::Evaluator};
use crate::{
evaluator::Eval,
grid::Grid,
messages::{LeadMsg, MsgType},
};
#[tokio::main]
async fn main() -> Result<(), Error> {
env_logger::init();
// let mut input = String::new();
// io::stdin().read_line(&mut input).expect("Expected input.");
// let mut ast = parser::parse(&input).unwrap();
// println!("{}", ast.pretty());
let mut evaluator = Evaluator::new();
// // println!("{}", evaluator.evaluate(input).unwrap());
// let a1 = CellRef { row: 1, col: 2 };
// evaluator.set_cell(a1, input).unwrap();
// println!("{:?}", evaluator.get_cell(a1).unwrap());
let addr = env::args()
.nth(1)
@@ -38,78 +34,8 @@ async fn main() -> Result<(), Error> {
}
Ok(())
// println!("CMDS : set <cell_ref>, get <cell_ref>");
// loop {
// let mut input = String::new();
// io::stdin().read_line(&mut input).expect("Expected input.");
//
// let cmds = ["set", "get"];
// let cmd = &input[0..3];
// if !cmds.iter().any(|c| c == &cmd) {
// println!("{} is an invalid command!", cmd);
// println!("CMDS : set <cell_ref>, get <cell_ref>");
// continue;
// }
//
// let rest = &input[4..];
// let mut parts = rest.splitn(2, char::is_whitespace);
//
// let raw_ref = parts.next().unwrap_or("").trim(); // cell reference
// let raw_str = parts.next().unwrap_or("").trim(); // rest of the string (value)
// // println!("{} {}", raw_ref, raw_str);
//
// if let Ok(cell_ref) = CellRef::new(raw_ref.to_owned()) {
// match cmd {
// "set" => match evaluator.set_cell(cell_ref, raw_str.to_owned()) {
// Ok(_) => println!("Successfully set cell {} to {}.", raw_ref, raw_str),
// Err(e) => println!("{}", e),
// },
// "get" => match evaluator.get_cell(cell_ref) {
// Ok(res) => println!("{:?}", res),
// Err(e) => println!("{}", e),
// },
// _ => {
// panic!("Impossible.");
// }
// }
// } else {
// println!("{} is an invalid cell reference!", raw_ref);
// continue;
// }
// }
}
// async fn accept_connection(stream: TcpStream) {
// let addr = stream
// .peer_addr()
// .expect("connected streams should have a peer address");
// info!("Peer address: {}", addr);
//
// let ws_stream = tokio_tungstenite::accept_async(stream)
// .await
// .expect("Error during the websocket handshake occurred");
//
// info!("New WebSocket connection: {}", addr);
//
// let (mut write, mut read) = ws_stream.split();
//
// // We should not forward messages other than text or binary.
// while let Some(msg) = read.try_next().await.unwrap_or(None) {
// if msg.is_text() || msg.is_binary() {
// if let Err(e) = write
// .send(format!("This is a message {}!", msg.to_text().unwrap_or("")).into())
// .await
// {
// eprintln!("send error: {}", e);
// break;
// }
// }
// }
//
// info!("Disconnected from {}", addr);
// }
async fn accept_connection(stream: TcpStream) {
let addr = stream
.peer_addr()
@@ -125,56 +51,51 @@ async fn accept_connection(stream: TcpStream) {
let (mut write, mut read) = ws_stream.split();
// Each connection gets its own evaluator
let mut evaluator = Evaluator::new();
let mut grid = Grid::new();
while let Some(msg) = read.try_next().await.unwrap_or(None) {
if msg.is_text() {
let input = msg.to_text().unwrap_or("").trim().to_string();
let input = msg.to_text().unwrap();
let cmds = ["set", "get"];
let cmd = &input[0..3.min(input.len())]; // avoid panic on short strings
if let Ok(req) = serde_json::from_str::<LeadMsg>(&input) {
match req.msg_type {
MsgType::Set => {
let Some(cell_ref) = req.cell else { continue };
let Some(raw) = req.raw else { continue };
if !cmds.iter().any(|c| c == &cmd) {
let _ = write
.send(format!("ERR invalid command: {}", input).into())
.await;
continue;
}
let rest = input[4..].trim();
let mut parts = rest.splitn(2, char::is_whitespace);
let raw_ref = parts.next().unwrap_or("").trim(); // cell reference
let raw_str = parts.next().unwrap_or("").trim(); // rest (value)
if let Ok(cell_ref) = CellRef::new(raw_ref.to_owned()) {
match cmd {
"set" => match evaluator.set_cell(cell_ref.clone(), raw_str.to_owned()) {
Ok(eval) => {
let _ = write.send(format!("{} {}", raw_ref, eval).into()).await;
match grid.set_cell(cell_ref.clone(), raw.to_owned()) {
Ok(eval) => match eval {
Eval::Literal(lit) => {
let res = LeadMsg {
msg_type: MsgType::Set,
cell: Some(cell_ref),
raw: Some(raw.to_string()),
eval: Some(lit),
};
let _ = write
.send(serde_json::to_string(&res).unwrap().into())
.await;
}
},
Err(e) => {
let res = LeadMsg {
msg_type: MsgType::Error,
cell: Some(cell_ref),
raw: Some(e.to_string()),
eval: None,
};
let _ = write
.send(serde_json::to_string(&res).unwrap().into())
.await;
}
}
Err(e) => {
let _ = write.send(format!("ERR {}", e).into()).await;
}
},
"get" => match evaluator.get_cell(cell_ref.clone()) {
Ok(res) => {
let _ = write
.send(format!("{} {}", raw_ref, res.to_string()).into())
.await;
}
Err(e) => {
let _ = write.send(format!("ERR {}", e).into()).await;
}
},
}
_ => {
let _ = write.send("ERR impossible".into()).await;
continue; // handle other cases
}
}
} else {
let _ = write
.send(format!("ERR invalid cell reference: {}", raw_ref).into())
.await;
continue;
}
}
}

19
backend/src/messages.rs Normal file
View File

@@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
use crate::{cell::CellRef, tokenizer::Literal};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MsgType {
Set,
Get,
Error,
}
#[derive(Serialize, Deserialize)]
pub struct LeadMsg {
pub msg_type: MsgType,
pub cell: Option<CellRef>,
pub raw: Option<String>,
pub eval: Option<Literal>,
}

View File

@@ -169,11 +169,9 @@ pub fn _parse(
) -> Result<Expr, String> {
let mut lhs = match input.next() {
Token::Literal(it) => Expr::Literal(it),
Token::Identifier(id) if id == "true" => Expr::Literal(Literal::Boolean(true)),
Token::Identifier(id) if id == "false" => Expr::Literal(Literal::Boolean(false)),
Token::Paren('(') => {
Token::OpenParen => {
let lhs = _parse(input, 0, deps)?;
if input.next() != Token::Paren(')') {
if input.next() != Token::CloseParen {
return Err(format!("Parse error: expected closing paren."));
}
Expr::Group(Box::new(lhs))
@@ -194,14 +192,14 @@ pub fn _parse(
}
}
Token::Identifier(id) => match input.peek() {
Token::Paren('(') => {
Token::OpenParen => {
input.next();
let mut args: Vec<Expr> = Vec::new();
loop {
let nxt = input.peek();
if nxt == Token::Paren(')') {
if nxt == Token::CloseParen {
input.next();
break;
} else if nxt != Token::Comma && args.len() != 0 {
@@ -286,6 +284,5 @@ pub fn _parse(
}
}
Ok(lhs)
}

View File

@@ -1,7 +1,9 @@
#[derive(Debug, Clone, PartialEq)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum Literal {
Integer(i64),
Double(f64),
Number(f64),
Boolean(bool),
String(String),
}
@@ -11,7 +13,8 @@ pub enum Token {
Identifier(String), // Could be a function
Literal(Literal),
Operator(char),
Paren(char),
OpenParen,
CloseParen,
Comma,
Eof,
}
@@ -39,29 +42,38 @@ impl Tokenizer {
break;
}
}
tokens.push(Token::Identifier(ident));
let res = match ident.as_str() {
"true" => Token::Literal(Literal::Boolean(true)),
"false" => Token::Literal(Literal::Boolean(false)),
it => Token::Identifier(it.into()),
};
tokens.push(res);
} else if c.is_ascii_digit() {
// parse number
let mut number = String::new();
let mut is_decimal = false;
let mut is_exp = false;
while let Some(&ch) = chars.peek() {
if ch.is_ascii_digit() {
number.push(ch);
chars.next();
} else if ch == '.' && !is_decimal {
} else if ch == '.' && !is_decimal && !is_exp {
is_decimal = true;
number.push(ch);
chars.next();
} else if ch == 'e' && !is_decimal && !is_exp {
is_exp = true;
number.push(ch);
chars.next();
} else {
break;
}
}
if is_decimal {
tokens.push(Token::Literal(Literal::Double(number.parse().unwrap())))
} else {
tokens.push(Token::Literal(Literal::Integer(number.parse().unwrap())))
};
// TODO: REMOVE UNWRAP
tokens.push(Token::Literal(Literal::Number(number.parse().unwrap())));
} else if c == '"' || c == '\'' {
// parse string literal
let mut string = String::new();
@@ -89,7 +101,11 @@ impl Tokenizer {
tokens.push(Token::Operator(c));
chars.next();
} else if "()".contains(c) {
tokens.push(Token::Paren(c));
if c == '(' {
tokens.push(Token::OpenParen);
} else {
tokens.push(Token::CloseParen);
}
chars.next();
} else if c == ',' {
tokens.push(Token::Comma);
@@ -116,20 +132,140 @@ mod tests {
use super::*;
#[test]
fn test_tokenizer() {
let raw = "hello hello 1.23 this 5 (1+2)";
let expected: Vec<Token> = vec![
Token::Identifier("hello".to_string()),
Token::Identifier("hello".to_string()),
Token::Literal(Literal::Double(1.23)),
Token::Identifier("this".to_string()),
Token::Literal(Literal::Integer(5)),
Token::Paren('('),
Token::Literal(Literal::Integer(1)),
fn test_single_token() {
assert_eq!(
Tokenizer::new("1").unwrap().tokens,
Vec::from([Token::Literal(Literal::Number(1.0))])
);
assert_eq!(
Tokenizer::new("2.0").unwrap().tokens,
Vec::from([Token::Literal(Literal::Number(2.0))])
);
assert_eq!(
Tokenizer::new("\"hello\"").unwrap().tokens,
Vec::from([Token::Literal(Literal::String("hello".into()))])
);
assert_eq!(
Tokenizer::new("\'hello\'").unwrap().tokens,
Vec::from([Token::Literal(Literal::String("hello".into()))])
);
assert_eq!(
Tokenizer::new("hello").unwrap().tokens,
Vec::from([Token::Identifier("hello".into())])
);
assert_eq!(
Tokenizer::new("+").unwrap().tokens,
Vec::from([Token::Operator('+')])
);
assert_eq!(
Tokenizer::new(",").unwrap().tokens,
Vec::from([Token::Comma])
);
assert_eq!(
Tokenizer::new(")").unwrap().tokens,
Vec::from([Token::CloseParen])
);
assert_eq!(
Tokenizer::new("(").unwrap().tokens,
Vec::from([Token::OpenParen])
);
}
#[test]
fn test_token_punctuation() {
let mut exp = Vec::from([
Token::Comma,
Token::CloseParen,
Token::Comma,
Token::OpenParen,
]);
exp.reverse();
assert_eq!(Tokenizer::new(", ) , (").unwrap().tokens, exp);
}
#[test]
fn test_token_operators() {
let mut exp = Vec::from([
Token::Operator('+'),
Token::Literal(Literal::Integer(2)),
Token::Paren(')'),
Token::Operator('-'),
Token::Operator('*'),
Token::Operator('/'),
Token::Operator('^'),
Token::Operator('!'),
Token::Operator('%'),
Token::Operator('&'),
Token::Operator('|'),
]);
exp.reverse();
assert_eq!(Tokenizer::new("+-*/^!%&|").unwrap().tokens, exp);
}
#[test]
fn test_token_string() {
let raw = "\"hello\" \'world\'";
let mut expected: Vec<Token> = vec![
Token::Literal(Literal::String("hello".into())),
Token::Literal(Literal::String("world".into())),
];
expected.reverse();
let t = Tokenizer::new(&raw).unwrap();
assert_eq!(t.tokens, expected);
}
#[test]
fn test_token_number() {
let raw = "123 4.56";
let mut expected: Vec<Token> = vec![
Token::Literal(Literal::Number(123.0)),
Token::Literal(Literal::Number(4.56)),
];
expected.reverse();
let t = Tokenizer::new(&raw).unwrap();
assert_eq!(t.tokens, expected);
}
#[test]
fn test_token_boolean() {
let raw = "false true";
let mut expected: Vec<Token> = vec![
Token::Literal(Literal::Boolean(false)),
Token::Literal(Literal::Boolean(true)),
];
expected.reverse();
let t = Tokenizer::new(&raw).unwrap();
assert_eq!(t.tokens, expected);
}
#[test]
fn test_token_identifier() {
let raw = "hello test";
let mut expected: Vec<Token> = vec![
Token::Identifier("hello".to_string()),
Token::Identifier("test".to_string()),
];
expected.reverse();
let t = Tokenizer::new(&raw).unwrap();
assert_eq!(t.tokens, expected);
}
#[test]
fn test_token_mix() {
let raw = "hello test 1.23 this 5 (1+2)";
let mut expected: Vec<Token> = vec![
Token::Identifier("hello".to_string()),
Token::Identifier("test".to_string()),
Token::Literal(Literal::Number(1.23)),
Token::Identifier("this".to_string()),
Token::Literal(Literal::Number(5.0)),
Token::OpenParen,
Token::Literal(Literal::Number(1.0)),
Token::Operator('+'),
Token::Literal(Literal::Number(2.0)),
Token::CloseParen,
];
expected.reverse();
let t = Tokenizer::new(&raw).unwrap();
assert_eq!(t.tokens, expected);
}