From 683e2504b9031c3c4524e8f640fcd2bb4fd95835 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 31 Aug 2018 19:10:54 -0700 Subject: [PATCH 01/56] WIP --- types/src/lib.rs | 3 + types/src/number/integer.rs | 54 ++++++------ types/src/number/mod.rs | 166 ++++++++++++++++++------------------ types/src/object.rs | 10 +++ 4 files changed, 124 insertions(+), 109 deletions(-) diff --git a/types/src/lib.rs b/types/src/lib.rs index 95037dc..7632128 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -1,4 +1,5 @@ mod bool; +mod number; mod object; mod pair; mod sym; @@ -7,3 +8,5 @@ pub use bool::Bool; pub use object::Obj; pub use pair::Pair; pub use sym::Sym; + +pub use self::number::Int; diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index cad3893..a11024b 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -3,40 +3,40 @@ */ use std::any::Any; -use value::*; -use super::*; +use number::Number; +use object::{Obj, Object}; -#[derive(Debug, Eq, PartialEq)] -pub struct Integer(pub Int); - -impl Number for Integer { - fn convert_down(&self) -> Option> { None } - - fn is_exact(&self) -> bool { true } -} - -impl Value for Integer { - fn as_value(&self) -> &Value { self } -} - -impl IsBool for Integer { } -impl IsChar for Integer { } - -impl IsNumber for Integer { - fn is_integer(&self) -> bool { true } -} - -impl ValueEq for Integer { - fn eq(&self, other: &Value) -> bool { - other.as_any().downcast_ref::().map_or(false, |x| x == self) - } +pub type Int = i64; +impl Object for Int { fn as_any(&self) -> &Any { self } } +impl Number for Int { + fn as_int(&self) -> Option<&Int> { Some(self) } +} + +impl PartialEq for Int { + fn eq(&self, rhs: &Obj) -> bool { + match rhs.obj().and_then(Object::as_num) { + Some(num) => self == num, + None => false + } + } +} + +impl PartialEq for Int { + fn eq(&self, rhs: &Number) -> bool { + match rhs.as_int() { + Some(rhs) => *self == *rhs, + None => false + } + } +} + #[cfg(test)] mod tests { - use super::Integer; + use super::Int; use number::*; use value::*; diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index f79ad46..122214e 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -6,16 +6,18 @@ /// /// Scheme numbers are complex, literally. +mod integer; + use std::fmt; +use object::Object; -pub mod real; -mod add; -mod math; +pub use self::integer::Int; -pub use self::real::Real; - -type Int = i64; -type Flt = f64; +pub trait Number: + Object +{ + fn as_int(&self) -> Option<&Int> { None } +} #[derive(Debug, Eq, PartialEq)] pub enum Exact { Yes, No } @@ -30,79 +32,79 @@ impl fmt::Display for Exact { } // TODO: Implement PartialEq myself cause there are some weird nuances to comparing numbers. -#[derive(Debug, PartialEq)] -pub struct Number { - real: Real, - imag: Option, - exact: Exact, -} +//#[derive(Debug, PartialEq)] +//pub struct Number { +// real: Real, +// imag: Option, +// exact: Exact, +//} -impl Number { - fn new(real: Real, imag: Option, exact: Exact) -> Number { - Number { - real: real.reduce(), - imag: imag.map(|n| n.reduce()), - exact: exact, - } - } - - pub fn from_int(value: Int, exact: Exact) -> Number { - Number::new(Real::Integer(value), None, exact) - } - - pub fn from_quotient(p: Int, q: Int, exact: Exact) -> Number { - let real = if exact == Exact::Yes { - // Make an exact rational an integer if possible. - Real::Rational(p, q).demote() - } - else { - // Make an inexact rational an irrational. - Real::Rational(p, q).promote_once() - }; - Number::new(real, None, exact) - } - - pub fn from_float(value: Flt, exact: Exact) -> Number { - let real = if exact == Exact::Yes { - // Attempt to demote irrationals. - Real::Irrational(value).demote() - } - else { - Real::Irrational(value) - }; - Number::new(real, None, exact) - } - - pub fn is_exact(&self) -> bool { - match self.exact { - Exact::Yes => true, - Exact::No => false, - } - } -} - -impl fmt::Display for Number { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.real).and_then( - |r| self.imag.map(|i| write!(f, "{:+}i", i)).unwrap_or(Ok(r))) - } -} - -#[cfg(test)] -mod tests { - use super::Exact; - use super::Number; - use super::real::Real; - - #[test] - fn exact_numbers_are_exact() { - assert!(Number::from_int(3, Exact::Yes).is_exact()); - assert!(!Number::from_int(3, Exact::No).is_exact()); - } - - #[test] - fn exact_irrationals_are_reduced() { - let real = Real::Rational(3, 2); - assert_eq!(Number::from_float(1.5, Exact::Yes), Number::new(real, None, Exact::Yes)); - } -} +//impl Number { +// fn new(real: Real, imag: Option, exact: Exact) -> Number { +// Number { +// real: real.reduce(), +// imag: imag.map(|n| n.reduce()), +// exact: exact, +// } +// } +// +// pub fn from_int(value: Int, exact: Exact) -> Number { +// Number::new(Real::Integer(value), None, exact) +// } +// +// pub fn from_quotient(p: Int, q: Int, exact: Exact) -> Number { +// let real = if exact == Exact::Yes { +// // Make an exact rational an integer if possible. +// Real::Rational(p, q).demote() +// } +// else { +// // Make an inexact rational an irrational. +// Real::Rational(p, q).promote_once() +// }; +// Number::new(real, None, exact) +// } +// +// pub fn from_float(value: Flt, exact: Exact) -> Number { +// let real = if exact == Exact::Yes { +// // Attempt to demote irrationals. +// Real::Irrational(value).demote() +// } +// else { +// Real::Irrational(value) +// }; +// Number::new(real, None, exact) +// } +// +// pub fn is_exact(&self) -> bool { +// match self.exact { +// Exact::Yes => true, +// Exact::No => false, +// } +// } +//} +// +//impl fmt::Display for Number { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// write!(f, "{}", self.real).and_then( +// |r| self.imag.map(|i| write!(f, "{:+}i", i)).unwrap_or(Ok(r))) +// } +//} +// +//#[cfg(test)] +//mod tests { +// use super::Exact; +// use super::Number; +// use super::real::Real; +// +// #[test] +// fn exact_numbers_are_exact() { +// assert!(Number::from_int(3, Exact::Yes).is_exact()); +// assert!(!Number::from_int(3, Exact::No).is_exact()); +// } +// +// #[test] +// fn exact_irrationals_are_reduced() { +// let real = Real::Rational(3, 2); +// assert_eq!(Number::from_float(1.5, Exact::Yes), Number::new(real, None, Exact::Yes)); +// } +//} diff --git a/types/src/object.rs b/types/src/object.rs index 0b988ba..3eb2201 100644 --- a/types/src/object.rs +++ b/types/src/object.rs @@ -18,6 +18,7 @@ use std::mem; use std::any::Any; use std::fmt; use super::*; +use number::Number; #[derive(Debug)] pub enum Obj { @@ -38,6 +39,8 @@ pub trait Object: fn as_pair(&self) -> Option<&Pair> { None } /// Cast this Object to a Sym if possible. fn as_sym(&self) -> Option<&Sym> { None } + /// Cast this Object to a Number if possible. + fn as_num(&self) -> Option<&Number> { None } } impl Obj { @@ -45,6 +48,13 @@ impl Obj { Obj::Ptr(Box::new(obj)) } + pub fn obj<'a>(&'a self) -> Option<&'a Object> { + match self { + Obj::Ptr(obj) => Some(obj.deref()), + Obj::Null => None + } + } + pub fn take(&mut self) -> Obj { // Stole Option's implementation of this. Handy. :D mem::replace(self, Obj::Null) From f18b6a5719384f9f9a4d2828364773388d762f30 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 11:52:49 -0700 Subject: [PATCH 02/56] [types] Simplify explicit lifetimes for Int::eq(Obj) --- types/src/number/integer.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index ab725b7..639d70d 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -18,9 +18,7 @@ impl Number for Int { impl PartialEq for Int { fn eq<'a>(&self, rhs: &'a Obj) -> bool { - let obj: Option<&'a Object> = rhs.obj(); - let num: Option<&'a Number> = obj.and_then(Object::as_num); - match num { + match rhs.obj().and_then(Object::as_num) { Some(num) => self == num, None => false } From 9801113c01d3a7b44141fa0072a7ced5599e1760 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 11:58:25 -0700 Subject: [PATCH 03/56] [types] Move exactness flag to is_exact() method on Number --- types/src/number/mod.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index 122214e..a8277bd 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -8,7 +8,6 @@ mod integer; -use std::fmt; use object::Object; pub use self::integer::Int; @@ -16,19 +15,10 @@ pub use self::integer::Int; pub trait Number: Object { + /// Cast this Number to an Int if possible. fn as_int(&self) -> Option<&Int> { None } -} - -#[derive(Debug, Eq, PartialEq)] -pub enum Exact { Yes, No } - -impl fmt::Display for Exact { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", match *self { - Exact::Yes => "#e", - Exact::No => "#i", - }) - } + /// Return `true` if this Number is an exact representation of its value. + fn is_exact(&self) -> bool { true } } // TODO: Implement PartialEq myself cause there are some weird nuances to comparing numbers. From 3bdf14720ceffddc6940872be43e262d9176712f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 12:03:26 -0700 Subject: [PATCH 04/56] [types] Expand the documentation of Numbers --- types/src/number/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index a8277bd..65fac0a 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -2,9 +2,14 @@ * Eryn Wells */ -/// # Numbers -/// -/// Scheme numbers are complex, literally. +//! # Numbers +//! +//! Scheme numbers are complex, literally. The model it uses is a hierarchy of types called the +//! Number Tower. It consists of four types, in order: Integers, Rationals (or Fractionals), +//! Irrationals (or Reals), and Complex Numbers. Each type going down the tower can be +//! unequivocally cast to the type below it, but the reverse is not necessarily true. So, an +//! Integer can be cast as a Rational (by putting its value over 1), but a Rational like 1/2 cannot +//! be represented as an Integer. mod integer; From 68eec8578c2d6ce5ff8841467c48f0ff182ab73a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 12:21:13 -0700 Subject: [PATCH 05/56] [types] Passing integer tests and build fixes --- types/src/number/integer.rs | 34 +++++++++++++++++----------------- types/src/object.rs | 2 -- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index 639d70d..f842c6b 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -3,13 +3,22 @@ */ use std::any::Any; +use std::fmt; use number::Number; use object::{Obj, Object}; -pub type Int = i64; +#[derive(Debug, Eq, PartialEq)] +pub struct Int(i64); + +impl fmt::Display for Int { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} impl Object for Int { fn as_any(&self) -> &Any { self } + fn as_num(&self) -> Option<&Number> { Some(self) } } impl Number for Int { @@ -36,32 +45,23 @@ impl<'a> PartialEq for Int { #[cfg(test)] mod tests { - use super::Int; - use number::*; - use value::*; + use super::*; #[test] fn equal_integers_are_equal() { - assert_eq!(Integer(3), Integer(3)); - assert_ne!(Integer(12), Integer(9)); - assert_eq!(Integer(4).as_value(), Integer(4).as_value()); - assert_ne!(Integer(5).as_value(), Integer(7).as_value()); + assert_eq!(Int(3), Int(3)); + assert_ne!(Int(12), Int(9)); + assert_eq!(Obj::new(Int(3)), Obj::new(Int(3))); + assert_ne!(Obj::new(Int(3)), Obj::new(Int(4))); } #[test] fn integers_are_integers() { - assert!(Integer(4).is_complex()); - assert!(Integer(4).is_real()); - assert!(Integer(4).is_rational()); - assert!(Integer(4).is_integer()); - assert!(Integer(4).is_number()); - assert!(!Integer(6).is_char()); - assert!(!Integer(6).is_bool()); + assert_eq!(Int(4).as_bool(), None); } #[test] fn integers_are_exact() { - assert!(Integer(4).is_exact()); - assert!(!Integer(4).is_inexact()); + assert!(Int(4).is_exact()); } } diff --git a/types/src/object.rs b/types/src/object.rs index 584421a..3680ef7 100644 --- a/types/src/object.rs +++ b/types/src/object.rs @@ -188,8 +188,6 @@ impl PartialEq for Obj { #[cfg(test)] mod tests { - use super::Obj; - // #[test] // fn display_bools() { // assert_eq!(format!("{}", Object::Bool(true)), "#t"); From 0ed4aa3ae52961fcfffacf13981ebe989308ddd4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 20:00:09 -0700 Subject: [PATCH 06/56] [types] Implement Add and Mul on Int --- types/src/lib.rs | 1 + types/src/number/integer.rs | 47 +++++++++++++++++++++++++++++++++++-- types/src/number/mod.rs | 2 +- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/types/src/lib.rs b/types/src/lib.rs index 7632128..c077ee5 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -9,4 +9,5 @@ pub use object::Obj; pub use pair::Pair; pub use sym::Sym; +pub use self::number::Number; pub use self::number::Int; diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index f842c6b..169e2a7 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -4,11 +4,33 @@ use std::any::Any; use std::fmt; +use std::ops::{Add, Mul}; use number::Number; use object::{Obj, Object}; -#[derive(Debug, Eq, PartialEq)] -pub struct Int(i64); +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Int(pub i64); + +impl Add for Int { + type Output = Int; + fn add(self, rhs: Self) -> Self::Output { + Int(self.0 + rhs.0) + } +} + +impl<'a> Add for &'a Int { + type Output = Int; + fn add(self, rhs: Int) -> Self::Output { + Int(self.0 + rhs.0) + } +} + +impl<'a, 'b> Add<&'a Int> for &'b Int { + type Output = Int; + fn add(self, rhs: &Int) -> Self::Output { + Int(self.0 + rhs.0) + } +} impl fmt::Display for Int { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -25,6 +47,27 @@ impl Number for Int { fn as_int(&self) -> Option<&Int> { Some(self) } } +impl Mul for Int { + type Output = Int; + fn mul(self, rhs: Self) -> Self::Output { + Int(self.0 * rhs.0) + } +} + +impl<'a> Mul for &'a Int { + type Output = Int; + fn mul(self, rhs: Int) -> Self::Output { + Int(self.0 * rhs.0) + } +} + +impl<'a, 'b> Mul<&'a Int> for &'b Int { + type Output = Int; + fn mul(self, rhs: &Int) -> Self::Output { + Int(self.0 * rhs.0) + } +} + impl PartialEq for Int { fn eq<'a>(&self, rhs: &'a Obj) -> bool { match rhs.obj().and_then(Object::as_num) { diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index 65fac0a..e63aa46 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -18,7 +18,7 @@ use object::Object; pub use self::integer::Int; pub trait Number: - Object + Object { /// Cast this Number to an Int if possible. fn as_int(&self) -> Option<&Int> { None } From d69c3dbc3199685f8886101ff3a284ae36fbced4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 22:21:05 -0700 Subject: [PATCH 07/56] [lexer] Obey Rust book recommendation about order of leading lines --- lexer/src/lib.rs | 6 +++--- lexer/src/main.rs | 2 +- lexer/src/states/mod.rs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index 032d88e..50165c2 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -2,6 +2,9 @@ * Eryn Wells */ +use std::iter::Peekable; +use states::*; + mod chars; mod error; mod states; @@ -10,9 +13,6 @@ mod token; pub use error::Error; pub use token::{Lex, Token}; -use std::iter::Peekable; -use states::*; - pub type Result = std::result::Result; pub struct Lexer where T: Iterator { diff --git a/lexer/src/main.rs b/lexer/src/main.rs index 366ef92..2079c06 100644 --- a/lexer/src/main.rs +++ b/lexer/src/main.rs @@ -4,8 +4,8 @@ extern crate sibillexer; -use std::io::prelude::*; use std::io; +use std::io::Write; use sibillexer::Lexer; fn main() { diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index ac9004d..94595e2 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -2,15 +2,15 @@ * Eryn Wells */ +use std::fmt::Debug; +use token::Token; + mod begin; mod hash; mod id; pub use self::begin::Begin; -use std::fmt::Debug; -use token::Token; - #[derive(Debug)] pub enum StateResult { /// Consume the character, remain on this state. From 7b6259977feae5aa240f8f843214d8f5fa3597fa Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 22:21:21 -0700 Subject: [PATCH 08/56] [lexer] Add fail() constructor to StateResult --- lexer/src/states/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index 94595e2..f661fd0 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -35,3 +35,9 @@ pub trait State: Debug { fn lex(&mut self, c: char) -> StateResult; fn none(&mut self) -> Result, String>; } + +impl StateResult { + pub fn fail(msg: &str) -> StateResult { + StateResult::Fail { msg: msg.to_string() } + } +} \ No newline at end of file From aa4de7d4bd4c3c8f4a52e1dc7a9cf4bc93a6442c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 1 Sep 2018 22:25:40 -0700 Subject: [PATCH 09/56] [lexer] Laying groundwork for lexing numbers... --- lexer/src/states/mod.rs | 2 ++ lexer/src/states/number.rs | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 lexer/src/states/number.rs diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index f661fd0..a9c45a0 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -7,9 +7,11 @@ use token::Token; mod begin; mod hash; +mod number; mod id; pub use self::begin::Begin; +pub use self::number::BeginNumber; #[derive(Debug)] pub enum StateResult { diff --git a/lexer/src/states/number.rs b/lexer/src/states/number.rs new file mode 100644 index 0000000..2bf4c35 --- /dev/null +++ b/lexer/src/states/number.rs @@ -0,0 +1,45 @@ +/* lexer/src/states/number.rs + * Eryn Wells + */ + +use super::{State, StateResult, Token}; + +#[derive(Debug, Eq, PartialEq)] +pub enum Base { + Bin = 2, + Oct = 8, + Dec = 10, + Hex = 16 +} + +#[derive(Debug)] +pub struct Builder { + base: Base +} + +#[derive(Debug)] +pub struct BeginNumber(Builder); + +impl Builder { + pub fn new() -> Builder { + Builder { base: Base::Dec } + } +} + +impl BeginNumber { + pub fn new() -> BeginNumber { + BeginNumber(Builder::new()) + } +} + +impl State for BeginNumber { + fn lex(&mut self, c: char) -> StateResult { + // TODO: Implement. + StateResult::fail("blah") + } + + fn none(&mut self) -> Result, String> { + // TODO: Implement. + Err("blah".to_string()) + } +} \ No newline at end of file From f35fe5fd08e4e49e954c9b868d6e83adff83b016 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 2 Sep 2018 13:50:33 -0700 Subject: [PATCH 10/56] [lexer] Implement Hash::new() --- lexer/src/states/begin.rs | 2 +- lexer/src/states/hash.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index 432ba9a..cea8aa5 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -19,7 +19,7 @@ impl State for Begin { // TODO: Figure out some way to track newlines. c if c.is_whitespace() => StateResult::Continue, c if c.is_identifier_initial() => StateResult::Advance { to: Box::new(IdSub{}) }, - c if c.is_hash() => StateResult::Advance { to: Box::new(Hash{}) }, + c if c.is_hash() => StateResult::Advance { to: Box::new(Hash::new()) }, _ => { let msg = format!("Invalid character: {}", c); StateResult::Fail { msg } diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index 53eb0b3..2ff45c3 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -16,6 +16,10 @@ const FALSE: &'static str = "false"; #[derive(Debug)] pub struct Hash; #[derive(Debug)] pub struct BoolSub(String); +impl Hash { + pub fn new() { Hash{} } +} + impl State for Hash { fn lex(&mut self, c: char) -> StateResult { match c { From 7a6c2b91d1980a2897c4ec6977c5dc411601d2d5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 2 Sep 2018 14:05:35 -0700 Subject: [PATCH 11/56] [lexer] Bit of code cleanup in Lexer --- lexer/src/lib.rs | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index 50165c2..8741851 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -26,21 +26,26 @@ impl Lexer where T: Iterator { Lexer { input: input.peekable(), line: 0, - offset: 0 + offset: 0, + } + } + + fn next(&mut self) -> Option { + let out = self.input.next(); + out + } + + fn handle_whitespace(&mut self, c: char) { + if c == '\n' { + self.line += 1; + self.offset = 1; + } else { + self.offset += 1; } } } impl Lexer where T: Iterator { - fn handle_whitespace(&mut self, c: char) { - if c == '\n' { - self.line += 1; - self.offset = 0; - } - else { - self.offset += 1; - } - } } impl Iterator for Lexer where T: Iterator { @@ -67,17 +72,17 @@ impl Iterator for Lexer where T: Iterator { match result { StateResult::Continue => { buffer.push(c); - self.input.next(); + self.next(); }, StateResult::Advance { to } => { buffer.push(c); - self.input.next(); + self.next(); state = to; }, StateResult::Emit(token, resume) => { if resume == Resume::AtNext { buffer.push(c); - self.input.next(); + self.next(); } out = Some(Ok(Lex::new(token, &buffer, self.line, self.offset))); break; @@ -86,7 +91,7 @@ impl Iterator for Lexer where T: Iterator { panic!("{}", msg); } } - } + }, } } out From 757f943fff9f3ce3a8d2f8d76f22b454226643bf Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 2 Sep 2018 14:05:56 -0700 Subject: [PATCH 12/56] [lexer] Oops forgot a return type on Hash::new() --- lexer/src/states/hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index 2ff45c3..1823bb0 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -17,7 +17,7 @@ const FALSE: &'static str = "false"; #[derive(Debug)] pub struct BoolSub(String); impl Hash { - pub fn new() { Hash{} } + pub fn new() -> Hash { Hash{} } } impl State for Hash { From 04f2eb093703ea2cbbd5dfb2e92d78d047acf0ee Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 2 Sep 2018 17:23:32 -0700 Subject: [PATCH 13/56] [lexer] Remove empty Lexer impl --- lexer/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index 8741851..dd947a9 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -45,9 +45,6 @@ impl Lexer where T: Iterator { } } -impl Lexer where T: Iterator { -} - impl Iterator for Lexer where T: Iterator { type Item = Result; From 0f18569292342a91edc966205c277cda8a4879d7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 2 Sep 2018 17:23:48 -0700 Subject: [PATCH 14/56] [lexer] Add (failing) test for integers --- lexer/tests/single_tokens.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lexer/tests/single_tokens.rs b/lexer/tests/single_tokens.rs index 0bcf2b0..5c89c31 100644 --- a/lexer/tests/single_tokens.rs +++ b/lexer/tests/single_tokens.rs @@ -72,3 +72,11 @@ fn bool_with_spaces() { assert_eq!(lex.next(), Some(Ok(expected_lex))); assert_eq!(lex.next(), None); } + +#[test] +fn integer() { + let expected_lex = Lex::new(Token::Num(23), "23", 0, 0); + let mut lex = Lexer::new("23".chars()); + assert_eq!(lex.next(), Some(Ok(expected_lex))); + assert_eq!(lex.next(), None); +} From ebee8c8646c833967537558393e414235c6eb746 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 2 Sep 2018 17:24:07 -0700 Subject: [PATCH 15/56] [lexer] Add Num token type, which only takes ints for now... --- lexer/src/token.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lexer/src/token.rs b/lexer/src/token.rs index a97925b..c1008d0 100644 --- a/lexer/src/token.rs +++ b/lexer/src/token.rs @@ -13,6 +13,7 @@ pub struct Lex { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Token { Bool(bool), + Num(i64), LeftParen, RightParen, Id From 4341268d0d36555c11e8b7548ddf2675abcfb674 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 10:43:41 -0700 Subject: [PATCH 16/56] [lexer] DOT graph of lexer states: numbers, bools, and parens so far --- doc/Makefile | 2 ++ doc/lexer.dot | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 doc/Makefile create mode 100644 doc/lexer.dot diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..73a0d44 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,2 @@ +lexer.pdf: lexer.dot + dot -Tpdf -O$@ $^ diff --git a/doc/lexer.dot b/doc/lexer.dot new file mode 100644 index 0000000..71bebde --- /dev/null +++ b/doc/lexer.dot @@ -0,0 +1,74 @@ +digraph lexer { + rankdir=LR; + node [shape = doublecircle] LP RP B0 BF DP1 DD0 DD1 DD2 DR0 DR1 INF NAN EXD; + node [shape = circle]; + BEGIN -> LP [ label = "(" ]; + BEGIN -> RP [ label = ")" ]; + BEGIN -> H [ label = "#" ]; + + subgraph bools { + H -> B0 [ label = "t,f" ]; + B0 -> BF [ label = "rue,alse" ]; + } + +/* + subgraph chars { + H -> SL [ label = "\\" ]; + SL -> CH1 [ label = "*" ]; + SL -> NMCH [ label = "alarm" ]; + SL -> NMCH [ label = "backspace" ]; + SL -> NMCH [ label = "delete" ]; + SL -> NMCH [ label = "escape" ]; + SL -> NMCH [ label = "newline" ]; + SL -> NMCH [ label = "null" ]; + SL -> NMCH [ label = "return" ]; + SL -> NMCH [ label = "space" ]; + SL -> NMCH [ label = "tab" ]; + SL -> XC [ label = "x" ]; + } +*/ + + subgraph numbers { + BEGIN -> DD0 [ label = "0-9" ]; + BEGIN -> SN0 [ label = "+,-" ]; + BEGIN -> DP0 [ label = "." ]; + DD0 -> DD0 [ label = "0-9" ]; + DD0 -> DP1 [ label = "." ]; + DP1 -> DD1 [ label = "0-9" ]; + DP0 -> DD1 [ label = "0-9" ]; + DD1 -> DD1 [ label = "0-9" ]; + SN0 -> DD0 [ label = "0-9" ]; + SN0 -> DP0 [ label = "." ]; + SN0 -> INF [ label = "inf.0" ]; + SN0 -> NAN [ label = "nan.0" ]; + + H -> NEX [ label = "i,e" ]; + NEX -> DD0 [ label = "0-9" ]; + NEX -> SN0 [ label = "." ]; + + H -> NBD [ label = "d" ]; + NBD -> DD0 [ label = "0-9" ]; + NBD -> SN0 [ label = "." ]; + + H -> NBS [ label = "b,o,x" ]; + NBS -> DR0 [ label = "Dr" ]; + DR0 -> DR0 [ label = "Dr" ]; + + DD0 -> EXP [ label = "e" ]; + DP1 -> EXP [ label = "e" ]; + DD1 -> EXP [ label = "e" ]; + + EXP -> EXS [ label = "+,-" ]; + EXS -> EXD [ label = "0-9" ]; + EXP -> EXD [ label = "0-9" ]; + EXD -> EXD [ label = "0-9" ]; + + DR0 -> FR0 [ label = "/" ]; + FR0 -> DR1 [ label = "Dr" ]; + DR1 -> DR1 [ label = "Dr" ]; + + DD0 -> FR1 [ label = "/" ]; + FR1 -> DD2 [ label = "0-9" ]; + DD2 -> DD2 [ label = "0-9" ]; + } +} From 0b5fb57ba964495a7105297ed38c88484a20a4a2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 10:44:23 -0700 Subject: [PATCH 17/56] [lexer] Ignore lexer DOT .pdf output --- doc/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/.gitignore diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..5e99328 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +lexer*.pdf From fb77e7e203fabce774146a9fc6d1eb7fabe95e89 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 11:10:29 -0700 Subject: [PATCH 18/56] [lexer] Add fractionals and prefix fixes - Add states to handle fractionals. Easy. - Add states to properly handle the prefixes. You can have #i#x and #x#i. It should now reflect that... --- doc/lexer.dot | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/doc/lexer.dot b/doc/lexer.dot index 71bebde..f571e2e 100644 --- a/doc/lexer.dot +++ b/doc/lexer.dot @@ -44,15 +44,35 @@ digraph lexer { H -> NEX [ label = "i,e" ]; NEX -> DD0 [ label = "0-9" ]; - NEX -> SN0 [ label = "." ]; + NEX -> SN0 [ label = "+,-" ]; + NEX -> NXH1 [ label = "#" ]; + NXH1 -> NXD1 [ label = "d" ]; + NXD1 -> DD0 [ label = "0-9" ]; + NXD1 -> SN0 [ label = "+,-" ]; + NXH1 -> NXX1 [ label = "b,o,x" ]; + NXX1 -> SN1 [ label = "+,-" ]; + NXX1 -> DR0 [ label = "Dr" ]; H -> NBD [ label = "d" ]; NBD -> DD0 [ label = "0-9" ]; - NBD -> SN0 [ label = "." ]; + NBD -> SN0 [ label = "+,-" ]; + NBD -> NBH [ label = "#" ]; + NBH -> NBX [ label = "i,e" ]; + NBX -> SN0 [ label = "+,-" ]; + NBX -> DD0 [ label = "0-9" ]; H -> NBS [ label = "b,o,x" ]; NBS -> DR0 [ label = "Dr" ]; DR0 -> DR0 [ label = "Dr" ]; + NBS -> NXH [ label = "#" ]; + NXH -> NXX [ label = "i,e" ]; + NXX -> DR0 [ label = "Dr" ]; + + NBS -> SN1 [ label = "+,-" ]; + NXX -> SN1 [ label = "+,-" ]; + SN1 -> DR0 [ label = "Dr" ]; + SN1 -> INF [ label = "inf.0" ]; + SN1 -> NAN [ label = "nan.0" ]; DD0 -> EXP [ label = "e" ]; DP1 -> EXP [ label = "e" ]; From 92df0d9cfde899fb57116703e75f4c521863318e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 11:46:45 -0700 Subject: [PATCH 19/56] [lexer] Attempt to add complex numbers to the DOT graph -- this is dumb --- doc/lexer.dot | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/lexer.dot b/doc/lexer.dot index f571e2e..e15307a 100644 --- a/doc/lexer.dot +++ b/doc/lexer.dot @@ -1,6 +1,7 @@ digraph lexer { rankdir=LR; - node [shape = doublecircle] LP RP B0 BF DP1 DD0 DD1 DD2 DR0 DR1 INF NAN EXD; + node [shape = doublecircle] LP RP B0 BF DP1 DD0 DD1 DD2 DR0 DR1 INF NAN EXD IMG; + node [shape = octagon, ratio=1] NRAT DDAT; node [shape = circle]; BEGIN -> LP [ label = "(" ]; BEGIN -> RP [ label = ")" ]; @@ -30,7 +31,7 @@ digraph lexer { subgraph numbers { BEGIN -> DD0 [ label = "0-9" ]; - BEGIN -> SN0 [ label = "+,-" ]; + BEGIN -> SN2 [ label = "+,-" ]; BEGIN -> DP0 [ label = "." ]; DD0 -> DD0 [ label = "0-9" ]; DD0 -> DP1 [ label = "." ]; @@ -90,5 +91,21 @@ digraph lexer { DD0 -> FR1 [ label = "/" ]; FR1 -> DD2 [ label = "0-9" ]; DD2 -> DD2 [ label = "0-9" ]; + + SN2 -> IMG [ label = "i" ]; + INF -> IMG [ label = "i" ]; + NAN -> IMG [ label = "i" ]; + DD0 -> IMG [ label = "i" ]; + + DR0 -> NRAT [ label = "@" ]; + DR1 -> NRAT [ label = "" ]; + NRAT -> DR0 [ label = "Dr" ]; + + DD0 -> DDAT [ label = "@" ]; + DD2 -> DDAT [ label = "@" ]; + DP1 -> DDAT [ label = "@" ]; + DD1 -> DDAT [ label = "@" ]; + EXD -> DDAT [ label = "@" ]; + DDAT -> SN0 [ label = "+,-" ]; } } From e339b519decc241e6fc9d26486918cf728ce4eb3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 12:26:01 -0700 Subject: [PATCH 20/56] Abort trying to do complex numbers in the DOT graph for now --- doc/lexer.dot | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/doc/lexer.dot b/doc/lexer.dot index e15307a..f571e2e 100644 --- a/doc/lexer.dot +++ b/doc/lexer.dot @@ -1,7 +1,6 @@ digraph lexer { rankdir=LR; - node [shape = doublecircle] LP RP B0 BF DP1 DD0 DD1 DD2 DR0 DR1 INF NAN EXD IMG; - node [shape = octagon, ratio=1] NRAT DDAT; + node [shape = doublecircle] LP RP B0 BF DP1 DD0 DD1 DD2 DR0 DR1 INF NAN EXD; node [shape = circle]; BEGIN -> LP [ label = "(" ]; BEGIN -> RP [ label = ")" ]; @@ -31,7 +30,7 @@ digraph lexer { subgraph numbers { BEGIN -> DD0 [ label = "0-9" ]; - BEGIN -> SN2 [ label = "+,-" ]; + BEGIN -> SN0 [ label = "+,-" ]; BEGIN -> DP0 [ label = "." ]; DD0 -> DD0 [ label = "0-9" ]; DD0 -> DP1 [ label = "." ]; @@ -91,21 +90,5 @@ digraph lexer { DD0 -> FR1 [ label = "/" ]; FR1 -> DD2 [ label = "0-9" ]; DD2 -> DD2 [ label = "0-9" ]; - - SN2 -> IMG [ label = "i" ]; - INF -> IMG [ label = "i" ]; - NAN -> IMG [ label = "i" ]; - DD0 -> IMG [ label = "i" ]; - - DR0 -> NRAT [ label = "@" ]; - DR1 -> NRAT [ label = "" ]; - NRAT -> DR0 [ label = "Dr" ]; - - DD0 -> DDAT [ label = "@" ]; - DD2 -> DDAT [ label = "@" ]; - DP1 -> DDAT [ label = "@" ]; - DD1 -> DDAT [ label = "@" ]; - EXD -> DDAT [ label = "@" ]; - DDAT -> SN0 [ label = "+,-" ]; } } From 14ea07e4414f83e0cc48611ea2a386589027a1f7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 12:27:09 -0700 Subject: [PATCH 21/56] [lexer] Move Bool state to its own file --- lexer/src/states/bool.rs | 68 ++++++++++++++++++++++++++++++++ lexer/src/states/hash.rs | 83 +++++++--------------------------------- lexer/src/states/mod.rs | 13 +++++-- 3 files changed, 91 insertions(+), 73 deletions(-) create mode 100644 lexer/src/states/bool.rs diff --git a/lexer/src/states/bool.rs b/lexer/src/states/bool.rs new file mode 100644 index 0000000..20796f2 --- /dev/null +++ b/lexer/src/states/bool.rs @@ -0,0 +1,68 @@ +/* lexer/src/states/bool.rs + * Eryn Wells + */ + +use chars::Lexable; +use states::{Resume, State, StateResult}; +use token::Token; + +const TRUE_SHORT: &'static str = "t"; +const TRUE: &'static str = "true"; +const FALSE_SHORT: &'static str = "f"; +const FALSE: &'static str = "false"; + +#[derive(Debug)] pub struct Bool(String); + +impl Bool { + pub fn new(buf: &str) -> Bool { + Bool(buf.to_string()) + } + + fn handle_delimiter(&self) -> Option { + if self.0 == TRUE || self.0 == TRUE_SHORT { + Some(Token::Bool(true)) + } else if self.0 == FALSE || self.0 == FALSE_SHORT { + Some(Token::Bool(false)) + } else { + None + } + } +} + +impl State for Bool { + fn lex(&mut self, c: char) -> StateResult { + match c { + c if c.is_identifier_delimiter() => match self.handle_delimiter() { + Some(token) => StateResult::Emit(token, Resume::Here), + None => { + let msg = format!("Invalid character: {}", c); + StateResult::fail(msg.as_str()) + }, + }, + _ => { + let buf = { + let mut b = String::from(self.0.as_str()); + b.push(c); + b + }; + if TRUE.starts_with(&buf) || FALSE.starts_with(&buf) { + StateResult::advance(Box::new(Bool(buf))) + } else { + let msg = format!("Invalid character: {}", c); + StateResult::fail(msg.as_str()) + } + }, + } + } + + fn none(&mut self) -> Result, String> { + match self.handle_delimiter() { + Some(token) => Ok(Some(token)), + None => { + let msg = format!("Found EOF while trying to parse a bool"); + Err(msg) + } + } + } +} + diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index 1823bb0..14ef37b 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -2,19 +2,16 @@ * Eryn Wells */ -//! Lexer states for handling tokens that begin with hash marks '#'. - -use chars::Lexable; -use states::{Resume, State, StateResult}; +use states::{State, StateResult}; +use states::bool::Bool; use token::Token; -const TRUE_SHORT: &'static str = "t"; -const TRUE: &'static str = "true"; -const FALSE_SHORT: &'static str = "f"; -const FALSE: &'static str = "false"; +trait HashLexable { + fn is_bool_initial(&self) -> bool; + fn is_slash(&self) -> bool; +} #[derive(Debug)] pub struct Hash; -#[derive(Debug)] pub struct BoolSub(String); impl Hash { pub fn new() -> Hash { Hash{} } @@ -23,14 +20,14 @@ impl Hash { impl State for Hash { fn lex(&mut self, c: char) -> StateResult { match c { - c if TRUE.starts_with(c) || FALSE.starts_with(c) => { - let buf = c.to_lowercase().to_string(); - StateResult::Advance { to: Box::new(BoolSub(buf)) } - } + c if c.is_bool_initial() => { + let buf = c.to_ascii_lowercase().to_string(); + StateResult::advance(Box::new(Bool::new(buf.as_str()))) + }, _ => { let msg = format!("Invalid character: {}", c); - StateResult::Fail { msg } - } + StateResult::fail(msg.as_str()) + }, } } @@ -39,61 +36,7 @@ impl State for Hash { } } -impl BoolSub { - fn handle_delimiter(&self) -> Result<(Token, Resume), ()> { - if self.0 == TRUE || self.0 == TRUE_SHORT { - Ok((Token::Bool(true), Resume::Here)) - } else if self.0 == FALSE || self.0 == FALSE_SHORT { - Ok((Token::Bool(false), Resume::Here)) - } else { - Err(()) - } - } -} - -impl State for BoolSub { - fn lex(&mut self, c: char) -> StateResult { - match c { - c if c.is_identifier_delimiter() => match self.handle_delimiter() { - Ok((token, resume)) => StateResult::Emit(token, resume), - Err(_) => { - let msg = format!("Invalid character: {}", c); - StateResult::Fail { msg } - } - }, - _ => { - let buf = { - let mut b = String::from(self.0.as_str()); - b.push(c); - b - }; - if TRUE.starts_with(&buf) || FALSE.starts_with(&buf) { - StateResult::Advance { to: Box::new(BoolSub(buf)) } - } else { - let msg = format!("Invalid character: {}", c); - StateResult::Fail { msg } - } - } - } - } - - fn none(&mut self) -> Result, String> { - match self.handle_delimiter() { - Ok((token, _)) => Ok(Some(token)), - Err(_) => { - let msg = format!("Found EOF while trying to parse a bool"); - Err(msg) - } - } - } -} - -trait HashLexable { - fn is_tf(&self) -> bool; - fn is_slash(&self) -> bool; -} - impl HashLexable for char { - fn is_tf(&self) -> bool { "tfTF".contains(*self) } + fn is_bool_initial(&self) -> bool { "tf".contains(self.to_ascii_lowercase()) } fn is_slash(&self) -> bool { *self == '\\' } } diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index a9c45a0..e8b244e 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -6,12 +6,11 @@ use std::fmt::Debug; use token::Token; mod begin; +mod bool; mod hash; -mod number; mod id; pub use self::begin::Begin; -pub use self::number::BeginNumber; #[derive(Debug)] pub enum StateResult { @@ -39,7 +38,15 @@ pub trait State: Debug { } impl StateResult { + pub fn advance(to: Box) -> StateResult { + StateResult::Advance { to } + } + + pub fn emit(token: Token, at: Resume) -> StateResult { + StateResult::Emit(token, at) + } + pub fn fail(msg: &str) -> StateResult { StateResult::Fail { msg: msg.to_string() } } -} \ No newline at end of file +} From 469929fb8f58acafd65c17670eecd8e723810b3e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 14:05:57 -0700 Subject: [PATCH 22/56] [lexer] Make a states::number module There will be several number-specific states. --- lexer/src/states/number.rs | 45 ------- lexer/src/states/number/mod.rs | 238 +++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 45 deletions(-) delete mode 100644 lexer/src/states/number.rs create mode 100644 lexer/src/states/number/mod.rs diff --git a/lexer/src/states/number.rs b/lexer/src/states/number.rs deleted file mode 100644 index 2bf4c35..0000000 --- a/lexer/src/states/number.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* lexer/src/states/number.rs - * Eryn Wells - */ - -use super::{State, StateResult, Token}; - -#[derive(Debug, Eq, PartialEq)] -pub enum Base { - Bin = 2, - Oct = 8, - Dec = 10, - Hex = 16 -} - -#[derive(Debug)] -pub struct Builder { - base: Base -} - -#[derive(Debug)] -pub struct BeginNumber(Builder); - -impl Builder { - pub fn new() -> Builder { - Builder { base: Base::Dec } - } -} - -impl BeginNumber { - pub fn new() -> BeginNumber { - BeginNumber(Builder::new()) - } -} - -impl State for BeginNumber { - fn lex(&mut self, c: char) -> StateResult { - // TODO: Implement. - StateResult::fail("blah") - } - - fn none(&mut self) -> Result, String> { - // TODO: Implement. - Err("blah".to_string()) - } -} \ No newline at end of file diff --git a/lexer/src/states/number/mod.rs b/lexer/src/states/number/mod.rs new file mode 100644 index 0000000..8c0aec7 --- /dev/null +++ b/lexer/src/states/number/mod.rs @@ -0,0 +1,238 @@ +/* lexer/src/states/number.rs + * Eryn Wells + */ + +use std::collections::HashSet; +use chars::Lexable; +use super::{Resume, State, StateResult, Token}; + +trait NumberLexable { + /// Returns the value of this character interpreted as the indicator for a + /// base. In Scheme, you indicate the base of a number by prefixing it with + /// #[bodx]. + fn base_value(&self) -> Option; + /// Returns the value of the character interpreted as a numerical digit. + fn digit_value(&self) -> Option; + fn sign_value(&self) -> Option; + fn is_dot(&self) -> bool; + fn is_hash(&self) -> bool; + fn is_sign(&self) -> bool; +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Base { Bin = 2, Oct = 8, Dec = 10, Hex = 16 } + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Sign { Neg = -1, Pos = 1 } + +#[derive(Copy, Clone, Debug)] +pub struct Builder { + base: Option, + sign: Option, + value: i64 +} + +#[derive(Debug)] pub struct BeginState(Builder); +#[derive(Debug)] pub struct DigitState(Builder); +#[derive(Debug)] pub struct HashState(Builder); +#[derive(Debug)] pub struct SignState(Builder); + +impl Base { + pub fn contains(&self, digit: u8) -> bool { + digit < (*self as u8) + } +} + +impl Builder { + pub fn new() -> Builder { + Builder { + base: None, + sign: None, + value: 0 + } + } + + fn base(&self) -> Base { + match self.base { + Some(b) => b, + None => Base::Dec + } + } + + fn sign(&self) -> Sign { + match self.sign { + Some(s) => s, + None => Sign::Pos + } + } + + fn push_base(&mut self, base: Base) { + self.base = Some(base); + } + + fn push_sign(&mut self, sign: Sign) { + self.sign = Some(sign); + } + + fn push_digit(&mut self, digit: u8) { + self.value = self.value * self.base_value() as i64 + digit as i64; + } + + fn resolve(&self) -> i64 { + let sign_factor: i64 = if let Some(sign) = self.sign { sign as i64 } else { 1 }; + self.value * sign_factor + } + + fn seen_base(&self) -> bool { self.base.is_some() } + + fn base_value(&self) -> u8 { self.base() as u8 } +} + +impl BeginState { + pub fn new() -> BeginState { + BeginState (Builder::new()) + } +} + +impl State for BeginState { + fn lex(&mut self, c: char) -> StateResult { + match c { + c if c.is_hash() => StateResult::advance(Box::new(HashState(self.0))), + c if c.is_sign() => { + self.0.push_sign(c.sign_value().unwrap()); + StateResult::advance(Box::new(SignState(self.0))) + }, + c if c.is_digit(self.0.base_value() as u32) => { + let value = c.digit_value().unwrap(); + if self.0.base().contains(value) { + self.0.push_digit(value); + StateResult::advance(Box::new(DigitState(self.0))) + } else { + StateResult::fail(format!("invalid digit for current base: {}", c).as_str()) + } + }, + _ => StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } + + fn none(&mut self) -> Result, String> { + // TODO: Implement. + Err("blah".to_string()) + } +} + +impl State for HashState { + fn lex(&mut self, c: char) -> StateResult { + if let Some(base) = c.base_value() { + if !self.0.seen_base() { + self.0.push_base(base); + StateResult::advance(Box::new(BeginState (self.0))) + } else { + StateResult::fail("got base again, despite already having one") + } + } else { + StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } + + fn none(&mut self) -> Result, String> { + // TODO: Implement. + Err("blah".to_string()) + } +} + +impl SignState { + pub fn initials() -> HashSet { + let mut inits = HashSet::new(); + inits.insert('+'); + inits.insert('-'); + inits + } +} + +impl State for SignState { + fn lex(&mut self, c: char) -> StateResult { + if let Some(digit) = c.digit_value() { + if self.0.base().contains(digit) { + self.0.push_digit(digit); + StateResult::advance(Box::new(DigitState(self.0))) + } else { + StateResult::fail(format!("invalid digit for current base: {}", c).as_str()) + } + } else { + StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } + + fn none(&mut self) -> Result, String> { + // TODO: Implement. + Err("blah".to_string()) + } +} + +impl DigitState { + pub fn initials() -> HashSet { + let foldp = |acc: HashSet, x: u8| { + let c = char::from(x); + acc.insert(c); + acc + }; + '0'..='9'.chain(('a' as u8)..=('f' as u8)).fold(HashSet::new(), foldp) + } +} + +impl State for DigitState { + fn lex(&mut self, c: char) -> StateResult { + if let Some(digit) = c.digit_value() { + if self.0.base().contains(digit) { + self.0.push_digit(digit); + StateResult::Continue + } else { + StateResult::fail(format!("invalid digit for current base: {}", c).as_str()) + } + } else if c.is_identifier_delimiter() { + StateResult::emit(Token::Num(self.0.resolve()), Resume::Here) + } else { + StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } + + fn none(&mut self) -> Result, String> { + // TODO: Implement. + Err("blah".to_string()) + } +} + +impl NumberLexable for char { + fn base_value(&self) -> Option { + match *self { + 'b' => Some(Base::Bin), + 'o' => Some(Base::Oct), + 'd' => Some(Base::Dec), + 'x' => Some(Base::Hex), + _ => None + } + } + + fn digit_value(&self) -> Option { + let ascii_value = *self as u32; + match *self { + '0'...'9' => Some((ascii_value - '0' as u32) as u8), + 'a'...'f' => Some((ascii_value - 'a' as u32 + 10) as u8), + 'A'...'F' => Some((ascii_value - 'A' as u32 + 10) as u8), + _ => None + } + } + + fn sign_value(&self) -> Option { + match *self { + '+' => Some(Sign::Pos), + '-' => Some(Sign::Neg), + _ => None + } + } + + fn is_dot(&self) -> bool { *self == '.' } + fn is_hash(&self) -> bool { *self == '#' } + fn is_sign(&self) -> bool { self.sign_value().is_some() } +} From b759ee4c57f28ac10725ef272b8ee87a125cc51e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 15:18:15 -0700 Subject: [PATCH 23/56] [lexer] Add Error::invalid_char constructor --- lexer/src/error.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lexer/src/error.rs b/lexer/src/error.rs index 73a71bc..c7aa1ce 100644 --- a/lexer/src/error.rs +++ b/lexer/src/error.rs @@ -14,5 +14,9 @@ impl Error { } } + pub fn invalid_char(c: char) -> Error { + Error::new(format!("invalid character: {}", c)) + } + pub fn msg(&self) -> &str { &self.message } } From def35966eb19b6434bda8d84de789f5c3aeee207 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 15:19:28 -0700 Subject: [PATCH 24/56] [lexer] Re-kajigger the states for numbers --- lexer/src/chars.rs | 12 ++ lexer/src/states/hash.rs | 9 ++ lexer/src/states/mod.rs | 1 + lexer/src/states/number/mod.rs | 231 ++++++------------------------ lexer/src/states/number/prefix.rs | 68 +++++++++ 5 files changed, 135 insertions(+), 186 deletions(-) create mode 100644 lexer/src/states/number/prefix.rs diff --git a/lexer/src/chars.rs b/lexer/src/chars.rs index 60192c5..6ef0375 100644 --- a/lexer/src/chars.rs +++ b/lexer/src/chars.rs @@ -8,9 +8,16 @@ pub trait Lexable { fn is_identifier_initial(&self) -> bool; fn is_identifier_subsequent(&self) -> bool; fn is_identifier_delimiter(&self) -> bool; + + fn is_exactness(&self) -> bool; + fn is_radix(&self) -> bool; } impl Lexable for char { + fn is_exactness(&self) -> bool { + *self == 'i' || *self == 'e' + } + fn is_left_paren(&self) -> bool { *self == '(' } @@ -30,6 +37,11 @@ impl Lexable for char { fn is_identifier_delimiter(&self) -> bool { self.is_whitespace() || self.is_left_paren() || self.is_right_paren() } + + fn is_radix(&self) -> bool { + let radishes = &['b', 'd', 'o', 'x']; + radishes.contains(self) + } } trait LexableSpecial { diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index 14ef37b..04f078b 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -2,8 +2,10 @@ * Eryn Wells */ +use chars::Lexable; use states::{State, StateResult}; use states::bool::Bool; +use states::number::Prefix; use token::Token; trait HashLexable { @@ -24,6 +26,13 @@ impl State for Hash { let buf = c.to_ascii_lowercase().to_string(); StateResult::advance(Box::new(Bool::new(buf.as_str()))) }, + c if c.is_radix() || c.is_exactness() => { + if let Some(st) = Prefix::with_char(c) { + StateResult::advance(Box::new(st)) + } else { + StateResult::fail(format!("invalid numeric prefix character: {}", c).as_str()) + } + }, _ => { let msg = format!("Invalid character: {}", c); StateResult::fail(msg.as_str()) diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index e8b244e..3c6194d 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -8,6 +8,7 @@ use token::Token; mod begin; mod bool; mod hash; +mod number; mod id; pub use self::begin::Begin; diff --git a/lexer/src/states/number/mod.rs b/lexer/src/states/number/mod.rs index 8c0aec7..27d0315 100644 --- a/lexer/src/states/number/mod.rs +++ b/lexer/src/states/number/mod.rs @@ -1,16 +1,17 @@ -/* lexer/src/states/number.rs +/* lexer/src/states/number/mod.rs * Eryn Wells */ -use std::collections::HashSet; -use chars::Lexable; -use super::{Resume, State, StateResult, Token}; +mod prefix; +mod sign; + +pub use self::prefix::Prefix; trait NumberLexable { /// Returns the value of this character interpreted as the indicator for a /// base. In Scheme, you indicate the base of a number by prefixing it with /// #[bodx]. - fn base_value(&self) -> Option; + fn base_value(&self) -> Option; /// Returns the value of the character interpreted as a numerical digit. fn digit_value(&self) -> Option; fn sign_value(&self) -> Option; @@ -20,219 +21,77 @@ trait NumberLexable { } #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Base { Bin = 2, Oct = 8, Dec = 10, Hex = 16 } +pub enum Radix { Bin = 2, Oct = 8, Dec = 10, Hex = 16 } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Sign { Neg = -1, Pos = 1 } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Exact { Yes, No } + #[derive(Copy, Clone, Debug)] pub struct Builder { - base: Option, + radix: Option, sign: Option, - value: i64 + exact: Option, + value: i64, } -#[derive(Debug)] pub struct BeginState(Builder); -#[derive(Debug)] pub struct DigitState(Builder); -#[derive(Debug)] pub struct HashState(Builder); -#[derive(Debug)] pub struct SignState(Builder); +impl Radix { + pub fn from(c: char) -> Option { + match c { + 'b'|'B' => Some(Radix::Bin), + 'o'|'O' => Some(Radix::Oct), + 'd'|'D' => Some(Radix::Dec), + 'x'|'X' => Some(Radix::Hex), + _ => None + } + } +} -impl Base { - pub fn contains(&self, digit: u8) -> bool { - digit < (*self as u8) +impl Exact { + pub fn from(c: char) -> Option { + match c { + 'i'|'I' => Some(Exact::No), + 'e'|'E' => Some(Exact::Yes), + _ => None + } } } impl Builder { pub fn new() -> Builder { Builder { - base: None, + radix: None, sign: None, + exact: None, value: 0 } } - fn base(&self) -> Base { - match self.base { - Some(b) => b, - None => Base::Dec - } + fn push_digit(&mut self, digit: u8) { + //self.value = self.value * self.base_value() as i64 + digit as i64; } - fn sign(&self) -> Sign { - match self.sign { - Some(s) => s, - None => Sign::Pos - } + fn push_exact(&mut self, ex: Exact) { + self.exact = Some(ex); } - fn push_base(&mut self, base: Base) { - self.base = Some(base); + fn push_radix(&mut self, radix: Radix) { + self.radix = Some(radix); } fn push_sign(&mut self, sign: Sign) { self.sign = Some(sign); } - fn push_digit(&mut self, digit: u8) { - self.value = self.value * self.base_value() as i64 + digit as i64; - } - fn resolve(&self) -> i64 { - let sign_factor: i64 = if let Some(sign) = self.sign { sign as i64 } else { 1 }; - self.value * sign_factor + //let sign_factor: i64 = if let Some(sign) = self.sign { sign as i64 } else { 1 }; + //self.value * sign_factor + 0 } - fn seen_base(&self) -> bool { self.base.is_some() } - - fn base_value(&self) -> u8 { self.base() as u8 } -} - -impl BeginState { - pub fn new() -> BeginState { - BeginState (Builder::new()) - } -} - -impl State for BeginState { - fn lex(&mut self, c: char) -> StateResult { - match c { - c if c.is_hash() => StateResult::advance(Box::new(HashState(self.0))), - c if c.is_sign() => { - self.0.push_sign(c.sign_value().unwrap()); - StateResult::advance(Box::new(SignState(self.0))) - }, - c if c.is_digit(self.0.base_value() as u32) => { - let value = c.digit_value().unwrap(); - if self.0.base().contains(value) { - self.0.push_digit(value); - StateResult::advance(Box::new(DigitState(self.0))) - } else { - StateResult::fail(format!("invalid digit for current base: {}", c).as_str()) - } - }, - _ => StateResult::fail(format!("invalid char: {}", c).as_str()) - } - } - - fn none(&mut self) -> Result, String> { - // TODO: Implement. - Err("blah".to_string()) - } -} - -impl State for HashState { - fn lex(&mut self, c: char) -> StateResult { - if let Some(base) = c.base_value() { - if !self.0.seen_base() { - self.0.push_base(base); - StateResult::advance(Box::new(BeginState (self.0))) - } else { - StateResult::fail("got base again, despite already having one") - } - } else { - StateResult::fail(format!("invalid char: {}", c).as_str()) - } - } - - fn none(&mut self) -> Result, String> { - // TODO: Implement. - Err("blah".to_string()) - } -} - -impl SignState { - pub fn initials() -> HashSet { - let mut inits = HashSet::new(); - inits.insert('+'); - inits.insert('-'); - inits - } -} - -impl State for SignState { - fn lex(&mut self, c: char) -> StateResult { - if let Some(digit) = c.digit_value() { - if self.0.base().contains(digit) { - self.0.push_digit(digit); - StateResult::advance(Box::new(DigitState(self.0))) - } else { - StateResult::fail(format!("invalid digit for current base: {}", c).as_str()) - } - } else { - StateResult::fail(format!("invalid char: {}", c).as_str()) - } - } - - fn none(&mut self) -> Result, String> { - // TODO: Implement. - Err("blah".to_string()) - } -} - -impl DigitState { - pub fn initials() -> HashSet { - let foldp = |acc: HashSet, x: u8| { - let c = char::from(x); - acc.insert(c); - acc - }; - '0'..='9'.chain(('a' as u8)..=('f' as u8)).fold(HashSet::new(), foldp) - } -} - -impl State for DigitState { - fn lex(&mut self, c: char) -> StateResult { - if let Some(digit) = c.digit_value() { - if self.0.base().contains(digit) { - self.0.push_digit(digit); - StateResult::Continue - } else { - StateResult::fail(format!("invalid digit for current base: {}", c).as_str()) - } - } else if c.is_identifier_delimiter() { - StateResult::emit(Token::Num(self.0.resolve()), Resume::Here) - } else { - StateResult::fail(format!("invalid char: {}", c).as_str()) - } - } - - fn none(&mut self) -> Result, String> { - // TODO: Implement. - Err("blah".to_string()) - } -} - -impl NumberLexable for char { - fn base_value(&self) -> Option { - match *self { - 'b' => Some(Base::Bin), - 'o' => Some(Base::Oct), - 'd' => Some(Base::Dec), - 'x' => Some(Base::Hex), - _ => None - } - } - - fn digit_value(&self) -> Option { - let ascii_value = *self as u32; - match *self { - '0'...'9' => Some((ascii_value - '0' as u32) as u8), - 'a'...'f' => Some((ascii_value - 'a' as u32 + 10) as u8), - 'A'...'F' => Some((ascii_value - 'A' as u32 + 10) as u8), - _ => None - } - } - - fn sign_value(&self) -> Option { - match *self { - '+' => Some(Sign::Pos), - '-' => Some(Sign::Neg), - _ => None - } - } - - fn is_dot(&self) -> bool { *self == '.' } - fn is_hash(&self) -> bool { *self == '#' } - fn is_sign(&self) -> bool { self.sign_value().is_some() } + fn seen_exact(&self) -> bool { self.exact.is_some() } + fn seen_radix(&self) -> bool { self.radix.is_some() } + fn seen_sign(&self) -> bool { self.sign.is_some() } } diff --git a/lexer/src/states/number/prefix.rs b/lexer/src/states/number/prefix.rs new file mode 100644 index 0000000..9c68043 --- /dev/null +++ b/lexer/src/states/number/prefix.rs @@ -0,0 +1,68 @@ +/* lexer/src/states/number/prefix.rs + * Eryn Wells + */ + +use super::{Radix, Exact}; +use states::{State, StateResult}; +use states::number::Builder; +use token::Token; + +#[derive(Debug)] pub struct Prefix(Builder); +#[derive(Debug)] pub struct Hash(Builder); + +impl Prefix { + pub fn new(b: Builder) -> Prefix { + Prefix(b) + } + + pub fn with_char(c: char) -> Option { + let mut builder = Builder::new(); + if let Some(ex) = Exact::from(c) { + builder.push_exact(ex); + } else if let Some(rx) = Radix::from(c) { + builder.push_radix(rx); + } else { + return None; + } + Some(Prefix::new(builder)) + } +} + +impl State for Prefix { + fn lex(&mut self, c: char) -> StateResult { + match c { + '#' => StateResult::advance(Box::new(Hash(self.0))), + _ => StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } + + fn none(&mut self) -> Result, String> { + Err("blah".to_string()) + } +} + +impl State for Hash { + fn lex(&mut self, c: char) -> StateResult { + if let Some(ex) = Exact::from(c) { + if !self.0.seen_exact() { + self.0.push_exact(ex); + StateResult::advance(Box::new(Prefix::new(self.0))) + } else { + StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } else if let Some(rx) = Radix::from(c) { + if !self.0.seen_radix() { + self.0.push_radix(rx); + StateResult::advance(Box::new(Prefix::new(self.0))) + } else { + StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } else { + StateResult::fail(format!("invalid char: {}", c).as_str()) + } + } + + fn none(&mut self) -> Result, String> { + Err("blah".to_string()) + } +} From dc8f5a76864b8433bbff7d2685363853451f300a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 15:30:48 -0700 Subject: [PATCH 25/56] [lexer] Replace String error messages with Error type --- lexer/src/lib.rs | 6 +++--- lexer/src/states/begin.rs | 8 +++----- lexer/src/states/bool.rs | 16 +++++----------- lexer/src/states/hash.rs | 10 ++++------ lexer/src/states/id.rs | 8 +++----- lexer/src/states/mod.rs | 9 +++++---- lexer/src/states/number/prefix.rs | 19 ++++++++++--------- 7 files changed, 33 insertions(+), 43 deletions(-) diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index dd947a9..449afc3 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -62,7 +62,7 @@ impl Iterator for Lexer where T: Iterator { out = Some(Ok(Lex::new(token, &buffer, self.line, self.offset))); break; }, - Err(msg) => panic!("{}", msg) + Err(err) => panic!("{}:{}: {}", self.line, self.offset, err.msg()) }, Some(c) => { let result = state.lex(c); @@ -84,8 +84,8 @@ impl Iterator for Lexer where T: Iterator { out = Some(Ok(Lex::new(token, &buffer, self.line, self.offset))); break; }, - StateResult::Fail { msg } => { - panic!("{}", msg); + StateResult::Fail(err) => { + panic!("{}:{}: {}", self.line, self.offset, err.msg()); } } }, diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index cea8aa5..b4d9732 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -3,6 +3,7 @@ */ use chars::Lexable; +use error::Error; use token::Token; use states::{Resume, State, StateResult}; use states::id::IdSub; @@ -20,14 +21,11 @@ impl State for Begin { c if c.is_whitespace() => StateResult::Continue, c if c.is_identifier_initial() => StateResult::Advance { to: Box::new(IdSub{}) }, c if c.is_hash() => StateResult::Advance { to: Box::new(Hash::new()) }, - _ => { - let msg = format!("Invalid character: {}", c); - StateResult::Fail { msg } - } + _ => StateResult::fail(Error::invalid_char(c)), } } - fn none(&mut self) -> Result, String> { + fn none(&mut self) -> Result, Error> { Ok(None) } } diff --git a/lexer/src/states/bool.rs b/lexer/src/states/bool.rs index 20796f2..c9a52e0 100644 --- a/lexer/src/states/bool.rs +++ b/lexer/src/states/bool.rs @@ -2,6 +2,7 @@ * Eryn Wells */ +use error::Error; use chars::Lexable; use states::{Resume, State, StateResult}; use token::Token; @@ -34,10 +35,7 @@ impl State for Bool { match c { c if c.is_identifier_delimiter() => match self.handle_delimiter() { Some(token) => StateResult::Emit(token, Resume::Here), - None => { - let msg = format!("Invalid character: {}", c); - StateResult::fail(msg.as_str()) - }, + None => StateResult::fail(Error::invalid_char(c)), }, _ => { let buf = { @@ -48,20 +46,16 @@ impl State for Bool { if TRUE.starts_with(&buf) || FALSE.starts_with(&buf) { StateResult::advance(Box::new(Bool(buf))) } else { - let msg = format!("Invalid character: {}", c); - StateResult::fail(msg.as_str()) + StateResult::fail(Error::invalid_char(c)) } }, } } - fn none(&mut self) -> Result, String> { + fn none(&mut self) -> Result, Error> { match self.handle_delimiter() { Some(token) => Ok(Some(token)), - None => { - let msg = format!("Found EOF while trying to parse a bool"); - Err(msg) - } + None => Err(Error::new("Found EOF while trying to parse a bool".to_string())) } } } diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index 04f078b..9123773 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -3,6 +3,7 @@ */ use chars::Lexable; +use error::Error; use states::{State, StateResult}; use states::bool::Bool; use states::number::Prefix; @@ -30,17 +31,14 @@ impl State for Hash { if let Some(st) = Prefix::with_char(c) { StateResult::advance(Box::new(st)) } else { - StateResult::fail(format!("invalid numeric prefix character: {}", c).as_str()) + StateResult::fail(Error::new(format!("invalid numeric prefix character: {}", c))) } }, - _ => { - let msg = format!("Invalid character: {}", c); - StateResult::fail(msg.as_str()) - }, + _ => StateResult::fail(Error::invalid_char(c)), } } - fn none(&mut self) -> Result, String> { + fn none(&mut self) -> Result, Error> { Ok(None) } } diff --git a/lexer/src/states/id.rs b/lexer/src/states/id.rs index 0232c00..bd792ce 100644 --- a/lexer/src/states/id.rs +++ b/lexer/src/states/id.rs @@ -3,6 +3,7 @@ */ use chars::Lexable; +use error::Error; use states::{Resume, State, StateResult}; use token::Token; @@ -14,14 +15,11 @@ impl State for IdSub { match c { c if c.is_identifier_subsequent() => StateResult::Continue, c if c.is_identifier_delimiter() => StateResult::Emit(Token::Id, Resume::Here), - _ => { - let msg = format!("Invalid character: {}", c); - StateResult::Fail { msg } - } + _ => StateResult::fail(Error::invalid_char(c)), } } - fn none(&mut self) -> Result, String> { + fn none(&mut self) -> Result, Error> { Ok(Some(Token::Id)) } } diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index 3c6194d..33f0360 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -3,6 +3,7 @@ */ use std::fmt::Debug; +use error::Error; use token::Token; mod begin; @@ -22,7 +23,7 @@ pub enum StateResult { /// Emit a Lex with the provided Token and the accumulated buffer. The Resume value indicates /// whether to revisit the current input character or advance to the next one. Emit(Token, Resume), - Fail { msg: String } + Fail(Error) } #[derive(Debug, Eq, PartialEq)] @@ -35,7 +36,7 @@ pub enum Resume { pub trait State: Debug { fn lex(&mut self, c: char) -> StateResult; - fn none(&mut self) -> Result, String>; + fn none(&mut self) -> Result, Error>; } impl StateResult { @@ -47,7 +48,7 @@ impl StateResult { StateResult::Emit(token, at) } - pub fn fail(msg: &str) -> StateResult { - StateResult::Fail { msg: msg.to_string() } + pub fn fail(err: Error) -> StateResult { + StateResult::Fail(err) } } diff --git a/lexer/src/states/number/prefix.rs b/lexer/src/states/number/prefix.rs index 9c68043..fbb7460 100644 --- a/lexer/src/states/number/prefix.rs +++ b/lexer/src/states/number/prefix.rs @@ -2,8 +2,9 @@ * Eryn Wells */ -use super::{Radix, Exact}; +use error::Error; use states::{State, StateResult}; +use states::number::{Radix, Exact}; use states::number::Builder; use token::Token; @@ -32,12 +33,12 @@ impl State for Prefix { fn lex(&mut self, c: char) -> StateResult { match c { '#' => StateResult::advance(Box::new(Hash(self.0))), - _ => StateResult::fail(format!("invalid char: {}", c).as_str()) + _ => StateResult::fail(Error::invalid_char(c)) } } - fn none(&mut self) -> Result, String> { - Err("blah".to_string()) + fn none(&mut self) -> Result, Error> { + Err(Error::new("blah".to_string())) } } @@ -48,21 +49,21 @@ impl State for Hash { self.0.push_exact(ex); StateResult::advance(Box::new(Prefix::new(self.0))) } else { - StateResult::fail(format!("invalid char: {}", c).as_str()) + StateResult::fail(Error::invalid_char(c)) } } else if let Some(rx) = Radix::from(c) { if !self.0.seen_radix() { self.0.push_radix(rx); StateResult::advance(Box::new(Prefix::new(self.0))) } else { - StateResult::fail(format!("invalid char: {}", c).as_str()) + StateResult::fail(Error::invalid_char(c)) } } else { - StateResult::fail(format!("invalid char: {}", c).as_str()) + StateResult::fail(Error::invalid_char(c)) } } - fn none(&mut self) -> Result, String> { - Err("blah".to_string()) + fn none(&mut self) -> Result, Error> { + Err(Error::new("blah".to_string())) } } From 663ae3a9f141b6864c93771faeb1c9554e6b94df Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 15:48:48 -0700 Subject: [PATCH 26/56] [lexer] move char::is_hash() to chars::Lexable --- lexer/src/chars.rs | 5 +++++ lexer/src/states/begin.rs | 8 -------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lexer/src/chars.rs b/lexer/src/chars.rs index 6ef0375..f1507b1 100644 --- a/lexer/src/chars.rs +++ b/lexer/src/chars.rs @@ -11,6 +11,7 @@ pub trait Lexable { fn is_exactness(&self) -> bool; fn is_radix(&self) -> bool; + fn is_hash(&self) -> bool; } impl Lexable for char { @@ -42,6 +43,10 @@ impl Lexable for char { let radishes = &['b', 'd', 'o', 'x']; radishes.contains(self) } + + fn is_hash(&self) -> bool { + *self == '#' + } } trait LexableSpecial { diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index b4d9732..670b0dc 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -29,11 +29,3 @@ impl State for Begin { Ok(None) } } - -trait BeginLexable { - fn is_hash(&self) -> bool; -} - -impl BeginLexable for char { - fn is_hash(&self) -> bool { *self == '#' } -} From eabc8f98e8d42c26e2258f883ac07c414b77f2f8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 15:49:13 -0700 Subject: [PATCH 27/56] [lexer] Add number::Sign state --- lexer/src/error.rs | 4 +++ lexer/src/states/number/prefix.rs | 14 +++++---- lexer/src/states/number/sign.rs | 47 +++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 lexer/src/states/number/sign.rs diff --git a/lexer/src/error.rs b/lexer/src/error.rs index c7aa1ce..ca05e36 100644 --- a/lexer/src/error.rs +++ b/lexer/src/error.rs @@ -18,5 +18,9 @@ impl Error { Error::new(format!("invalid character: {}", c)) } + pub fn unexpected_eof() -> Error { + Error::new("unexpected EOF".to_string()) + } + pub fn msg(&self) -> &str { &self.message } } diff --git a/lexer/src/states/number/prefix.rs b/lexer/src/states/number/prefix.rs index fbb7460..0bf8a39 100644 --- a/lexer/src/states/number/prefix.rs +++ b/lexer/src/states/number/prefix.rs @@ -2,10 +2,11 @@ * Eryn Wells */ +use chars::Lexable; use error::Error; use states::{State, StateResult}; -use states::number::{Radix, Exact}; -use states::number::Builder; +use states::number::{Builder, Radix, Exact}; +use states::number::sign::Sign; use token::Token; #[derive(Debug)] pub struct Prefix(Builder); @@ -31,9 +32,12 @@ impl Prefix { impl State for Prefix { fn lex(&mut self, c: char) -> StateResult { - match c { - '#' => StateResult::advance(Box::new(Hash(self.0))), - _ => StateResult::fail(Error::invalid_char(c)) + if c.is_hash() { + StateResult::advance(Box::new(Hash(self.0))) + } else if let Some(sn) = Sign::with_char(self.0, c) { + StateResult::advance(Box::new(sn)) + } else { + StateResult::fail(Error::invalid_char(c)) } } diff --git a/lexer/src/states/number/sign.rs b/lexer/src/states/number/sign.rs new file mode 100644 index 0000000..11310aa --- /dev/null +++ b/lexer/src/states/number/sign.rs @@ -0,0 +1,47 @@ +/* lexer/src/states/number/sign.rs + * Eryn Wells + */ + +use error::Error; +use states::{State, StateResult}; +use states::number::Builder; +use states::number::Sign as Sgn; +use token::Token; + +#[derive(Debug)] pub struct Sign(Builder); + +impl Sign { + pub fn new(b: Builder) -> Sign { + Sign(b) + } + + pub fn with_char(b: Builder, c: char) -> Option { + if !b.seen_sign() { + match c { + '+' => { + let mut b = b.clone(); + b.push_sign(Sgn::Pos); + Some(Sign::new(b)) + }, + '-' => { + let mut b = b.clone(); + b.push_sign(Sgn::Neg); + Some(Sign::new(b)) + }, + _ => None + } + } else { + None + } + } +} + +impl State for Sign { + fn lex(&mut self, c: char) -> StateResult { + StateResult::fail(Error::invalid_char(c)) + } + + fn none(&mut self) -> Result, Error> { + Err(Error::unexpected_eof()) + } +} From 176686b3a71483dbfda77b25ea6bb5e2468c4662 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 16:06:52 -0700 Subject: [PATCH 28/56] [lexer] Let states handle their own creation in from_char --- lexer/src/states/hash.rs | 4 ++-- lexer/src/states/number/prefix.rs | 36 ++++++++++++++----------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index 9123773..ffccbb5 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -6,7 +6,7 @@ use chars::Lexable; use error::Error; use states::{State, StateResult}; use states::bool::Bool; -use states::number::Prefix; +use states::number::{Builder, Prefix}; use token::Token; trait HashLexable { @@ -28,7 +28,7 @@ impl State for Hash { StateResult::advance(Box::new(Bool::new(buf.as_str()))) }, c if c.is_radix() || c.is_exactness() => { - if let Some(st) = Prefix::with_char(c) { + if let Some(st) = Prefix::with_char(Builder::new(), c) { StateResult::advance(Box::new(st)) } else { StateResult::fail(Error::new(format!("invalid numeric prefix character: {}", c))) diff --git a/lexer/src/states/number/prefix.rs b/lexer/src/states/number/prefix.rs index 0bf8a39..dddadfa 100644 --- a/lexer/src/states/number/prefix.rs +++ b/lexer/src/states/number/prefix.rs @@ -17,16 +17,24 @@ impl Prefix { Prefix(b) } - pub fn with_char(c: char) -> Option { - let mut builder = Builder::new(); + pub fn with_char(b: Builder, c: char) -> Option { if let Some(ex) = Exact::from(c) { - builder.push_exact(ex); + if b.seen_exact() { + return None; + } + let mut b = b.clone(); + b.push_exact(ex); + Some(Prefix::new(b)) } else if let Some(rx) = Radix::from(c) { - builder.push_radix(rx); + if b.seen_radix() { + return None; + } + let mut b = b.clone(); + b.push_radix(rx); + Some(Prefix::new(b)) } else { - return None; + None } - Some(Prefix::new(builder)) } } @@ -48,20 +56,8 @@ impl State for Prefix { impl State for Hash { fn lex(&mut self, c: char) -> StateResult { - if let Some(ex) = Exact::from(c) { - if !self.0.seen_exact() { - self.0.push_exact(ex); - StateResult::advance(Box::new(Prefix::new(self.0))) - } else { - StateResult::fail(Error::invalid_char(c)) - } - } else if let Some(rx) = Radix::from(c) { - if !self.0.seen_radix() { - self.0.push_radix(rx); - StateResult::advance(Box::new(Prefix::new(self.0))) - } else { - StateResult::fail(Error::invalid_char(c)) - } + if let Some(st) = Prefix::with_char(self.0, c) { + StateResult::advance(Box::new(st)) } else { StateResult::fail(Error::invalid_char(c)) } From d272b211ae7aec40ad7fc2170549f7a5a61e3e9e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 17:08:05 -0700 Subject: [PATCH 29/56] [lexer] WIP number::Digit state --- lexer/src/states/number/digit.rs | 44 +++++++++++++++++++++++++++++++ lexer/src/states/number/mod.rs | 37 +++++++++++++++----------- lexer/src/states/number/prefix.rs | 2 +- 3 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 lexer/src/states/number/digit.rs diff --git a/lexer/src/states/number/digit.rs b/lexer/src/states/number/digit.rs new file mode 100644 index 0000000..d571cbb --- /dev/null +++ b/lexer/src/states/number/digit.rs @@ -0,0 +1,44 @@ +/* lexer/src/states/number/digit.rs + * Eryn Wells + */ + +use chars::Lexable; +use error::Error; +use states::{State, StateResult}; +use states::number::{Builder, Radix, Exact}; +use states::number::sign::Sign; +use token::Token; + +#[derive(Debug)] pub struct Digit(Builder); + +impl Digit { + pub fn new(b: Builder) -> Digit { + Digit(b) + } + + pub fn with_char(b: Builder, c: char) -> Option { + let mut b = b.clone(); + if !b.seen_radix() { + b.push_radix(Radix::Dec); + } + match b.push_digit(c) { + Ok(_) => Some(Digit::new(b)), + // TODO: Deal with this error properly. Don't just ignore it. + Err(_) => None, + } + } +} + +impl State for Digit { + fn lex(&mut self, c: char) -> StateResult { + if self.0.push_digit(c).is_ok() { + StateResult::Continue + } else { + StateResult::fail(Error::invalid_char(c)) + } + } + + fn none(&mut self) -> Result, Error> { + Err(Error::unexpected_eof()) + } +} diff --git a/lexer/src/states/number/mod.rs b/lexer/src/states/number/mod.rs index 27d0315..daeecde 100644 --- a/lexer/src/states/number/mod.rs +++ b/lexer/src/states/number/mod.rs @@ -2,24 +2,14 @@ * Eryn Wells */ +use error::Error; + +mod digit; mod prefix; mod sign; pub use self::prefix::Prefix; -trait NumberLexable { - /// Returns the value of this character interpreted as the indicator for a - /// base. In Scheme, you indicate the base of a number by prefixing it with - /// #[bodx]. - fn base_value(&self) -> Option; - /// Returns the value of the character interpreted as a numerical digit. - fn digit_value(&self) -> Option; - fn sign_value(&self) -> Option; - fn is_dot(&self) -> bool; - fn is_hash(&self) -> bool; - fn is_sign(&self) -> bool; -} - #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Radix { Bin = 2, Oct = 8, Dec = 10, Hex = 16 } @@ -65,12 +55,19 @@ impl Builder { radix: None, sign: None, exact: None, - value: 0 + value: 0, } } - fn push_digit(&mut self, digit: u8) { - //self.value = self.value * self.base_value() as i64 + digit as i64; + fn push_digit(&mut self, c: char) -> Result<(), Error> { + let rx = self.radix_value(); + match c.to_digit(rx as u32) { + Some(d) => { + self.value = self.value * rx as i64 + d as i64; + Ok(()) + }, + None => Err(Error::invalid_char(c)) + } } fn push_exact(&mut self, ex: Exact) { @@ -94,4 +91,12 @@ impl Builder { fn seen_exact(&self) -> bool { self.exact.is_some() } fn seen_radix(&self) -> bool { self.radix.is_some() } fn seen_sign(&self) -> bool { self.sign.is_some() } + + fn radix_value(&self) -> u8 { + let rx = match self.radix { + Some(r) => r, + None => Radix::Dec, + }; + rx as u8 + } } diff --git a/lexer/src/states/number/prefix.rs b/lexer/src/states/number/prefix.rs index dddadfa..6003b81 100644 --- a/lexer/src/states/number/prefix.rs +++ b/lexer/src/states/number/prefix.rs @@ -50,7 +50,7 @@ impl State for Prefix { } fn none(&mut self) -> Result, Error> { - Err(Error::new("blah".to_string())) + Err(Error::unexpected_eof()) } } From 853312ce67703fb38c10c176ae4de42d75994930 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 17:17:49 -0700 Subject: [PATCH 30/56] [lexer] We can lex integers! --- lexer/src/states/begin.rs | 22 +++++++++++++++------- lexer/src/states/hash.rs | 2 +- lexer/src/states/number/digit.rs | 4 +--- lexer/src/states/number/mod.rs | 10 +++++++--- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index 670b0dc..d8770ee 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -8,20 +8,28 @@ use token::Token; use states::{Resume, State, StateResult}; use states::id::IdSub; use states::hash::Hash; +use states::number::{Builder, Digit}; #[derive(Debug)] pub struct Begin; impl State for Begin { fn lex(&mut self, c: char) -> StateResult { - match c { - c if c.is_left_paren() => StateResult::Emit(Token::LeftParen, Resume::AtNext), - c if c.is_right_paren() => StateResult::Emit(Token::RightParen, Resume::AtNext), + if c.is_left_paren() { + StateResult::Emit(Token::LeftParen, Resume::AtNext) + } else if c.is_right_paren() { + StateResult::Emit(Token::RightParen, Resume::AtNext) + } else if c.is_whitespace() { // TODO: Figure out some way to track newlines. - c if c.is_whitespace() => StateResult::Continue, - c if c.is_identifier_initial() => StateResult::Advance { to: Box::new(IdSub{}) }, - c if c.is_hash() => StateResult::Advance { to: Box::new(Hash::new()) }, - _ => StateResult::fail(Error::invalid_char(c)), + StateResult::Continue + } else if c.is_identifier_initial() { + StateResult::advance(Box::new(IdSub{})) + } else if c.is_hash() { + StateResult::advance(Box::new(Hash::new())) + } else if let Some(st) = Digit::with_char(Builder::new(), c) { + StateResult::advance(Box::new(st)) + } else { + StateResult::fail(Error::invalid_char(c)) } } diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index ffccbb5..80f7103 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -39,7 +39,7 @@ impl State for Hash { } fn none(&mut self) -> Result, Error> { - Ok(None) + Err(Error::unexpected_eof()) } } diff --git a/lexer/src/states/number/digit.rs b/lexer/src/states/number/digit.rs index d571cbb..fb5ccef 100644 --- a/lexer/src/states/number/digit.rs +++ b/lexer/src/states/number/digit.rs @@ -2,11 +2,9 @@ * Eryn Wells */ -use chars::Lexable; use error::Error; use states::{State, StateResult}; use states::number::{Builder, Radix, Exact}; -use states::number::sign::Sign; use token::Token; #[derive(Debug)] pub struct Digit(Builder); @@ -39,6 +37,6 @@ impl State for Digit { } fn none(&mut self) -> Result, Error> { - Err(Error::unexpected_eof()) + Ok(Some(Token::Num(self.0.resolve()))) } } diff --git a/lexer/src/states/number/mod.rs b/lexer/src/states/number/mod.rs index daeecde..c0612a7 100644 --- a/lexer/src/states/number/mod.rs +++ b/lexer/src/states/number/mod.rs @@ -9,6 +9,7 @@ mod prefix; mod sign; pub use self::prefix::Prefix; +pub use self::digit::Digit; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Radix { Bin = 2, Oct = 8, Dec = 10, Hex = 16 } @@ -83,9 +84,12 @@ impl Builder { } fn resolve(&self) -> i64 { - //let sign_factor: i64 = if let Some(sign) = self.sign { sign as i64 } else { 1 }; - //self.value * sign_factor - 0 + let sign_factor: i64 = if let Some(sign) = self.sign { + sign as i64 + } else { + 1 + }; + self.value * sign_factor } fn seen_exact(&self) -> bool { self.exact.is_some() } From 569fe82c1adb0a3dfa96fb283ceb76aaf81c709c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 17:20:27 -0700 Subject: [PATCH 31/56] [lexer] Identify delimiters and emit numbers --- lexer/src/states/number/digit.rs | 7 +++++-- lexer/tests/single_tokens.rs | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lexer/src/states/number/digit.rs b/lexer/src/states/number/digit.rs index fb5ccef..26e9493 100644 --- a/lexer/src/states/number/digit.rs +++ b/lexer/src/states/number/digit.rs @@ -2,9 +2,10 @@ * Eryn Wells */ +use chars::Lexable; use error::Error; -use states::{State, StateResult}; -use states::number::{Builder, Radix, Exact}; +use states::{State, StateResult, Resume}; +use states::number::{Builder, Radix}; use token::Token; #[derive(Debug)] pub struct Digit(Builder); @@ -31,6 +32,8 @@ impl State for Digit { fn lex(&mut self, c: char) -> StateResult { if self.0.push_digit(c).is_ok() { StateResult::Continue + } else if c.is_identifier_delimiter() { + StateResult::emit(Token::Num(self.0.resolve()), Resume::Here) } else { StateResult::fail(Error::invalid_char(c)) } diff --git a/lexer/tests/single_tokens.rs b/lexer/tests/single_tokens.rs index 5c89c31..ff77dfa 100644 --- a/lexer/tests/single_tokens.rs +++ b/lexer/tests/single_tokens.rs @@ -75,8 +75,9 @@ fn bool_with_spaces() { #[test] fn integer() { - let expected_lex = Lex::new(Token::Num(23), "23", 0, 0); - let mut lex = Lexer::new("23".chars()); - assert_eq!(lex.next(), Some(Ok(expected_lex))); + let mut lex = Lexer::new("23 42".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(23), "23", 0, 0)))); + // TODO: Fix this once issue #12 is fixed. + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(42), " 42", 0, 0)))); assert_eq!(lex.next(), None); } From 0bdfc24abd043dcc985c0174ad28e7bab41edf99 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 3 Sep 2018 19:24:06 -0700 Subject: [PATCH 32/56] [lexer] Simply some Option logic --- lexer/src/states/number/mod.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lexer/src/states/number/mod.rs b/lexer/src/states/number/mod.rs index c0612a7..933fca4 100644 --- a/lexer/src/states/number/mod.rs +++ b/lexer/src/states/number/mod.rs @@ -84,11 +84,7 @@ impl Builder { } fn resolve(&self) -> i64 { - let sign_factor: i64 = if let Some(sign) = self.sign { - sign as i64 - } else { - 1 - }; + let sign_factor = self.sign_value() as i64; self.value * sign_factor } @@ -96,11 +92,6 @@ impl Builder { fn seen_radix(&self) -> bool { self.radix.is_some() } fn seen_sign(&self) -> bool { self.sign.is_some() } - fn radix_value(&self) -> u8 { - let rx = match self.radix { - Some(r) => r, - None => Radix::Dec, - }; - rx as u8 - } + fn radix_value(&self) -> u8 { self.radix.unwrap_or(Radix::Dec) as u8 } + fn sign_value(&self) -> u8 { self.sign.unwrap_or(Sign::Pos) as u8 } } From e139cf0c6bb3b6db1376caeed157460d66fb03ff Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 4 Sep 2018 17:58:45 -0700 Subject: [PATCH 33/56] [lexer] Lex and discard whitespace Closes #12. --- lexer/src/lib.rs | 48 +++++++++++++++++++++++++--------- lexer/src/states/begin.rs | 11 +++++++- lexer/src/states/mod.rs | 3 +++ lexer/src/states/whitespace.rs | 30 +++++++++++++++++++++ lexer/src/token.rs | 8 +++++- 5 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 lexer/src/states/whitespace.rs diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index 449afc3..393a6de 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -3,7 +3,7 @@ */ use std::iter::Peekable; -use states::*; +use states::{Begin, Resume, StateResult}; mod chars; mod error; @@ -16,8 +16,12 @@ pub use token::{Lex, Token}; pub type Result = std::result::Result; pub struct Lexer where T: Iterator { + /// The input stream. input: Peekable, + + /// Current line number. line: usize, + /// Character offset from the start of the input. offset: usize, } @@ -32,15 +36,28 @@ impl Lexer where T: Iterator { fn next(&mut self) -> Option { let out = self.input.next(); + if let Some(c) = out { + self.update_offsets(c); + } out } - fn handle_whitespace(&mut self, c: char) { - if c == '\n' { - self.line += 1; - self.offset = 1; - } else { - self.offset += 1; + fn handle_error(&self, err: Error) { + panic!("{}:{}: {}", self.line, self.offset, err.msg()) + } + + fn prepare_offsets(&mut self) { } + + fn update_offsets(&mut self, c: char) { + self.offset += 1; + match c { + '\n' => { + self.line += 1; + self.offset = 0; + }, + _ => { + self.offset += 1; + }, } } } @@ -49,8 +66,10 @@ impl Iterator for Lexer where T: Iterator { type Item = Result; fn next(&mut self) -> Option { + self.prepare_offsets(); + let mut buffer = String::new(); - let mut state: Box = Box::new(states::Begin{}); + let mut state: Box = Box::new(Begin::new()); let mut out: Option = None; loop { let peek = self.input.peek().map(char::clone); @@ -62,7 +81,7 @@ impl Iterator for Lexer where T: Iterator { out = Some(Ok(Lex::new(token, &buffer, self.line, self.offset))); break; }, - Err(err) => panic!("{}:{}: {}", self.line, self.offset, err.msg()) + Err(err) => self.handle_error(err) }, Some(c) => { let result = state.lex(c); @@ -76,6 +95,13 @@ impl Iterator for Lexer where T: Iterator { self.next(); state = to; }, + StateResult::Discard(resume) => { + buffer.clear(); + state = Box::new(Begin::new()); + if resume == Resume::AtNext { + self.next(); + } + }, StateResult::Emit(token, resume) => { if resume == Resume::AtNext { buffer.push(c); @@ -84,9 +110,7 @@ impl Iterator for Lexer where T: Iterator { out = Some(Ok(Lex::new(token, &buffer, self.line, self.offset))); break; }, - StateResult::Fail(err) => { - panic!("{}:{}: {}", self.line, self.offset, err.msg()); - } + StateResult::Fail(err) => self.handle_error(err), } }, } diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index d8770ee..4231cc6 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -9,13 +9,22 @@ use states::{Resume, State, StateResult}; use states::id::IdSub; use states::hash::Hash; use states::number::{Builder, Digit}; +use states::whitespace::Whitespace; #[derive(Debug)] pub struct Begin; +impl Begin { + pub fn new() -> Begin { + Begin{} + } +} + impl State for Begin { fn lex(&mut self, c: char) -> StateResult { - if c.is_left_paren() { + if c.is_whitespace() { + StateResult::advance(Box::new(Whitespace::new())) + } else if c.is_left_paren() { StateResult::Emit(Token::LeftParen, Resume::AtNext) } else if c.is_right_paren() { StateResult::Emit(Token::RightParen, Resume::AtNext) diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index 33f0360..d4fd04f 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -11,6 +11,7 @@ mod bool; mod hash; mod number; mod id; +mod whitespace; pub use self::begin::Begin; @@ -20,6 +21,8 @@ pub enum StateResult { Continue, /// Consume the character, advance to the provided state. Advance { to: Box }, + /// Discard the input consumed to this point. Resume as specified. + Discard(Resume), /// Emit a Lex with the provided Token and the accumulated buffer. The Resume value indicates /// whether to revisit the current input character or advance to the next one. Emit(Token, Resume), diff --git a/lexer/src/states/whitespace.rs b/lexer/src/states/whitespace.rs new file mode 100644 index 0000000..556590d --- /dev/null +++ b/lexer/src/states/whitespace.rs @@ -0,0 +1,30 @@ +/* lexer/src/states/whitespace.rs + * Eryn Wells + */ + +use error::Error; +use states::{Resume, State, StateResult}; +use token::Token; + +#[derive(Debug)] +pub struct Whitespace; + +impl Whitespace { + pub fn new() -> Whitespace { + Whitespace{} + } +} + +impl State for Whitespace { + fn lex(&mut self, c: char) -> StateResult { + if c.is_whitespace() { + StateResult::Continue + } else { + StateResult::Discard(Resume::Here) + } + } + + fn none(&mut self) -> Result, Error> { + Ok(None) + } +} diff --git a/lexer/src/token.rs b/lexer/src/token.rs index c1008d0..0453169 100644 --- a/lexer/src/token.rs +++ b/lexer/src/token.rs @@ -2,7 +2,7 @@ * Eryn Wells */ -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq)] pub struct Lex { token: Token, value: String, @@ -32,3 +32,9 @@ impl Lex { pub fn token(&self) -> Token { self.token } pub fn value(&self) -> &str { self.value.as_str() } } + +impl PartialEq for Lex { + fn eq(&self, rhs: &Lex) -> bool { + self.token == rhs.token && self.value == rhs.value + } +} From c8de135d2fdb22370d2454926a1fca6677e98e64 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 5 Sep 2018 17:36:29 -0700 Subject: [PATCH 34/56] [lexer] Update failing integer lexing test after fixing #12 --- lexer/tests/single_tokens.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lexer/tests/single_tokens.rs b/lexer/tests/single_tokens.rs index ff77dfa..bb747f8 100644 --- a/lexer/tests/single_tokens.rs +++ b/lexer/tests/single_tokens.rs @@ -77,7 +77,6 @@ fn bool_with_spaces() { fn integer() { let mut lex = Lexer::new("23 42".chars()); assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(23), "23", 0, 0)))); - // TODO: Fix this once issue #12 is fixed. - assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(42), " 42", 0, 0)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(42), "42", 0, 0)))); assert_eq!(lex.next(), None); } From 8b96eb190c6ce292e7b7ee56796318cfdf6be2b4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 5 Sep 2018 22:17:18 -0700 Subject: [PATCH 35/56] [lexer] Continue on to Digit state after lexing Prefix characters --- lexer/src/states/number/prefix.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lexer/src/states/number/prefix.rs b/lexer/src/states/number/prefix.rs index 6003b81..df6fe87 100644 --- a/lexer/src/states/number/prefix.rs +++ b/lexer/src/states/number/prefix.rs @@ -6,6 +6,7 @@ use chars::Lexable; use error::Error; use states::{State, StateResult}; use states::number::{Builder, Radix, Exact}; +use states::number::digit::Digit; use states::number::sign::Sign; use token::Token; @@ -42,8 +43,10 @@ impl State for Prefix { fn lex(&mut self, c: char) -> StateResult { if c.is_hash() { StateResult::advance(Box::new(Hash(self.0))) - } else if let Some(sn) = Sign::with_char(self.0, c) { - StateResult::advance(Box::new(sn)) + } else if let Some(st) = Sign::with_char(self.0, c) { + StateResult::advance(Box::new(st)) + } else if let Some(st) = Digit::with_char(self.0, c) { + StateResult::advance(Box::new(st)) } else { StateResult::fail(Error::invalid_char(c)) } From 9365e51893da56fc9ecb59609e1fcc524e3e74c5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 5 Sep 2018 22:18:27 -0700 Subject: [PATCH 36/56] [lexer] Correctly set line/offset for emitted Lexes --- lexer/src/lib.rs | 17 +++++++++++------ lexer/src/token.rs | 7 +------ lexer/tests/single_tokens.rs | 14 +++++++++++--- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index 393a6de..2220eb6 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -49,16 +49,14 @@ impl Lexer where T: Iterator { fn prepare_offsets(&mut self) { } fn update_offsets(&mut self, c: char) { - self.offset += 1; match c { '\n' => { self.line += 1; self.offset = 0; }, - _ => { - self.offset += 1; - }, + _ => self.offset += 1 } + println!("incremented offsets {}:{}", self.line, self.offset); } } @@ -68,6 +66,10 @@ impl Iterator for Lexer where T: Iterator { fn next(&mut self) -> Option { self.prepare_offsets(); + let mut token_line = self.line; + let mut token_offset = self.offset; + println!("beginning token at {}:{}", token_line, token_offset); + let mut buffer = String::new(); let mut state: Box = Box::new(Begin::new()); let mut out: Option = None; @@ -78,7 +80,7 @@ impl Iterator for Lexer where T: Iterator { None => match state.none() { Ok(None) => break, Ok(Some(token)) => { - out = Some(Ok(Lex::new(token, &buffer, self.line, self.offset))); + out = Some(Ok(Lex::new(token, &buffer, token_line, token_offset))); break; }, Err(err) => self.handle_error(err) @@ -101,13 +103,15 @@ impl Iterator for Lexer where T: Iterator { if resume == Resume::AtNext { self.next(); } + token_line = self.line; + token_offset = self.offset; }, StateResult::Emit(token, resume) => { if resume == Resume::AtNext { buffer.push(c); self.next(); } - out = Some(Ok(Lex::new(token, &buffer, self.line, self.offset))); + out = Some(Ok(Lex::new(token, &buffer, token_line, token_offset))); break; }, StateResult::Fail(err) => self.handle_error(err), @@ -115,6 +119,7 @@ impl Iterator for Lexer where T: Iterator { }, } } + println!("emitting {:?}", out); out } } diff --git a/lexer/src/token.rs b/lexer/src/token.rs index 0453169..2cc77bb 100644 --- a/lexer/src/token.rs +++ b/lexer/src/token.rs @@ -2,7 +2,7 @@ * Eryn Wells */ -#[derive(Debug, Eq)] +#[derive(Debug, Eq, PartialEq)] pub struct Lex { token: Token, value: String, @@ -33,8 +33,3 @@ impl Lex { pub fn value(&self) -> &str { self.value.as_str() } } -impl PartialEq for Lex { - fn eq(&self, rhs: &Lex) -> bool { - self.token == rhs.token && self.value == rhs.value - } -} diff --git a/lexer/tests/single_tokens.rs b/lexer/tests/single_tokens.rs index bb747f8..0578934 100644 --- a/lexer/tests/single_tokens.rs +++ b/lexer/tests/single_tokens.rs @@ -67,16 +67,24 @@ fn bool_long_false() { #[test] fn bool_with_spaces() { // See issue #12 - let expected_lex = Lex::new(Token::Bool(false), "#f", 0, 0); + let expected_lex = Lex::new(Token::Bool(false), "#f", 0, 2); let mut lex = Lexer::new(" #f ".chars()); assert_eq!(lex.next(), Some(Ok(expected_lex))); assert_eq!(lex.next(), None); } #[test] -fn integer() { +fn simple_integers() { let mut lex = Lexer::new("23 42".chars()); assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(23), "23", 0, 0)))); - assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(42), "42", 0, 0)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(42), "42", 0, 3)))); + assert_eq!(lex.next(), None); +} + +#[test] +fn integers_in_alternative_bases() { + let mut lex = Lexer::new("#x2A #b11001".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0x2A), "#x2A", 0, 0)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0b11001), "#b11001", 0, 5)))); assert_eq!(lex.next(), None); } From 15e275513d762fd590d718e6e1b3e6bd3ee78034 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 6 Sep 2018 17:21:30 -0700 Subject: [PATCH 37/56] [lexer] Pass Builders around by reference instead of implicitly Copying --- lexer/src/states/begin.rs | 2 +- lexer/src/states/hash.rs | 2 +- lexer/src/states/number/digit.rs | 2 +- lexer/src/states/number/mod.rs | 2 +- lexer/src/states/number/prefix.rs | 16 +++++++++++----- lexer/src/states/number/sign.rs | 2 +- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index 4231cc6..c1bdec4 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -35,7 +35,7 @@ impl State for Begin { StateResult::advance(Box::new(IdSub{})) } else if c.is_hash() { StateResult::advance(Box::new(Hash::new())) - } else if let Some(st) = Digit::with_char(Builder::new(), c) { + } else if let Some(st) = Digit::with_char(&Builder::new(), c) { StateResult::advance(Box::new(st)) } else { StateResult::fail(Error::invalid_char(c)) diff --git a/lexer/src/states/hash.rs b/lexer/src/states/hash.rs index 80f7103..cd96cd3 100644 --- a/lexer/src/states/hash.rs +++ b/lexer/src/states/hash.rs @@ -28,7 +28,7 @@ impl State for Hash { StateResult::advance(Box::new(Bool::new(buf.as_str()))) }, c if c.is_radix() || c.is_exactness() => { - if let Some(st) = Prefix::with_char(Builder::new(), c) { + if let Some(st) = Prefix::with_char(&Builder::new(), c) { StateResult::advance(Box::new(st)) } else { StateResult::fail(Error::new(format!("invalid numeric prefix character: {}", c))) diff --git a/lexer/src/states/number/digit.rs b/lexer/src/states/number/digit.rs index 26e9493..d2823c0 100644 --- a/lexer/src/states/number/digit.rs +++ b/lexer/src/states/number/digit.rs @@ -15,7 +15,7 @@ impl Digit { Digit(b) } - pub fn with_char(b: Builder, c: char) -> Option { + pub fn with_char(b: &Builder, c: char) -> Option { let mut b = b.clone(); if !b.seen_radix() { b.push_radix(Radix::Dec); diff --git a/lexer/src/states/number/mod.rs b/lexer/src/states/number/mod.rs index 933fca4..9852460 100644 --- a/lexer/src/states/number/mod.rs +++ b/lexer/src/states/number/mod.rs @@ -20,7 +20,7 @@ pub enum Sign { Neg = -1, Pos = 1 } #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Exact { Yes, No } -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct Builder { radix: Option, sign: Option, diff --git a/lexer/src/states/number/prefix.rs b/lexer/src/states/number/prefix.rs index df6fe87..001abd2 100644 --- a/lexer/src/states/number/prefix.rs +++ b/lexer/src/states/number/prefix.rs @@ -18,7 +18,7 @@ impl Prefix { Prefix(b) } - pub fn with_char(b: Builder, c: char) -> Option { + pub fn with_char(b: &Builder, c: char) -> Option { if let Some(ex) = Exact::from(c) { if b.seen_exact() { return None; @@ -42,10 +42,10 @@ impl Prefix { impl State for Prefix { fn lex(&mut self, c: char) -> StateResult { if c.is_hash() { - StateResult::advance(Box::new(Hash(self.0))) - } else if let Some(st) = Sign::with_char(self.0, c) { + StateResult::advance(Box::new(Hash::new(&self.0))) + } else if let Some(st) = Sign::with_char(&self.0, c) { StateResult::advance(Box::new(st)) - } else if let Some(st) = Digit::with_char(self.0, c) { + } else if let Some(st) = Digit::with_char(&self.0, c) { StateResult::advance(Box::new(st)) } else { StateResult::fail(Error::invalid_char(c)) @@ -57,9 +57,15 @@ impl State for Prefix { } } +impl Hash { + fn new(b: &Builder) -> Hash { + Hash(b.clone()) + } +} + impl State for Hash { fn lex(&mut self, c: char) -> StateResult { - if let Some(st) = Prefix::with_char(self.0, c) { + if let Some(st) = Prefix::with_char(&self.0, c) { StateResult::advance(Box::new(st)) } else { StateResult::fail(Error::invalid_char(c)) diff --git a/lexer/src/states/number/sign.rs b/lexer/src/states/number/sign.rs index 11310aa..6e5d1a4 100644 --- a/lexer/src/states/number/sign.rs +++ b/lexer/src/states/number/sign.rs @@ -15,7 +15,7 @@ impl Sign { Sign(b) } - pub fn with_char(b: Builder, c: char) -> Option { + pub fn with_char(b: &Builder, c: char) -> Option { if !b.seen_sign() { match c { '+' => { From 34d612a832db08362b170c190721f04056bc057b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 6 Sep 2018 18:07:40 -0700 Subject: [PATCH 38/56] [lexer] Add state for finding Dots at the beginning of input This happens to be a valid token by itself and the beginning of a decimal number. --- lexer/src/chars.rs | 16 ++++++++++------ lexer/src/states/begin.rs | 9 ++++----- lexer/src/states/dot.rs | 33 +++++++++++++++++++++++++++++++++ lexer/src/states/mod.rs | 1 + lexer/src/token.rs | 1 + lexer/tests/single_tokens.rs | 7 +++++++ 6 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 lexer/src/states/dot.rs diff --git a/lexer/src/chars.rs b/lexer/src/chars.rs index f1507b1..ae17af1 100644 --- a/lexer/src/chars.rs +++ b/lexer/src/chars.rs @@ -3,18 +3,22 @@ */ pub trait Lexable { - fn is_left_paren(&self) -> bool; - fn is_right_paren(&self) -> bool; + fn is_dot(&self) -> bool; + fn is_exactness(&self) -> bool; + fn is_hash(&self) -> bool; + fn is_identifier_delimiter(&self) -> bool; fn is_identifier_initial(&self) -> bool; fn is_identifier_subsequent(&self) -> bool; - fn is_identifier_delimiter(&self) -> bool; - - fn is_exactness(&self) -> bool; + fn is_left_paren(&self) -> bool; fn is_radix(&self) -> bool; - fn is_hash(&self) -> bool; + fn is_right_paren(&self) -> bool; } impl Lexable for char { + fn is_dot(&self) -> bool { + *self == '.' + } + fn is_exactness(&self) -> bool { *self == 'i' || *self == 'e' } diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index c1bdec4..4daff23 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -6,13 +6,13 @@ use chars::Lexable; use error::Error; use token::Token; use states::{Resume, State, StateResult}; +use states::dot::Dot; use states::id::IdSub; use states::hash::Hash; use states::number::{Builder, Digit}; use states::whitespace::Whitespace; -#[derive(Debug)] -pub struct Begin; +#[derive(Debug)] pub struct Begin; impl Begin { pub fn new() -> Begin { @@ -28,9 +28,8 @@ impl State for Begin { StateResult::Emit(Token::LeftParen, Resume::AtNext) } else if c.is_right_paren() { StateResult::Emit(Token::RightParen, Resume::AtNext) - } else if c.is_whitespace() { - // TODO: Figure out some way to track newlines. - StateResult::Continue + } else if c.is_dot() { + StateResult::advance(Box::new(Dot::new())) } else if c.is_identifier_initial() { StateResult::advance(Box::new(IdSub{})) } else if c.is_hash() { diff --git a/lexer/src/states/dot.rs b/lexer/src/states/dot.rs new file mode 100644 index 0000000..45bdfc8 --- /dev/null +++ b/lexer/src/states/dot.rs @@ -0,0 +1,33 @@ +/* lexer/src/states/dot.rs + * Eryn Wells + */ + +use chars::Lexable; +use error::Error; +use states::{Resume, State, StateResult}; +use states::number::{Builder, Digit}; +use token::Token; + +#[derive(Debug)] pub struct Dot; + +impl Dot { + pub fn new() -> Dot { + Dot{} + } +} + +impl State for Dot { + fn lex(&mut self, c: char) -> StateResult { + if c.is_identifier_delimiter() { + StateResult::emit(Token::Dot, Resume::Here) + } else if let Some(st) = Digit::with_char(&Builder::new(), c) { + StateResult::advance(Box::new(st)) + } else { + StateResult::fail(Error::invalid_char(c)) + } + } + + fn none(&mut self) -> Result, Error> { + Ok(Some(Token::Dot)) + } +} diff --git a/lexer/src/states/mod.rs b/lexer/src/states/mod.rs index d4fd04f..5b8c788 100644 --- a/lexer/src/states/mod.rs +++ b/lexer/src/states/mod.rs @@ -8,6 +8,7 @@ use token::Token; mod begin; mod bool; +mod dot; mod hash; mod number; mod id; diff --git a/lexer/src/token.rs b/lexer/src/token.rs index 2cc77bb..57135b5 100644 --- a/lexer/src/token.rs +++ b/lexer/src/token.rs @@ -13,6 +13,7 @@ pub struct Lex { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Token { Bool(bool), + Dot, Num(i64), LeftParen, RightParen, diff --git a/lexer/tests/single_tokens.rs b/lexer/tests/single_tokens.rs index 0578934..acd7c3d 100644 --- a/lexer/tests/single_tokens.rs +++ b/lexer/tests/single_tokens.rs @@ -88,3 +88,10 @@ fn integers_in_alternative_bases() { assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0b11001), "#b11001", 0, 5)))); assert_eq!(lex.next(), None); } + +#[test] +fn dot() { + let mut lex = Lexer::new(".".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Dot, ".", 0, 0)))); + assert_eq!(lex.next(), None); +} From 45bc366a411a80d9f4a7c78d778dcdccb6faa0c0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 7 Sep 2018 06:59:13 -0700 Subject: [PATCH 39/56] [types] Add Frac type --- types/src/number/frac.rs | 87 ++++++++++++++++++++++++++++++++++ types/src/number/integer.rs | 12 +++-- types/src/number/mod.rs | 6 ++- types/src/number/rational.rs | 90 ------------------------------------ 4 files changed, 101 insertions(+), 94 deletions(-) create mode 100644 types/src/number/frac.rs delete mode 100644 types/src/number/rational.rs diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs new file mode 100644 index 0000000..a22df8e --- /dev/null +++ b/types/src/number/frac.rs @@ -0,0 +1,87 @@ +/* types/src/number/frac.rs + * Eryn Wells + */ + +use std::any::Any; +use std::fmt; +use number::{Int, Number}; +use object::{Obj, Object}; + +/// A fraction consisting of a numerator and denominator. +#[derive(Debug, Eq, PartialEq)] +pub struct Frac(pub i64, pub u64); + +impl Frac { +} + +impl Number for Frac { + fn as_int(&self) -> Option { + if self.1 == 1 { + Some(Int(self.0)) + } else { + None + } + } + + fn as_frac(&self) -> Option { + Some(Frac(self.0, self.1)) + } +} + +impl fmt::Display for Frac { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.0, self.1) + } +} + +impl Object for Frac { + fn as_any(&self) -> &Any { self } + fn as_num(&self) -> Option<&Number> { Some(self) } +} + +impl PartialEq for Frac { + fn eq<'a>(&self, rhs: &'a Obj) -> bool { + match rhs.obj().and_then(Object::as_num) { + Some(num) => self == num, + None => false + } + } +} + +impl<'a> PartialEq for Frac { + fn eq(&self, rhs: &(Number + 'a)) -> bool { + match rhs.as_frac() { + Some(rhs) => *self == rhs, + None => false + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn equal_fracs_are_equal() { + assert_eq!(Frac(3, 2), Frac(3, 2)); + assert_ne!(Frac(12, 4), Frac(9, 7)); + } + + #[test] + fn fracs_should_reduce_to_ints_where_possible() { + let rational_as_integer = Frac(3, 1).as_int(); + assert!(rational_as_integer.is_some()); + // Oh my god this line is so dumb. + } + + #[test] + fn fracs_should_not_reduce_to_ints_where_impossible() { + let rational_as_integer = Frac(3, 2).as_int(); + assert!(rational_as_integer.is_none()); + } + + #[test] + fn fracs_are_exact() { + assert!(Frac(4, 2).is_exact()); + } +} diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index 169e2a7..35b87fb 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -5,7 +5,7 @@ use std::any::Any; use std::fmt; use std::ops::{Add, Mul}; -use number::Number; +use number::{Frac, Number}; use object::{Obj, Object}; #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -44,7 +44,8 @@ impl Object for Int { } impl Number for Int { - fn as_int(&self) -> Option<&Int> { Some(self) } + fn as_int(&self) -> Option { Some(*self) } + fn as_frac(&self) -> Option { Some(Frac(self.0, 1)) } } impl Mul for Int { @@ -80,7 +81,7 @@ impl PartialEq for Int { impl<'a> PartialEq for Int { fn eq(&self, rhs: &(Number + 'a)) -> bool { match rhs.as_int() { - Some(rhs) => *self == *rhs, + Some(rhs) => *self == rhs, None => false } } @@ -107,4 +108,9 @@ mod tests { fn integers_are_exact() { assert!(Int(4).is_exact()); } + + #[test] + fn integers_add() { + assert_eq!(Int(4) + Int(8), Int(12)); + } } diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index e63aa46..ae775bc 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -12,16 +12,20 @@ //! be represented as an Integer. mod integer; +mod frac; use object::Object; pub use self::integer::Int; +pub use self::frac::Frac; pub trait Number: Object { /// Cast this Number to an Int if possible. - fn as_int(&self) -> Option<&Int> { None } + fn as_int(&self) -> Option { None } + /// Cast this Number to a Frac if possible. + fn as_frac(&self) -> Option { None } /// Return `true` if this Number is an exact representation of its value. fn is_exact(&self) -> bool { true } } diff --git a/types/src/number/rational.rs b/types/src/number/rational.rs deleted file mode 100644 index 0ddbef2..0000000 --- a/types/src/number/rational.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* types/src/number/rational.rs - * Eryn Wells - */ - -use std::any::Any; -use value::*; -use super::*; - -#[derive(Debug, Eq, PartialEq)] -pub struct Rational(pub Int, pub Int); - -impl Number for Rational { - fn convert_down(&self) -> Option> { - if self.1 == 1 { - Some(Box::new(Integer(self.0))) - } - else { - None - } - } - - fn is_exact(&self) -> bool { true } -} - -impl Value for Rational { - fn as_value(&self) -> &Value { self } -} - -impl IsBool for Rational { } -impl IsChar for Rational { } - -impl IsNumber for Rational { - fn is_rational(&self) -> bool { true } -} - -impl ValueEq for Rational { - fn eq(&self, other: &Value) -> bool { - other.as_any().downcast_ref::().map_or(false, |x| x == self) - } - - fn as_any(&self) -> &Any { self } -} - -#[cfg(test)] -mod tests { - use std::ops::Deref; - use number::*; - use value::*; - - #[test] - fn equal_rationals_are_equal() { - assert_eq!(Rational(3, 2), Rational(3, 2)); - assert_ne!(Rational(12, 4), Rational(9, 7)); - assert_eq!(Rational(4, 5).as_value(), Rational(4, 5).as_value()); - assert_ne!(Rational(5, 6).as_value(), Rational(7, 6).as_value()); - } - - #[test] - fn rationals_are_rationals() { - assert!(Rational(4, 3).is_complex()); - assert!(Rational(4, 3).is_real()); - assert!(Rational(4, 3).is_rational()); - assert!(!Rational(4, 3).is_integer()); - assert!(Rational(4, 3).is_number()); - assert!(!Rational(6, 8).is_char()); - assert!(!Rational(6, 9).is_bool()); - } - - #[test] - fn rationals_should_reduce_to_integers_where_possible() { - let rational_as_integer = Rational(3, 1).convert_down(); - assert!(rational_as_integer.is_some()); - // Oh my god this line is so dumb. - let rational_as_integer = rational_as_integer.unwrap(); - let rational_as_integer = rational_as_integer.as_value(); - assert_eq!(rational_as_integer.deref(), Integer(3).as_value()); - } - - #[test] - fn rationals_should_not_reduce_to_integers_where_impossible() { - let rational_as_integer = Rational(3, 2).convert_down(); - assert!(rational_as_integer.is_none()); - } - - #[test] - fn rationals_are_exact() { - assert!(Rational(4, 2).is_exact()); - assert!(!Rational(4, 2).is_inexact()); - } -} From 1aabce4f605cbb08e93f589c507afbad403c8bae Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 7 Sep 2018 08:14:33 -0700 Subject: [PATCH 40/56] [types] Add GCD and LCM to Int; implement Rem on Int --- types/src/number/arith.rs | 37 +++++++++++++++ types/src/number/frac.rs | 1 + types/src/number/integer.rs | 78 +++++++++++++++++++++++++++++- types/src/number/math.rs | 95 ------------------------------------- types/src/number/mod.rs | 1 + 5 files changed, 116 insertions(+), 96 deletions(-) create mode 100644 types/src/number/arith.rs delete mode 100644 types/src/number/math.rs diff --git a/types/src/number/arith.rs b/types/src/number/arith.rs new file mode 100644 index 0000000..69740b5 --- /dev/null +++ b/types/src/number/arith.rs @@ -0,0 +1,37 @@ +/* types/src/number/arith.rs + * Eryn Wells + */ + +pub trait GCD { + /// Find the greatest common divisor of `self` and another number. + fn gcd(self, other: Self) -> Self; +} + +pub trait LCM { + /// Find the least common multiple of `self` and another number. + fn lcm(self, other: Self) -> Self; +} + +//impl Rational for Int { +// fn to_rational(self) -> (Int, Int) { (self, 1) } +//} +// +//impl Rational for Flt { +// fn to_rational(self) -> (Int, Int) { +// // Convert the float to a fraction by iteratively multiplying by 10 until the fractional part of the float is 0.0. +// let whole_part = self.trunc(); +// let mut p = self.fract(); +// let mut q = 1.0; +// while p.fract() != 0.0 { +// p *= 10.0; +// q *= 10.0; +// } +// p += whole_part * q; +// +// // Integers from here down. Reduce the fraction before returning. +// let p = p as Int; +// let q = q as Int; +// let gcd = p.gcd(q); +// (p / gcd, q / gcd) +// } +//} diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs index a22df8e..b175e79 100644 --- a/types/src/number/frac.rs +++ b/types/src/number/frac.rs @@ -4,6 +4,7 @@ use std::any::Any; use std::fmt; +use std::ops::{Add, Mul}; use number::{Int, Number}; use object::{Obj, Object}; diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index 35b87fb..98ff532 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -4,7 +4,8 @@ use std::any::Any; use std::fmt; -use std::ops::{Add, Mul}; +use std::ops::{Add, Mul, Rem}; +use number::arith::{GCD, LCM}; use number::{Frac, Number}; use object::{Obj, Object}; @@ -38,6 +39,32 @@ impl fmt::Display for Int { } } +impl GCD for Int { + fn gcd(self, other: Int) -> Int { + let (mut a, mut b) = if self.0 > other.0 { + (self.0, other.0) + } else { + (other.0, self.0) + }; + while b != 0 { + let r = a % b; + a = b; + b = r; + } + Int(a) + } +} + +impl LCM for Int { + fn lcm(self, other: Int) -> Int { + if self.0 == 0 && other.0 == 0 { + Int(0) + } else { + Int(self.0 * other.0 / self.gcd(other).0) + } + } +} + impl Object for Int { fn as_any(&self) -> &Any { self } fn as_num(&self) -> Option<&Number> { Some(self) } @@ -87,6 +114,27 @@ impl<'a> PartialEq for Int { } } +impl Rem for Int { + type Output = Int; + fn rem(self, rhs: Self) -> Self::Output { + Int(self.0 % rhs.0) + } +} + +impl<'a> Rem for &'a Int { + type Output = Int; + fn rem(self, rhs: Int) -> Self::Output { + Int(self.0 % rhs.0) + } +} + +impl<'a, 'b> Rem<&'a Int> for &'b Int { + type Output = Int; + fn rem(self, rhs: &Int) -> Self::Output { + Int(self.0 % rhs.0) + } +} + #[cfg(test)] mod tests { use super::*; @@ -113,4 +161,32 @@ mod tests { fn integers_add() { assert_eq!(Int(4) + Int(8), Int(12)); } + + #[test] + fn integers_multiply() { + assert_eq!(Int(4) * Int(5), Int(20)); + } + + #[test] + fn integer_modulo_divide() { + assert_eq!(Int(20) % Int(5), Int(0)); + assert_eq!(Int(20) % Int(6), Int(2)); + } + + #[test] + fn finding_int_gcd() { + assert_eq!(Int(0), Int(0).gcd(Int(0))); + assert_eq!(Int(10), Int(10).gcd(Int(0))); + assert_eq!(Int(10), Int(0).gcd(Int(10))); + assert_eq!(Int(10), Int(10).gcd(Int(20))); + assert_eq!(Int(44), Int(2024).gcd(Int(748))); + } + + #[test] + fn finding_int_lcm() { + assert_eq!(Int(0), Int(0).lcm(Int(0))); + assert_eq!(Int(0), Int(10).lcm(Int(0))); + assert_eq!(Int(0), Int(10).lcm(Int(0))); + assert_eq!(Int(42), Int(21).lcm(Int(6))); + } } diff --git a/types/src/number/math.rs b/types/src/number/math.rs deleted file mode 100644 index a5ef5b7..0000000 --- a/types/src/number/math.rs +++ /dev/null @@ -1,95 +0,0 @@ -/* types/src/number/math.rs - * Eryn Wells - */ - -use number::{Int, Flt}; - -pub trait GCD { - /// Find the greatest common divisor of `self` and another number. - fn gcd(self, other: Self) -> Self; -} - -pub trait LCM { - /// Find the least common multiple of `self` and another number. - fn lcm(self, other: Self) -> Self; -} - -pub trait Rational { - /// Convert `self` into a rational number -- the quotient of two whole numbers. - fn to_rational(self) -> (Int, Int); -} - -impl GCD for Int { - fn gcd(self, other: Int) -> Int { - let (mut a, mut b) = if self > other { - (self, other) - } else { - (other, self) - }; - - while b != 0 { - let r = a % b; - a = b; - b = r; - } - - a - } -} - -impl LCM for Int { - fn lcm(self, other: Int) -> Int { - if self == 0 && other == 0 { - 0 - } - else { - self * other / self.gcd(other) - } - } -} - -impl Rational for Int { - fn to_rational(self) -> (Int, Int) { (self, 1) } -} - -impl Rational for Flt { - fn to_rational(self) -> (Int, Int) { - // Convert the float to a fraction by iteratively multiplying by 10 until the fractional part of the float is 0.0. - let whole_part = self.trunc(); - let mut p = self.fract(); - let mut q = 1.0; - while p.fract() != 0.0 { - p *= 10.0; - q *= 10.0; - } - p += whole_part * q; - - // Integers from here down. Reduce the fraction before returning. - let p = p as Int; - let q = q as Int; - let gcd = p.gcd(q); - (p / gcd, q / gcd) - } -} - -#[cfg(test)] -mod tests { - use super::{LCM, GCD}; - - #[test] - fn gcd_works() { - assert_eq!(0, 0.gcd(0)); - assert_eq!(10, 10.gcd(0)); - assert_eq!(10, 0.gcd(10)); - assert_eq!(10, 10.gcd(20)); - assert_eq!(44, 2024.gcd(748)); - } - - #[test] - fn lcm_works() { - assert_eq!(0, 0.lcm(0)); - assert_eq!(0, 10.lcm(0)); - assert_eq!(0, 10.lcm(0)); - assert_eq!(42, 21.lcm(6)); - } -} diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index ae775bc..f34cd8a 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -11,6 +11,7 @@ //! Integer can be cast as a Rational (by putting its value over 1), but a Rational like 1/2 cannot //! be represented as an Integer. +mod arith; mod integer; mod frac; From a5ed11ea77e6d4adeaa7b784df3e79f123483cb4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 7 Sep 2018 08:30:13 -0700 Subject: [PATCH 41/56] [types] More operations on Ints and some tests for Frac Implement Div on Int Make sure Fracs are reduced when they are produced, plus some error handling if you create a x/0 Frac --- types/src/number/frac.rs | 36 ++++++++++++++++++++++++------------ types/src/number/integer.rs | 27 ++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs index b175e79..ad0d746 100644 --- a/types/src/number/frac.rs +++ b/types/src/number/frac.rs @@ -5,28 +5,40 @@ use std::any::Any; use std::fmt; use std::ops::{Add, Mul}; +use number::arith::GCD; use number::{Int, Number}; use object::{Obj, Object}; /// A fraction consisting of a numerator and denominator. -#[derive(Debug, Eq, PartialEq)] -pub struct Frac(pub i64, pub u64); +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Frac(Int, Int); impl Frac { + pub fn new(p: Int, q: Int) -> Result { + if q == Int(0) { + // TODO: Return a more specific error about dividing by zero. + Err(()) + } else { + Ok(Frac(p, q).reduced()) + } + } + + fn reduced(self) -> Frac { + let gcd = self.0.gcd(self.1); + Frac(self.0 / gcd, self.1 / gcd) + } } impl Number for Frac { fn as_int(&self) -> Option { - if self.1 == 1 { - Some(Int(self.0)) + if self.1 == Int(1) { + Some(self.0) } else { None } } - fn as_frac(&self) -> Option { - Some(Frac(self.0, self.1)) - } + fn as_frac(&self) -> Option { Frac::new(self.0, self.1).ok() } } impl fmt::Display for Frac { @@ -64,25 +76,25 @@ mod tests { #[test] fn equal_fracs_are_equal() { - assert_eq!(Frac(3, 2), Frac(3, 2)); - assert_ne!(Frac(12, 4), Frac(9, 7)); + assert_eq!(Frac(Int(3), Int(2)), Frac(Int(3), Int(2))); + assert_ne!(Frac(Int(12), Int(4)), Frac(Int(9), Int(7))); } #[test] fn fracs_should_reduce_to_ints_where_possible() { - let rational_as_integer = Frac(3, 1).as_int(); + let rational_as_integer = Frac(Int(3), Int(1)).as_int(); assert!(rational_as_integer.is_some()); // Oh my god this line is so dumb. } #[test] fn fracs_should_not_reduce_to_ints_where_impossible() { - let rational_as_integer = Frac(3, 2).as_int(); + let rational_as_integer = Frac(Int(3), Int(2)).as_int(); assert!(rational_as_integer.is_none()); } #[test] fn fracs_are_exact() { - assert!(Frac(4, 2).is_exact()); + assert!(Frac(Int(4), Int(2)).is_exact()); } } diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index 98ff532..d075035 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -4,12 +4,12 @@ use std::any::Any; use std::fmt; -use std::ops::{Add, Mul, Rem}; +use std::ops::{Add, Div, Mul, Rem}; use number::arith::{GCD, LCM}; use number::{Frac, Number}; use object::{Obj, Object}; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Int(pub i64); impl Add for Int { @@ -39,6 +39,27 @@ impl fmt::Display for Int { } } +impl Div for Int { + type Output = Int; + fn div(self, rhs: Self) -> Self::Output { + Int(self.0 / rhs.0) + } +} + +impl<'a> Div for &'a Int { + type Output = Int; + fn div(self, rhs: Int) -> Self::Output { + Int(self.0 / rhs.0) + } +} + +impl<'a, 'b> Div<&'a Int> for &'b Int { + type Output = Int; + fn div(self, rhs: &Int) -> Self::Output { + Int(self.0 / rhs.0) + } +} + impl GCD for Int { fn gcd(self, other: Int) -> Int { let (mut a, mut b) = if self.0 > other.0 { @@ -72,7 +93,7 @@ impl Object for Int { impl Number for Int { fn as_int(&self) -> Option { Some(*self) } - fn as_frac(&self) -> Option { Some(Frac(self.0, 1)) } + fn as_frac(&self) -> Option { Frac::new(*self, Int(1)).ok() } } impl Mul for Int { From 5aca4cfbe44ff9082a2714721c3f94d0319eb5ba Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 7 Sep 2018 08:48:46 -0700 Subject: [PATCH 42/56] [types] Implement Add for Frac --- types/src/number/frac.rs | 48 +++++++++++++++++++++++++++++-------- types/src/number/integer.rs | 2 +- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs index ad0d746..0a49a1e 100644 --- a/types/src/number/frac.rs +++ b/types/src/number/frac.rs @@ -5,13 +5,13 @@ use std::any::Any; use std::fmt; use std::ops::{Add, Mul}; -use number::arith::GCD; +use number::arith::{GCD, LCM}; use number::{Int, Number}; use object::{Obj, Object}; /// A fraction consisting of a numerator and denominator. -#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Frac(Int, Int); +#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Frac { p: Int, q: Int } impl Frac { pub fn new(p: Int, q: Int) -> Result { @@ -19,31 +19,59 @@ impl Frac { // TODO: Return a more specific error about dividing by zero. Err(()) } else { - Ok(Frac(p, q).reduced()) + Ok(Frac{p, q}.reduced()) } } fn reduced(self) -> Frac { - let gcd = self.0.gcd(self.1); - Frac(self.0 / gcd, self.1 / gcd) + let gcd = self.p.gcd(self.q); + Frac { p: self.p / gcd, q: self.q / gcd } + } + + fn _add(self, rhs: Frac) -> Frac { + let lcm = self.q.lcm(rhs.q); + let p = self.p * lcm + rhs.p * lcm; + let q = self.q * lcm; + Frac::new(p, q).unwrap() } } impl Number for Frac { fn as_int(&self) -> Option { - if self.1 == Int(1) { - Some(self.0) + if self.q == Int(1) { + Some(self.p) } else { None } } - fn as_frac(&self) -> Option { Frac::new(self.0, self.1).ok() } + fn as_frac(&self) -> Option { Frac::new(self.p, self.q).ok() } +} + +impl Add for Frac { + type Output = Frac; + fn add(self, rhs: Self) -> Self::Output { + self._add(rhs) + } +} + +impl<'a> Add for &'a Frac { + type Output = Frac; + fn add(self, rhs: Frac) -> Self::Output { + self._add(rhs) + } +} + +impl<'a, 'b> Add<&'a Frac> for &'b Frac { + type Output = Frac; + fn add(self, rhs: &Frac) -> Self::Output { + self._add(*rhs) + } } impl fmt::Display for Frac { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}/{}", self.0, self.1) + write!(f, "{}/{}", self.p, self.q) } } diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index d075035..eb146b2 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -62,7 +62,7 @@ impl<'a, 'b> Div<&'a Int> for &'b Int { impl GCD for Int { fn gcd(self, other: Int) -> Int { - let (mut a, mut b) = if self.0 > other.0 { + let (mut a, mut b) = if self > other { (self.0, other.0) } else { (other.0, self.0) From d5e6913197ad872039cf021d7130085651787d9b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 7 Sep 2018 17:50:02 -0700 Subject: [PATCH 43/56] [types] Fix up tests for Frac type --- types/src/number/frac.rs | 96 +++++++++++++++++++++++++++---------- types/src/number/integer.rs | 17 ++++--- types/src/number/mod.rs | 1 + 3 files changed, 83 insertions(+), 31 deletions(-) diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs index 0a49a1e..8e56140 100644 --- a/types/src/number/frac.rs +++ b/types/src/number/frac.rs @@ -5,7 +5,7 @@ use std::any::Any; use std::fmt; use std::ops::{Add, Mul}; -use number::arith::{GCD, LCM}; +use number::arith::GCD; use number::{Int, Number}; use object::{Obj, Object}; @@ -15,7 +15,7 @@ pub struct Frac { p: Int, q: Int } impl Frac { pub fn new(p: Int, q: Int) -> Result { - if q == Int(0) { + if q.is_zero() { // TODO: Return a more specific error about dividing by zero. Err(()) } else { @@ -23,29 +23,26 @@ impl Frac { } } + pub fn from_ints(p: i64, q: i64) -> Result { + Frac::new(Int(p), Int(q)) + } + fn reduced(self) -> Frac { let gcd = self.p.gcd(self.q); Frac { p: self.p / gcd, q: self.q / gcd } } fn _add(self, rhs: Frac) -> Frac { - let lcm = self.q.lcm(rhs.q); - let p = self.p * lcm + rhs.p * lcm; - let q = self.q * lcm; - Frac::new(p, q).unwrap() - } -} - -impl Number for Frac { - fn as_int(&self) -> Option { - if self.q == Int(1) { - Some(self.p) - } else { - None - } + let p = self.p * rhs.q + rhs.p * self.q; + let q = self.q * rhs.q; + Frac{p,q}.reduced() } - fn as_frac(&self) -> Option { Frac::new(self.p, self.q).ok() } + fn _mul(self, rhs: Frac) -> Frac { + let p = self.p * rhs.p; + let q = self.q * rhs.q; + Frac{p,q}.reduced() + } } impl Add for Frac { @@ -75,6 +72,41 @@ impl fmt::Display for Frac { } } +impl Mul for Frac { + type Output = Frac; + fn mul(self, rhs: Self) -> Self::Output { + self._mul(rhs) + } +} + +impl<'a> Mul for &'a Frac { + type Output = Frac; + fn mul(self, rhs: Frac) -> Self::Output { + self._mul(rhs) + } +} + +impl<'a, 'b> Mul<&'a Frac> for &'b Frac { + type Output = Frac; + fn mul(self, rhs: &Frac) -> Self::Output { + self._mul(*rhs) + } +} + +impl Number for Frac { + fn as_int(&self) -> Option { + if self.q == Int(1) { + Some(self.p) + } else { + None + } + } + + fn as_frac(&self) -> Option { Frac::new(self.p, self.q).ok() } + + fn is_zero(&self) -> bool { self.p.is_zero() } +} + impl Object for Frac { fn as_any(&self) -> &Any { self } fn as_num(&self) -> Option<&Number> { Some(self) } @@ -100,29 +132,43 @@ impl<'a> PartialEq for Frac { #[cfg(test)] mod tests { + use number::Number; use super::*; + #[test] + fn fracs_with_zero_q_are_invalid() { + assert!(Frac::from_ints(3, 0).is_err()) + } + #[test] fn equal_fracs_are_equal() { - assert_eq!(Frac(Int(3), Int(2)), Frac(Int(3), Int(2))); - assert_ne!(Frac(Int(12), Int(4)), Frac(Int(9), Int(7))); + assert_eq!(Frac::from_ints(3, 2), Frac::from_ints(3, 2)); + assert_ne!(Frac::from_ints(12, 4), Frac::from_ints(9, 7)); } #[test] fn fracs_should_reduce_to_ints_where_possible() { - let rational_as_integer = Frac(Int(3), Int(1)).as_int(); - assert!(rational_as_integer.is_some()); - // Oh my god this line is so dumb. + let fr = Frac::from_ints(3, 1).unwrap(); + assert_eq!(fr.as_int(), Some(Int(3))); } #[test] fn fracs_should_not_reduce_to_ints_where_impossible() { - let rational_as_integer = Frac(Int(3), Int(2)).as_int(); - assert!(rational_as_integer.is_none()); + let fr = Frac::from_ints(3, 2).unwrap(); + assert_eq!(fr.as_int(), None); } #[test] fn fracs_are_exact() { - assert!(Frac(Int(4), Int(2)).is_exact()); + let fr = Frac::from_ints(4, 2).unwrap(); + assert!(fr.is_exact()); + } + + #[test] + fn fracs_can_add() { + let a = Frac::from_ints(5, 6).unwrap(); + let b = Frac::from_ints(2, 3).unwrap(); + let r = Frac::from_ints(3, 2).unwrap(); + assert_eq!(a + b, r); } } diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index eb146b2..21cb6c4 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -12,6 +12,10 @@ use object::{Obj, Object}; #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Int(pub i64); +impl Int { + pub fn zero() -> Int { Int(0) } +} + impl Add for Int { type Output = Int; fn add(self, rhs: Self) -> Self::Output { @@ -63,25 +67,25 @@ impl<'a, 'b> Div<&'a Int> for &'b Int { impl GCD for Int { fn gcd(self, other: Int) -> Int { let (mut a, mut b) = if self > other { - (self.0, other.0) + (self, other) } else { - (other.0, self.0) + (other, self) }; - while b != 0 { + while !b.is_zero() { let r = a % b; a = b; b = r; } - Int(a) + a } } impl LCM for Int { fn lcm(self, other: Int) -> Int { if self.0 == 0 && other.0 == 0 { - Int(0) + Int::zero() } else { - Int(self.0 * other.0 / self.gcd(other).0) + self * other / self.gcd(other) } } } @@ -94,6 +98,7 @@ impl Object for Int { impl Number for Int { fn as_int(&self) -> Option { Some(*self) } fn as_frac(&self) -> Option { Frac::new(*self, Int(1)).ok() } + fn is_zero(&self) -> bool { self.0 == 0 } } impl Mul for Int { diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index f34cd8a..8424942 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -29,6 +29,7 @@ pub trait Number: fn as_frac(&self) -> Option { None } /// Return `true` if this Number is an exact representation of its value. fn is_exact(&self) -> bool { true } + fn is_zero(&self) -> bool; } // TODO: Implement PartialEq myself cause there are some weird nuances to comparing numbers. From 30a876a3f9cf58f131775fc4c810cba482a6ad5a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 7 Sep 2018 17:54:24 -0700 Subject: [PATCH 44/56] [types] Add test for multiplying Fracs --- types/src/number/frac.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs index 8e56140..7ef8507 100644 --- a/types/src/number/frac.rs +++ b/types/src/number/frac.rs @@ -171,4 +171,12 @@ mod tests { let r = Frac::from_ints(3, 2).unwrap(); assert_eq!(a + b, r); } + + #[test] + fn fracs_can_multiply() { + let a = Frac::from_ints(4, 3).unwrap(); + let b = Frac::from_ints(3, 8).unwrap(); + let r = Frac::from_ints(1, 2).unwrap(); + assert_eq!(a * b, r); + } } From 061868d2c2432ac6b8bbe5707854a67eef89ff16 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 8 Sep 2018 11:16:53 -0700 Subject: [PATCH 45/56] [lexer] Add Quote token --- lexer/src/chars.rs | 5 +++++ lexer/src/states/begin.rs | 2 ++ lexer/src/token.rs | 5 +++-- lexer/tests/single_tokens.rs | 7 +++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lexer/src/chars.rs b/lexer/src/chars.rs index ae17af1..8aa2e2c 100644 --- a/lexer/src/chars.rs +++ b/lexer/src/chars.rs @@ -10,6 +10,7 @@ pub trait Lexable { fn is_identifier_initial(&self) -> bool; fn is_identifier_subsequent(&self) -> bool; fn is_left_paren(&self) -> bool; + fn is_quote(&self) -> bool; fn is_radix(&self) -> bool; fn is_right_paren(&self) -> bool; } @@ -43,6 +44,10 @@ impl Lexable for char { self.is_whitespace() || self.is_left_paren() || self.is_right_paren() } + fn is_quote(&self) -> bool { + *self == '\'' + } + fn is_radix(&self) -> bool { let radishes = &['b', 'd', 'o', 'x']; radishes.contains(self) diff --git a/lexer/src/states/begin.rs b/lexer/src/states/begin.rs index 4daff23..5f839d0 100644 --- a/lexer/src/states/begin.rs +++ b/lexer/src/states/begin.rs @@ -36,6 +36,8 @@ impl State for Begin { StateResult::advance(Box::new(Hash::new())) } else if let Some(st) = Digit::with_char(&Builder::new(), c) { StateResult::advance(Box::new(st)) + } else if c.is_quote() { + StateResult::Emit(Token::Quote, Resume::AtNext) } else { StateResult::fail(Error::invalid_char(c)) } diff --git a/lexer/src/token.rs b/lexer/src/token.rs index 57135b5..5d79222 100644 --- a/lexer/src/token.rs +++ b/lexer/src/token.rs @@ -14,10 +14,11 @@ pub struct Lex { pub enum Token { Bool(bool), Dot, - Num(i64), + Id, LeftParen, + Num(i64), + Quote, RightParen, - Id } impl Lex { diff --git a/lexer/tests/single_tokens.rs b/lexer/tests/single_tokens.rs index acd7c3d..f60481c 100644 --- a/lexer/tests/single_tokens.rs +++ b/lexer/tests/single_tokens.rs @@ -95,3 +95,10 @@ fn dot() { assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Dot, ".", 0, 0)))); assert_eq!(lex.next(), None); } + +#[test] +fn quote() { + let mut lex = Lexer::new("'".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Quote, "'", 0, 0)))); + assert_eq!(lex.next(), None); +} From 9f5165f0aa9d91470412208a437fcfff45a0bffd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 8 Sep 2018 11:24:46 -0700 Subject: [PATCH 46/56] [lexer] Add expression tests; add explicit_sign to identifier_initials --- lexer/src/chars.rs | 2 +- lexer/tests/expressions.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 lexer/tests/expressions.rs diff --git a/lexer/src/chars.rs b/lexer/src/chars.rs index 8aa2e2c..70be6c7 100644 --- a/lexer/src/chars.rs +++ b/lexer/src/chars.rs @@ -33,7 +33,7 @@ impl Lexable for char { } fn is_identifier_initial(&self) -> bool { - self.is_alphabetic() || self.is_special_initial() + self.is_alphabetic() || self.is_special_initial() || self.is_explicit_sign() } fn is_identifier_subsequent(&self) -> bool { diff --git a/lexer/tests/expressions.rs b/lexer/tests/expressions.rs new file mode 100644 index 0000000..1ee9712 --- /dev/null +++ b/lexer/tests/expressions.rs @@ -0,0 +1,29 @@ +/* lexer/tests/expressions.rs + * Eryn Wells + */ + +extern crate sibillexer; + +use sibillexer::{Lexer, Lex, Token}; + +#[test] +fn addition() { + let mut lex = Lexer::new("(+ 3 4)".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::LeftParen, "(", 0, 0)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Id, "+", 0, 1)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(3), "3", 0, 3)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(4), "4", 0, 5)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::RightParen, ")", 0, 6)))); + assert_eq!(lex.next(), None); +} + +#[test] +fn subtraction() { + let mut lex = Lexer::new("(- 3 4)".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::LeftParen, "(", 0, 0)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Id, "-", 0, 1)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(3), "3", 0, 3)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(4), "4", 0, 5)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::RightParen, ")", 0, 6)))); + assert_eq!(lex.next(), None); +} From fe271dfd8b87f2b800a0aec0ef2aee4188f9ce3b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 8 Sep 2018 16:05:33 -0700 Subject: [PATCH 47/56] [parser] We do not need the return value of pop() --- parser/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 1fa8032..1a957e3 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -53,7 +53,7 @@ impl Parser where T: Iterator { } fn pop_parser(&mut self) { - let popped = self.parsers.pop(); + self.parsers.pop(); println!("popped parser stack --> {:?}", self.parsers); } From c6696c4f8be50d995e5eb65131cdbcfb26016512 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 8 Sep 2018 16:06:23 -0700 Subject: [PATCH 48/56] [parser] Handle dotted pairs! --- parser/src/parsers/list.rs | 55 ++++++++++++++++++++++++++--------- parser/src/parsers/program.rs | 5 +++- parser/tests/lists.rs | 13 +++++++++ parser/tests/single_item.rs | 2 +- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/parser/src/parsers/list.rs b/parser/src/parsers/list.rs index f15f492..14ec700 100644 --- a/parser/src/parsers/list.rs +++ b/parser/src/parsers/list.rs @@ -10,20 +10,25 @@ use parsers::sym::SymParser; #[derive(Debug)] pub struct ListParser { - pairs: Option> + pairs: Option>, + waiting_for_final: bool, } impl ListParser { pub fn new() -> ListParser { - ListParser { pairs: None } + ListParser { + pairs: None, + waiting_for_final: false, + } } fn assemble(&mut self) -> Result { match self.pairs.take() { - Some(pairs) => { - let obj = pairs.into_iter().rfold(Obj::Null, |acc, mut pair| { + Some(mut pairs) => { + let last = pairs.last_mut().and_then(|p| Some(p.cdr.take())).unwrap_or(Obj::Null); + let obj = pairs.into_iter().rfold(last, |acc, mut pair| { pair.cdr = acc; - Obj::Ptr(Box::new(pair)) + Obj::new(pair) }); Ok(obj) }, @@ -38,7 +43,11 @@ impl NodeParser for ListParser { Token::Bool(_) => { let parser = BoolParser{}; NodeParseResult::Push { next: Box::new(parser) } - } + }, + Token::Dot => { + self.waiting_for_final = true; + NodeParseResult::Continue + }, Token::LeftParen => { match self.pairs { None => { @@ -57,6 +66,12 @@ impl NodeParser for ListParser { let next = Box::new(SymParser{}); NodeParseResult::Push { next } }, + Token::Num(n) => { + panic!("TODO: Handle numbrs."); + }, + Token::Quote => { + panic!("TODO: Handle quotes."); + }, Token::RightParen => { match self.pairs { None => { @@ -71,7 +86,7 @@ impl NodeParser for ListParser { } } } - } + }, } } @@ -81,12 +96,26 @@ impl NodeParser for ListParser { } fn subparser_completed(&mut self, obj: Obj) -> NodeParseResult { - if let Some(ref mut pairs) = self.pairs { - pairs.push(Pair::with_car(obj)); - NodeParseResult::Continue - } else { - let msg = format!("what happened here???"); - NodeParseResult::error(msg) + match self.pairs { + Some(ref mut pairs) if self.waiting_for_final => match pairs.last_mut() { + Some(ref mut last) => { + last.cdr = obj; + // Waiting for RightParen to close list. + NodeParseResult::Continue + }, + None => { + let msg = "Found dot before any pairs parsed".to_string(); + NodeParseResult::error(msg) + }, + }, + Some(ref mut pairs) => { + pairs.push(Pair::with_car(obj)); + NodeParseResult::Continue + }, + None => { + let msg = "While attempting to parse list, found token before opening paren".to_string(); + NodeParseResult::error(msg) + }, } } } diff --git a/parser/src/parsers/program.rs b/parser/src/parsers/program.rs index 6165468..3a8c610 100644 --- a/parser/src/parsers/program.rs +++ b/parser/src/parsers/program.rs @@ -37,7 +37,10 @@ impl NodeParser for ProgramParser { let parser = SymParser{}; let parser = Box::new(parser); NodeParseResult::Push { next: parser } - } + }, + _ => { + panic!("unhandled symbol"); + }, } } diff --git a/parser/tests/lists.rs b/parser/tests/lists.rs index e3ade69..e030d3e 100644 --- a/parser/tests/lists.rs +++ b/parser/tests/lists.rs @@ -32,3 +32,16 @@ fn list_of_four_tokens() { assert_eq!(parser.next(), Some(Ok(ex_list))); assert_eq!(parser.next(), None); } + +#[test] +fn single_dotted_pair() { + let tokens = vec![Ok(Lex::new(Token::LeftParen, "(", 0, 0)), + Ok(Lex::new(Token::Id, "ab", 0, 0)), + Ok(Lex::new(Token::Dot, ".", 0, 0)), + Ok(Lex::new(Token::Id, "cd", 0, 0)), + Ok(Lex::new(Token::RightParen, ")", 0, 0))].into_iter(); + let mut parser = Parser::new(tokens); + let ex_list = Obj::new(Pair::new(Obj::new(Sym::with_str("ab")), Obj::new(Sym::with_str("cd")))); + assert_eq!(parser.next(), Some(Ok(ex_list))); + assert_eq!(parser.next(), None); +} diff --git a/parser/tests/single_item.rs b/parser/tests/single_item.rs index 108fc37..795aaea 100644 --- a/parser/tests/single_item.rs +++ b/parser/tests/single_item.rs @@ -11,7 +11,7 @@ extern crate sibiltypes; use sibillexer::{Lex, Token}; use sibillexer::Result as LexerResult; use sibilparser::Parser; -use sibiltypes::{Bool, Obj, Pair, Sym}; +use sibiltypes::{Bool, Obj, Sym}; #[test] fn single_sym() { From 125fb08f436a8a0a48ca90809cf4e431d1540aa1 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 8 Sep 2018 16:06:47 -0700 Subject: [PATCH 49/56] [types] Add (back) trailing commas --- types/src/pair.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/src/pair.rs b/types/src/pair.rs index 28e4565..cb1412c 100644 --- a/types/src/pair.rs +++ b/types/src/pair.rs @@ -10,7 +10,7 @@ use object::Object; #[derive(Debug, PartialEq)] pub struct Pair { pub car: Obj, - pub cdr: Obj + pub cdr: Obj, } impl Pair { From 2120417f76fddf89e0f717f465ec31928d5de756 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 8 Sep 2018 16:09:44 -0700 Subject: [PATCH 50/56] [parser] Add test for longer dotted pair: (ab cd . ef) --- parser/tests/lists.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/parser/tests/lists.rs b/parser/tests/lists.rs index e030d3e..e990c5a 100644 --- a/parser/tests/lists.rs +++ b/parser/tests/lists.rs @@ -9,7 +9,6 @@ extern crate sibilparser; extern crate sibiltypes; use sibillexer::{Lex, Token}; -use sibillexer::Result as LexerResult; use sibilparser::Parser; use sibiltypes::{Obj, Pair, Sym}; @@ -45,3 +44,18 @@ fn single_dotted_pair() { assert_eq!(parser.next(), Some(Ok(ex_list))); assert_eq!(parser.next(), None); } + +#[test] +fn three_element_dotted_pair() { + let tokens = vec![Ok(Lex::new(Token::LeftParen, "(", 0, 0)), + Ok(Lex::new(Token::Id, "ab", 0, 0)), + Ok(Lex::new(Token::Id, "cd", 0, 0)), + Ok(Lex::new(Token::Dot, ".", 0, 0)), + Ok(Lex::new(Token::Id, "ef", 0, 0)), + Ok(Lex::new(Token::RightParen, ")", 0, 0))].into_iter(); + let mut parser = Parser::new(tokens); + let ex_list = Obj::new(Pair::new(Obj::new(Sym::with_str("ab")), Obj::new( + Pair::new(Obj::new(Sym::with_str("cd")), Obj::new(Sym::with_str("ef")))))); + assert_eq!(parser.next(), Some(Ok(ex_list))); + assert_eq!(parser.next(), None); +} From 107bb394b7613654659080748db47552fe9355eb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 8 Sep 2018 16:24:04 -0700 Subject: [PATCH 51/56] [lexer] Link sibiltypes --- lexer/Cargo.toml | 1 + lexer/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lexer/Cargo.toml b/lexer/Cargo.toml index 16d75ef..f8eb105 100644 --- a/lexer/Cargo.toml +++ b/lexer/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" authors = ["Eryn Wells "] [dependencies] +sibiltypes = { path = "../types" } diff --git a/lexer/src/lib.rs b/lexer/src/lib.rs index 2220eb6..dd7f050 100644 --- a/lexer/src/lib.rs +++ b/lexer/src/lib.rs @@ -2,6 +2,8 @@ * Eryn Wells */ +extern crate sibiltypes; + use std::iter::Peekable; use states::{Begin, Resume, StateResult}; From ce50ab5101a628cf87aac41c831ad50401c37b6d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 9 Sep 2018 08:58:14 -0700 Subject: [PATCH 52/56] [types] Add some tests for pairs Working on defining a pattern for naming tests so it is easier to see what is testing what. --- types/src/pair.rs | 40 +++++++++++++++++++++++++++++++++++++--- types/src/sym.rs | 2 +- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/types/src/pair.rs b/types/src/pair.rs index cb1412c..3927e69 100644 --- a/types/src/pair.rs +++ b/types/src/pair.rs @@ -4,8 +4,7 @@ use std::any::Any; use std::fmt; -use super::*; -use object::Object; +use object::{Obj, Object}; #[derive(Debug, PartialEq)] pub struct Pair { @@ -72,11 +71,46 @@ impl PartialEq for Pair { #[cfg(test)] mod tests { use super::Pair; + use object::Obj; + use sym::Sym; #[test] - fn empty_pairs_are_equal() { + fn eq_empty_pairs() { let a = Pair::empty(); let b = Pair::empty(); assert_eq!(a, b); } + + #[test] + fn display_empty_pair() { + let a = Pair::empty(); + let disp = format!("{}", a); + assert_eq!(disp, "(())"); + } + + #[test] + fn display_single_element_pair() { + let a = Pair::with_car(Obj::new(Sym::new("abc".to_string()))); + let disp = format!("{}", a); + assert_eq!(disp, "(abc)"); + } + + #[test] + fn display_dotted_pair() { + let car = Obj::new(Sym::new("abc".to_string())); + let cdr = Obj::new(Sym::new("def".to_string())); + let p = Pair::new(car, cdr); + let disp = format!("{}", p); + assert_eq!(disp, "(abc . def)"); + } + + #[test] + fn display_long_dotted_pair() { + let a = Obj::new(Sym::new("abc".to_string())); + let d = Obj::new(Sym::new("def".to_string())); + let g = Obj::new(Sym::new("ghi".to_string())); + let p = Pair::new(a, Obj::new(Pair::new(d, g))); + let disp = format!("{}", p); + assert_eq!(disp, "(abc def . ghi)"); + } } diff --git a/types/src/sym.rs b/types/src/sym.rs index c318e08..1310ccd 100644 --- a/types/src/sym.rs +++ b/types/src/sym.rs @@ -54,7 +54,7 @@ mod tests { use super::Sym; #[test] - fn syms_with_the_same_name_are_equal() { + fn eq_syms_with_same_name() { let a = Sym::with_str("abc"); let b = Sym::with_str("abc"); assert_eq!(a, b); From 1b26497d19514333207e60488f1f8b9f227699d1 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 9 Sep 2018 11:23:44 -0700 Subject: [PATCH 53/56] =?UTF-8?q?[types]=20Move=20arithmetic=20ops=20to=20?= =?UTF-8?q?a=20macro=20=F0=9F=A4=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sketch out Irr type and implement arithmetic types on it. --- types/src/number/arith.rs | 64 ++++++++++++++++++---------- types/src/number/integer.rs | 85 ------------------------------------- types/src/number/irr.rs | 62 +++++++++++++++++++++++++++ types/src/number/mod.rs | 85 +++---------------------------------- 4 files changed, 108 insertions(+), 188 deletions(-) create mode 100644 types/src/number/irr.rs diff --git a/types/src/number/arith.rs b/types/src/number/arith.rs index 69740b5..546901f 100644 --- a/types/src/number/arith.rs +++ b/types/src/number/arith.rs @@ -2,6 +2,9 @@ * Eryn Wells */ +use std::ops::{Add, Div, Mul, Sub, Rem}; +use number::{Int, Irr}; + pub trait GCD { /// Find the greatest common divisor of `self` and another number. fn gcd(self, other: Self) -> Self; @@ -12,26 +15,41 @@ pub trait LCM { fn lcm(self, other: Self) -> Self; } -//impl Rational for Int { -// fn to_rational(self) -> (Int, Int) { (self, 1) } -//} -// -//impl Rational for Flt { -// fn to_rational(self) -> (Int, Int) { -// // Convert the float to a fraction by iteratively multiplying by 10 until the fractional part of the float is 0.0. -// let whole_part = self.trunc(); -// let mut p = self.fract(); -// let mut q = 1.0; -// while p.fract() != 0.0 { -// p *= 10.0; -// q *= 10.0; -// } -// p += whole_part * q; -// -// // Integers from here down. Reduce the fraction before returning. -// let p = p as Int; -// let q = q as Int; -// let gcd = p.gcd(q); -// (p / gcd, q / gcd) -// } -//} +macro_rules! impl_newtype_arith_op { + ($id:ident, $opt:ident, $opm:ident, $op:tt) => { + impl $opt for $id { + type Output = $id; + #[inline] + fn $opm(self, rhs: $id) -> Self::Output { + $id(self.0 $op rhs.0) + } + } + impl<'a> $opt<$id> for &'a $id { + type Output = $id; + #[inline] + fn $opm(self, rhs: $id) -> Self::Output { + $id(self.0 $op rhs.0) + } + } + impl<'a, 'b> $opt<&'a $id> for &'b $id { + type Output = $id; + #[inline] + fn $opm(self, rhs: &$id) -> Self::Output { + $id(self.0 $op rhs.0) + } + } + } +} + +macro_rules! impl_newtype_arith { + ($($id:ident)*) => ($( + impl_newtype_arith_op!{$id, Add, add, +} + impl_newtype_arith_op!{$id, Div, div, /} + impl_newtype_arith_op!{$id, Mul, mul, *} + impl_newtype_arith_op!{$id, Sub, sub, -} + )*) +} + +impl_newtype_arith!{ Int Irr } +impl_newtype_arith_op!{Int, Rem, rem, %} +impl_newtype_arith_op!{Irr, Rem, rem, %} diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index 21cb6c4..96853ad 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -4,7 +4,6 @@ use std::any::Any; use std::fmt; -use std::ops::{Add, Div, Mul, Rem}; use number::arith::{GCD, LCM}; use number::{Frac, Number}; use object::{Obj, Object}; @@ -16,54 +15,12 @@ impl Int { pub fn zero() -> Int { Int(0) } } -impl Add for Int { - type Output = Int; - fn add(self, rhs: Self) -> Self::Output { - Int(self.0 + rhs.0) - } -} - -impl<'a> Add for &'a Int { - type Output = Int; - fn add(self, rhs: Int) -> Self::Output { - Int(self.0 + rhs.0) - } -} - -impl<'a, 'b> Add<&'a Int> for &'b Int { - type Output = Int; - fn add(self, rhs: &Int) -> Self::Output { - Int(self.0 + rhs.0) - } -} - impl fmt::Display for Int { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } -impl Div for Int { - type Output = Int; - fn div(self, rhs: Self) -> Self::Output { - Int(self.0 / rhs.0) - } -} - -impl<'a> Div for &'a Int { - type Output = Int; - fn div(self, rhs: Int) -> Self::Output { - Int(self.0 / rhs.0) - } -} - -impl<'a, 'b> Div<&'a Int> for &'b Int { - type Output = Int; - fn div(self, rhs: &Int) -> Self::Output { - Int(self.0 / rhs.0) - } -} - impl GCD for Int { fn gcd(self, other: Int) -> Int { let (mut a, mut b) = if self > other { @@ -101,27 +58,6 @@ impl Number for Int { fn is_zero(&self) -> bool { self.0 == 0 } } -impl Mul for Int { - type Output = Int; - fn mul(self, rhs: Self) -> Self::Output { - Int(self.0 * rhs.0) - } -} - -impl<'a> Mul for &'a Int { - type Output = Int; - fn mul(self, rhs: Int) -> Self::Output { - Int(self.0 * rhs.0) - } -} - -impl<'a, 'b> Mul<&'a Int> for &'b Int { - type Output = Int; - fn mul(self, rhs: &Int) -> Self::Output { - Int(self.0 * rhs.0) - } -} - impl PartialEq for Int { fn eq<'a>(&self, rhs: &'a Obj) -> bool { match rhs.obj().and_then(Object::as_num) { @@ -140,27 +76,6 @@ impl<'a> PartialEq for Int { } } -impl Rem for Int { - type Output = Int; - fn rem(self, rhs: Self) -> Self::Output { - Int(self.0 % rhs.0) - } -} - -impl<'a> Rem for &'a Int { - type Output = Int; - fn rem(self, rhs: Int) -> Self::Output { - Int(self.0 % rhs.0) - } -} - -impl<'a, 'b> Rem<&'a Int> for &'b Int { - type Output = Int; - fn rem(self, rhs: &Int) -> Self::Output { - Int(self.0 % rhs.0) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/types/src/number/irr.rs b/types/src/number/irr.rs new file mode 100644 index 0000000..be24df0 --- /dev/null +++ b/types/src/number/irr.rs @@ -0,0 +1,62 @@ +/* types/src/number/irr.rs + * Eryn Wells + */ + +use std::any::Any; +use std::fmt; +use number::{Frac, Int, Number}; +use object::{Obj, Object}; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Irr(pub f64); + +impl Irr { + pub fn zero() -> Irr { Irr(0.0) } +} + +impl fmt::Display for Irr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Object for Irr { + fn as_any(&self) -> &Any { self } + fn as_num(&self) -> Option<&Number> { Some(self) } +} + +impl Number for Irr { + fn as_int(&self) -> Option { + if self.0.trunc() == self.0 { + Some(Int(self.0.trunc() as i64)) + } else { + None + } + } + + fn as_frac(&self) -> Option { + if !self.0.is_infinite() && !self.0.is_nan() { + // TODO + None + } else { + None + } + } + + fn is_zero(&self) -> bool { self.0 == 0.0 } +} + +impl PartialEq for Irr { + fn eq<'a>(&self, rhs: &'a Obj) -> bool { + match rhs.obj().and_then(Object::as_num) { + Some(num) => self == num, + None => false + } + } +} + +impl<'a> PartialEq for Irr { + fn eq(&self, rhs: &(Number + 'a)) -> bool { + false + } +} diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index 8424942..bc03b72 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -12,13 +12,15 @@ //! be represented as an Integer. mod arith; -mod integer; mod frac; +mod integer; +mod irr; use object::Object; -pub use self::integer::Int; pub use self::frac::Frac; +pub use self::integer::Int; +pub use self::irr::Irr; pub trait Number: Object @@ -29,83 +31,6 @@ pub trait Number: fn as_frac(&self) -> Option { None } /// Return `true` if this Number is an exact representation of its value. fn is_exact(&self) -> bool { true } + /// Return `true` if this Number is equal to 0. fn is_zero(&self) -> bool; } - -// TODO: Implement PartialEq myself cause there are some weird nuances to comparing numbers. -//#[derive(Debug, PartialEq)] -//pub struct Number { -// real: Real, -// imag: Option, -// exact: Exact, -//} - -//impl Number { -// fn new(real: Real, imag: Option, exact: Exact) -> Number { -// Number { -// real: real.reduce(), -// imag: imag.map(|n| n.reduce()), -// exact: exact, -// } -// } -// -// pub fn from_int(value: Int, exact: Exact) -> Number { -// Number::new(Real::Integer(value), None, exact) -// } -// -// pub fn from_quotient(p: Int, q: Int, exact: Exact) -> Number { -// let real = if exact == Exact::Yes { -// // Make an exact rational an integer if possible. -// Real::Rational(p, q).demote() -// } -// else { -// // Make an inexact rational an irrational. -// Real::Rational(p, q).promote_once() -// }; -// Number::new(real, None, exact) -// } -// -// pub fn from_float(value: Flt, exact: Exact) -> Number { -// let real = if exact == Exact::Yes { -// // Attempt to demote irrationals. -// Real::Irrational(value).demote() -// } -// else { -// Real::Irrational(value) -// }; -// Number::new(real, None, exact) -// } -// -// pub fn is_exact(&self) -> bool { -// match self.exact { -// Exact::Yes => true, -// Exact::No => false, -// } -// } -//} -// -//impl fmt::Display for Number { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// write!(f, "{}", self.real).and_then( -// |r| self.imag.map(|i| write!(f, "{:+}i", i)).unwrap_or(Ok(r))) -// } -//} -// -//#[cfg(test)] -//mod tests { -// use super::Exact; -// use super::Number; -// use super::real::Real; -// -// #[test] -// fn exact_numbers_are_exact() { -// assert!(Number::from_int(3, Exact::Yes).is_exact()); -// assert!(!Number::from_int(3, Exact::No).is_exact()); -// } -// -// #[test] -// fn exact_irrationals_are_reduced() { -// let real = Real::Rational(3, 2); -// assert_eq!(Number::from_float(1.5, Exact::Yes), Number::new(real, None, Exact::Yes)); -// } -//} From e7273d6c9815c2d86a9150bb56eb2bc995bed32c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 9 Sep 2018 11:24:10 -0700 Subject: [PATCH 54/56] [types] Document fields of Frac --- types/src/number/frac.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs index 7ef8507..068c4f6 100644 --- a/types/src/number/frac.rs +++ b/types/src/number/frac.rs @@ -9,9 +9,14 @@ use number::arith::GCD; use number::{Int, Number}; use object::{Obj, Object}; -/// A fraction consisting of a numerator and denominator. +/// A fraction of two integers. #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Frac { p: Int, q: Int } +pub struct Frac { + /// The numerator. + p: Int, + /// The denominator. + q: Int +} impl Frac { pub fn new(p: Int, q: Int) -> Result { From 9d40cdd9958659acff66f053913e06a7e384fce2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 12 Sep 2018 08:12:06 -0700 Subject: [PATCH 55/56] [lexer] Move number integration tests to their own module --- lexer/tests/numbers.rs | 35 +++++++++++++++++++++++++++++++++++ lexer/tests/single_tokens.rs | 16 ---------------- 2 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 lexer/tests/numbers.rs diff --git a/lexer/tests/numbers.rs b/lexer/tests/numbers.rs new file mode 100644 index 0000000..2940a7c --- /dev/null +++ b/lexer/tests/numbers.rs @@ -0,0 +1,35 @@ +/* lexer/tests/numbers.rs + * Eryn Wells + */ + +//! Tests for lexing numbers. + +extern crate sibillexer; + +use sibillexer::{Lexer, Lex, Token}; + +#[test] +fn ints_simple() { + let mut lex = Lexer::new("23 42 0".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(23), "23", 0, 0)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(42), "42", 0, 3)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0), "0", 0, 6)))); + assert_eq!(lex.next(), None); +} + +#[test] +fn ints_negative() { + let mut lex = Lexer::new("-56".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(-56), "-56", 0, 0)))); + assert_eq!(lex.next(), None); +} + +#[test] +fn ints_alternative_bases() { + let mut lex = Lexer::new("#x2A #b11001 #o56 #d78".chars()); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0x2A), "#x2A", 0, 0)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0b11001), "#b11001", 0, 5)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0o56), "#o56", 0, 13)))); + assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(78), "#d78", 0, 18)))); + assert_eq!(lex.next(), None); +} diff --git a/lexer/tests/single_tokens.rs b/lexer/tests/single_tokens.rs index f60481c..819e203 100644 --- a/lexer/tests/single_tokens.rs +++ b/lexer/tests/single_tokens.rs @@ -73,22 +73,6 @@ fn bool_with_spaces() { assert_eq!(lex.next(), None); } -#[test] -fn simple_integers() { - let mut lex = Lexer::new("23 42".chars()); - assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(23), "23", 0, 0)))); - assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(42), "42", 0, 3)))); - assert_eq!(lex.next(), None); -} - -#[test] -fn integers_in_alternative_bases() { - let mut lex = Lexer::new("#x2A #b11001".chars()); - assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0x2A), "#x2A", 0, 0)))); - assert_eq!(lex.next(), Some(Ok(Lex::new(Token::Num(0b11001), "#b11001", 0, 5)))); - assert_eq!(lex.next(), None); -} - #[test] fn dot() { let mut lex = Lexer::new(".".chars()); From c07e6aa99b4f69d43a87b2d6d733baffd4b9df28 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 13 Sep 2018 18:08:14 -0700 Subject: [PATCH 56/56] [types] Make math ops macros even more generic -- implement them for cross types too! Still need to write tests for this though. --- types/src/number/arith.rs | 19 +++++++------------ types/src/number/frac.rs | 10 ++++++++++ types/src/number/integer.rs | 2 +- types/src/number/irr.rs | 16 +++++++++++++--- types/src/number/mod.rs | 4 ++-- 5 files changed, 33 insertions(+), 18 deletions(-) diff --git a/types/src/number/arith.rs b/types/src/number/arith.rs index 546901f..f33a6c6 100644 --- a/types/src/number/arith.rs +++ b/types/src/number/arith.rs @@ -3,7 +3,7 @@ */ use std::ops::{Add, Div, Mul, Sub, Rem}; -use number::{Int, Irr}; +use number::{Int, Irr, Number}; pub trait GCD { /// Find the greatest common divisor of `self` and another number. @@ -17,24 +17,19 @@ pub trait LCM { macro_rules! impl_newtype_arith_op { ($id:ident, $opt:ident, $opm:ident, $op:tt) => { - impl $opt for $id { + impl $opt for $id where T: Number + Into<$id> { type Output = $id; #[inline] - fn $opm(self, rhs: $id) -> Self::Output { + fn $opm(self, rhs: T) -> Self::Output { + let rhs: $id = rhs.into(); $id(self.0 $op rhs.0) } } - impl<'a> $opt<$id> for &'a $id { + impl<'a, T> $opt for &'a $id where T: Number + Into<$id> { type Output = $id; #[inline] - fn $opm(self, rhs: $id) -> Self::Output { - $id(self.0 $op rhs.0) - } - } - impl<'a, 'b> $opt<&'a $id> for &'b $id { - type Output = $id; - #[inline] - fn $opm(self, rhs: &$id) -> Self::Output { + fn $opm(self, rhs: T) -> Self::Output { + let rhs: $id = rhs.into(); $id(self.0 $op rhs.0) } } diff --git a/types/src/number/frac.rs b/types/src/number/frac.rs index 068c4f6..67aa476 100644 --- a/types/src/number/frac.rs +++ b/types/src/number/frac.rs @@ -32,6 +32,10 @@ impl Frac { Frac::new(Int(p), Int(q)) } + pub fn quotient(&self) -> f64 { + self.p.0 as f64 / self.q.0 as f64 + } + fn reduced(self) -> Frac { let gcd = self.p.gcd(self.q); Frac { p: self.p / gcd, q: self.q / gcd } @@ -77,6 +81,12 @@ impl fmt::Display for Frac { } } +impl From for Frac { + fn from(i: Int) -> Frac { + Frac{p: i, q: Int(1)} + } +} + impl Mul for Frac { type Output = Frac; fn mul(self, rhs: Self) -> Self::Output { diff --git a/types/src/number/integer.rs b/types/src/number/integer.rs index 96853ad..72e1fa5 100644 --- a/types/src/number/integer.rs +++ b/types/src/number/integer.rs @@ -5,8 +5,8 @@ use std::any::Any; use std::fmt; use number::arith::{GCD, LCM}; -use number::{Frac, Number}; use object::{Obj, Object}; +use super::{Frac, Number}; #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Int(pub i64); diff --git a/types/src/number/irr.rs b/types/src/number/irr.rs index be24df0..be19b8a 100644 --- a/types/src/number/irr.rs +++ b/types/src/number/irr.rs @@ -20,9 +20,14 @@ impl fmt::Display for Irr { } } -impl Object for Irr { - fn as_any(&self) -> &Any { self } - fn as_num(&self) -> Option<&Number> { Some(self) } +impl From for Irr { + fn from(i: Int) -> Irr { Irr(i.0 as f64) } +} + +impl From for Irr { + fn from(f: Frac) -> Irr { + Irr(f.quotient()) + } } impl Number for Irr { @@ -46,6 +51,11 @@ impl Number for Irr { fn is_zero(&self) -> bool { self.0 == 0.0 } } +impl Object for Irr { + fn as_any(&self) -> &Any { self } + fn as_num(&self) -> Option<&Number> { Some(self) } +} + impl PartialEq for Irr { fn eq<'a>(&self, rhs: &'a Obj) -> bool { match rhs.obj().and_then(Object::as_num) { diff --git a/types/src/number/mod.rs b/types/src/number/mod.rs index bc03b72..afacf0f 100644 --- a/types/src/number/mod.rs +++ b/types/src/number/mod.rs @@ -11,13 +11,13 @@ //! Integer can be cast as a Rational (by putting its value over 1), but a Rational like 1/2 cannot //! be represented as an Integer. +use object::Object; + mod arith; mod frac; mod integer; mod irr; -use object::Object; - pub use self::frac::Frac; pub use self::integer::Int; pub use self::irr::Irr;