Commit afba9c5d authored by Michael Aaron Murphy's avatar Michael Aaron Murphy

Merge branch 'master' into 'master'

[feature]: remember last result as `ans`

See merge request redox-os/calc!24
parents 48d84749 d8b68f8f
Pipeline #6247 failed with stage
in 3 minutes and 27 seconds
......@@ -19,6 +19,7 @@ name = "calculate"
readme = "README.md"
repository = "https://gitlab.redox-os.org/redox-os/calc"
version = "0.7.0"
edition = "2018"
[[bin]]
name = "calc"
......
extern crate test;
use super::eval;
use test::Bencher;
......
extern crate atty;
extern crate calc;
extern crate clap;
extern crate liner;
use std::fmt;
use std::process::exit;
use std::io::{self, stdout, BufRead, Write};
use calc::{eval, eval_polish, CalcError};
use calc::{eval, eval_polish, eval_polish_with_env, eval_with_env, CalcError};
use clap::{App, Arg};
......@@ -71,10 +66,17 @@ pub fn calc() -> Result<(), RuntimeError> {
macro_rules! eval {
($expr:expr) => {
if polish {
eval_polish($expr)?
} else {
eval($expr)?
match polish {
true => eval_polish($expr)?,
false => eval($expr)?,
}
};
}
macro_rules! eval_with_env {
($expr:expr, $env:expr) => {
match polish {
true => eval_polish_with_env($expr, $env)?,
false => eval_with_env($expr, $env)?,
}
};
}
......@@ -90,18 +92,25 @@ pub fn calc() -> Result<(), RuntimeError> {
None => {
if atty::is(atty::Stream::Stdin) {
let mut con = Context::new();
let mut ans = None;
loop {
let line = con.read_line(PROMPT, &mut |_| {})?;
match line.trim() {
"" => (),
"exit" => break,
s => writeln!(stdout, "{}", eval!(s))?,
s => {
let mut env =
calc::parse::DefaultEnvironment::with_ans(ans);
let evaluated = eval_with_env!(s, &mut env);
writeln!(stdout, "{}", evaluated)?;
ans = Some(evaluated);
}
}
con.history.push(line.into())?;
}
} else {
let stdin = io::stdin();
let mut lock = stdin.lock();
let lock = stdin.lock();
for line in lock.lines() {
writeln!(stdout, "{}", eval!(&line?))?;
}
......
......@@ -75,6 +75,7 @@ pub enum CalcError {
WouldTruncate(PartialComp),
RecursionLimitReached,
ImpossibleDice,
MissingAns,
}
use CalcError::*;
......@@ -109,6 +110,7 @@ impl fmt::Display for CalcError {
UnmatchedParenthesis => write!(f, "unmatched patenthesis"),
RecursionLimitReached => write!(f, "recursion limit reached"),
ImpossibleDice => write!(f, "impossible dice"),
MissingAns => write!(f, "no `ans` from a previous computation"),
}
}
}
......
#![cfg_attr(test, feature(test))]
#[macro_use]
extern crate decimal;
#[macro_use]
extern crate failure;
extern crate num;
extern crate rand;
#[cfg(test)]
extern crate test;
#[cfg(test)]
mod bench;
......@@ -85,6 +76,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use decimal::d128;
#[test]
fn basics() {
......@@ -164,5 +156,4 @@ mod tests {
assert_eq!(eval(input), expected);
}
}
}
use error::CalcError;
use crate::error::CalcError;
use crate::token::*;
use crate::value::{Value, IR};
use decimal::d128;
use rand::Rng;
use token::*;
use value::{Value, IR};
const RECURSION_LIMIT: usize = 10;
......@@ -25,6 +26,8 @@ pub trait Environment {
fn add_recursion_level(&mut self);
fn subtract_recursion_level(&mut self);
fn get_recursion_level(&self) -> usize;
fn ans(&self) -> &Option<Value>;
}
fn d_expr<E>(token_list: &[Token], env: &mut E) -> Result<IR, CalcError>
......@@ -209,6 +212,10 @@ where
{
if !token_list.is_empty() {
match token_list[0] {
Token::Ans => match env.ans() {
Some(v) => Ok(IR::new(v.clone(), 1)),
None => Err(CalcError::MissingAns),
},
Token::Number(ref n) => Ok(IR::new(n.clone(), 1)),
Token::Atom(ref s) => {
if let Some(nargs) = env.arity(s) {
......@@ -274,11 +281,22 @@ where
pub struct DefaultEnvironment {
recursion_level: usize,
ans: Option<Value>,
}
impl DefaultEnvironment {
pub fn new() -> DefaultEnvironment {
DefaultEnvironment { recursion_level: 0 }
DefaultEnvironment {
recursion_level: 0,
ans: None,
}
}
pub fn with_ans(ans: Option<Value>) -> DefaultEnvironment {
DefaultEnvironment {
recursion_level: 0,
ans,
}
}
}
......@@ -321,6 +339,10 @@ impl Environment for DefaultEnvironment {
fn subtract_recursion_level(&mut self) {
self.recursion_level -= 1;
}
fn ans(&self) -> &Option<Value> {
&self.ans
}
}
pub fn parse<E>(tokens: &[Token], env: &mut E) -> Result<Value, CalcError>
......@@ -361,6 +383,14 @@ mod tests {
assert!(out_float >= d128!(3.0) && out_float <= d128!(18.0));
}
#[test]
fn ans_calculation() {
let expr = [Token::Ans, Token::Multiply, Token::Number(Value::dec(3))];
let expected = Value::dec(12);
let mut env = DefaultEnvironment::with_ans(Some(Value::dec(4)));
assert_eq!(super::parse(&expr, &mut env), Ok(expected));
}
#[test]
fn function_binding() {
let expr = [
......@@ -374,5 +404,4 @@ mod tests {
let mut env = DefaultEnvironment::new();
assert_eq!(super::parse(&expr, &mut env), Ok(expected));
}
}
use crate::error::CalcError;
use crate::error::CalcError::*;
use crate::value::{Integral, Value};
use decimal::d128;
use error::CalcError;
use error::CalcError::*;
use num::Num;
use std::fmt;
use std::iter::Peekable;
use value::{Integral, Value};
/// Tokens used for parsing an arithmetic expression
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
Ans,
Plus,
Minus,
Divide,
......@@ -33,6 +34,7 @@ pub enum Token {
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Token::Ans => write!(f, "Ans"),
Token::Plus => write!(f, "Plus"),
Token::Minus => write!(f, "Minus"),
Token::Divide => write!(f, "Divide"),
......@@ -69,8 +71,8 @@ trait IsOperator {
impl IsOperator for char {
fn is_operator(self) -> bool {
match self {
'+' | '-' | '/' | '^' | '²' | '³' | '&' | '|' | '~' | '>'
| '%' | '(' | ')' | '*' | '<' | 'd' => true,
'+' | '-' | '/' | '^' | '²' | '³' | '&' | '|' | '~' | '>' | '%'
| '(' | ')' | '*' | '<' | 'd' => true,
_ => false,
}
}
......@@ -83,8 +85,8 @@ trait CheckOperator {
impl CheckOperator for char {
fn check_operator(self) -> OperatorState {
match self {
'+' | '-' | '/' | '^' | '²' | '³' | '&' | '|' | '~' | '%'
| '(' | ')' | 'd' => OperatorState::Complete,
'+' | '-' | '/' | '^' | '²' | '³' | '&' | '|' | '~' | '%' | '('
| ')' | 'd' => OperatorState::Complete,
'*' | '<' | '>' => OperatorState::PotentiallyIncomplete,
_ => OperatorState::NotAnOperator,
}
......@@ -171,7 +173,7 @@ pub fn tokenize(input: &str) -> Result<Vec<Token>, CalcError> {
if c.is_whitespace() {
chars.next();
} else if c.is_alphabetic() {
tokens.push(Token::Atom(consume_atom(&mut chars)));
tokens.push(consume_ans_or_atom(&mut chars).into());
} else if c.is_digit(16) || c == '.' {
tokens.push(Token::Number(consume_number(&mut chars)?));
} else {
......@@ -201,17 +203,29 @@ pub fn tokenize_polish(input: &str) -> Result<Vec<Token>, CalcError> {
// tokens for the infix format.
#[derive(Debug)]
enum PolishValue {
Ans,
Atom(String),
Number(Value),
}
impl From<PolishValue> for Token {
fn from(polish: PolishValue) -> Token {
match polish {
PolishValue::Ans => Token::Ans,
PolishValue::Atom(atom) => Token::Atom(atom),
PolishValue::Number(val) => Token::Number(val),
}
}
}
impl From<Token> for PolishValue {
fn from(token: Token) -> PolishValue {
match token {
Token::Ans => PolishValue::Ans,
Token::Atom(atom) => PolishValue::Atom(atom),
Token::Number(val) => PolishValue::Number(val),
_ => unreachable!(),
}
}
}
let mut chars = input.chars().peekable();
let mut operators: Vec<Token> = Vec::with_capacity(input.len() / 4);
......@@ -261,8 +275,7 @@ pub fn tokenize_polish(input: &str) -> Result<Vec<Token>, CalcError> {
if c.is_whitespace() {
chars.next();
} else if c.is_alphabetic() {
values
.push(PolishValue::Atom(consume_atom(&mut chars)));
values.push(consume_ans_or_atom(&mut chars).into());
break;
} else if c.is_digit(16) || c == '.' {
values.push(PolishValue::Number(consume_number(
......@@ -281,7 +294,7 @@ pub fn tokenize_polish(input: &str) -> Result<Vec<Token>, CalcError> {
// operators are found.
while let Some(&c) = chars.peek() {
if c.is_alphabetic() {
values.push(PolishValue::Atom(consume_atom(&mut chars)));
values.push(consume_ans_or_atom(&mut chars).into());
} else if c.is_digit(16) || c == '.' {
values.push(PolishValue::Number(consume_number(&mut chars)?));
} else if c.is_whitespace() || c == ')' {
......@@ -373,6 +386,18 @@ pub fn tokenize_polish(input: &str) -> Result<Vec<Token>, CalcError> {
Ok(tokens)
}
fn consume_ans_or_atom<I>(input: &mut Peekable<I>) -> Token
where
I: Iterator<Item = char>,
{
let atom = consume_atom(input);
if atom.eq_ignore_ascii_case("ans") {
Token::Ans
} else {
Token::Atom(atom)
}
}
fn digits<I>(input: &mut Peekable<I>, radix: u32) -> String
where
I: Iterator<Item = char>,
......@@ -512,6 +537,29 @@ mod tests {
assert_eq!(tokenize_polish(line), Ok(expected));
}
#[test]
fn ans() {
let line = "ans*3";
let expected =
vec![Token::Ans, Token::Multiply, Token::Number(Value::dec(3))];
assert_eq!(tokenize(line), Ok(expected));
}
#[test]
fn ans_polish() {
let line = "* ans 3";
let expected =
vec![Token::Ans, Token::Multiply, Token::Number(Value::dec(3))];
assert_eq!(tokenize_polish(line), Ok(expected));
}
#[test]
fn ans_subtract_ans_polish() {
let line = "- ans ans";
let expected = vec![Token::Ans, Token::Minus, Token::Ans];
assert_eq!(tokenize_polish(line), Ok(expected));
}
#[test]
fn function_chaining() {
let line = "log 4 / log 2";
......@@ -535,5 +583,4 @@ mod tests {
];
assert_eq!(tokenize(line), Ok(expected));
}
}
use crate::error::{CalcError, PartialComp};
use decimal::d128;
use error::{CalcError, PartialComp};
use num::{BigInt, BigUint, ToPrimitive, Zero};
use std::fmt;
use std::ops::*;
......@@ -127,7 +127,6 @@ pub mod ops {
PartialComp::ToFloat(n.to_string()),
))
}
}
impl Value {
......@@ -446,5 +445,4 @@ mod tests {
assert_eq!(output, expected);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment