Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
redox-os
calc
Commits
afba9c5d
Commit
afba9c5d
authored
Jul 31, 2019
by
Michael Aaron Murphy
Browse files
Merge branch 'master' into 'master'
[feature]: remember last result as `ans` See merge request
!24
parents
48d84749
d8b68f8f
Pipeline
#5524
passed with stage
in 3 minutes and 44 seconds
Changes
8
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Cargo.toml
View file @
afba9c5d
...
...
@@ -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"
...
...
src/bench.rs
View file @
afba9c5d
extern
crate
test
;
use
super
::
eval
;
use
test
::
Bencher
;
...
...
src/bin.rs
View file @
afba9c5d
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
?
))
?
;
}
...
...
src/error.rs
View file @
afba9c5d
...
...
@@ -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"
),
}
}
}
...
...
src/lib.rs
View file @
afba9c5d
#![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
);
}
}
}
src/parse.rs
View file @
afba9c5d
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
));
}
}
src/token.rs
View file @
afba9c5d
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
));
}
}
src/value.rs
View file @
afba9c5d
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
);
}
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment