From 72789dff9c52cf2c5ef7772b301a1d5cfd90e272 Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 2 Nov 2020 20:00:42 +0530 Subject: fixes for nix --- .gitignore | 3 + default.nix | 6 ++ nix/sources.json | 38 +++++++ nix/sources.nix | 134 +++++++++++++++++++++++++ shell.nix | 10 ++ src/error.rs | 29 ++++++ src/error/mod.rs | 29 ------ src/format.rs | 97 ++++++++++++++++++ src/format/mod.rs | 97 ------------------ src/lex.rs | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lex/mod.rs | 283 ---------------------------------------------------- src/main.rs | 3 +- src/parse.rs | 105 +++++++++++++++++++ src/parse/mod.rs | 105 ------------------- src/readline.rs | 121 ++++++++++++++++++++++ src/readline/mod.rs | 121 ---------------------- 16 files changed, 826 insertions(+), 637 deletions(-) create mode 100644 default.nix create mode 100644 nix/sources.json create mode 100644 nix/sources.nix create mode 100644 shell.nix create mode 100644 src/error.rs delete mode 100644 src/error/mod.rs create mode 100644 src/format.rs delete mode 100644 src/format/mod.rs create mode 100644 src/lex.rs delete mode 100644 src/lex/mod.rs create mode 100644 src/parse.rs delete mode 100644 src/parse/mod.rs create mode 100644 src/readline.rs delete mode 100644 src/readline/mod.rs diff --git a/.gitignore b/.gitignore index 6c22375..fd72c00 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ # ignore history used by rustyline history.txt ./.idea + +.envrc +result diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..a755ba1 --- /dev/null +++ b/default.nix @@ -0,0 +1,6 @@ +let + pkgs = import {}; + sources = import ./nix/sources.nix; + naersk = pkgs.callPackage sources.naersk {}; +in + naersk.buildPackage ./. diff --git a/nix/sources.json b/nix/sources.json new file mode 100644 index 0000000..17be642 --- /dev/null +++ b/nix/sources.json @@ -0,0 +1,38 @@ +{ + "naersk": { + "branch": "master", + "description": "Build rust crates in Nix. No configuration, no code generation, no IFD. Sandbox friendly.", + "homepage": "", + "owner": "nmattia", + "repo": "naersk", + "rev": "22b96210b2433228d42bce460f3befbdcfde7520", + "sha256": "0qa8jnw71qk1i1fgpydx1nv17cnvlydb9jhd6f7gvhglagm19b4v", + "type": "tarball", + "url": "https://github.com/nmattia/naersk/archive/22b96210b2433228d42bce460f3befbdcfde7520.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "niv": { + "branch": "master", + "description": "Easy dependency management for Nix projects", + "homepage": "https://github.com/nmattia/niv", + "owner": "nmattia", + "repo": "niv", + "rev": "9d35b9e4837ab88517210b1701127612c260eccf", + "sha256": "0q50xhnm8g2yfyakrh0nly4swyygxpi0a8cb9gp65wcakcgvzvdh", + "type": "tarball", + "url": "https://github.com/nmattia/niv/archive/9d35b9e4837ab88517210b1701127612c260eccf.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + }, + "nixpkgs": { + "branch": "nixos-19.09", + "description": "DEPRECATED! This is an obsolete, read-only mirror of the NixOS/nixpkgs repository.", + "homepage": "https://github.com/NixOS/nixpkgs", + "owner": "NixOS", + "repo": "nixpkgs-channels", + "rev": "75f4ba05c63be3f147bcc2f7bd4ba1f029cedcb1", + "sha256": "157c64220lf825ll4c0cxsdwg7cxqdx4z559fdp7kpz0g6p8fhhr", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs-channels/archive/75f4ba05c63be3f147bcc2f7bd4ba1f029cedcb1.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/nix/sources.nix b/nix/sources.nix new file mode 100644 index 0000000..8a725cb --- /dev/null +++ b/nix/sources.nix @@ -0,0 +1,134 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: spec: + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; } + else + pkgs.fetchurl { inherit (spec) url sha256; }; + + fetch_tarball = pkgs: spec: + if spec.builtin or true then + builtins_fetchTarball { inherit (spec) url sha256; } + else + pkgs.fetchzip { inherit (spec) url sha256; }; + + fetch_git = spec: + builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; + + fetch_builtin-tarball = spec: + builtins.trace + '' + WARNING: + The niv type "builtin-tarball" will soon be deprecated. You should + instead use `builtin = true`. + + $ niv modify -a type=tarball -a builtin=true + '' + builtins_fetchTarball { inherit (spec) url sha256; }; + + fetch_builtin-url = spec: + builtins.trace + '' + WARNING: + The niv type "builtin-url" will soon be deprecated. You should + instead use `builtin = true`. + + $ niv modify -a type=file -a builtin=true + '' + (builtins_fetchurl { inherit (spec) url sha256; }); + + # + # Various helpers + # + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs spec + else if spec.type == "tarball" then fetch_tarball pkgs spec + else if spec.type == "git" then fetch_git spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec + else if spec.type == "builtin-url" then fetch_builtin-url spec + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball { inherit url; } + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl { inherit url; } + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = fetch config.pkgs name spec; } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? ./sources.json + , sources ? builtins.fromJSON (builtins.readFile sourcesFile) + , pkgs ? mkPkgs sources + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..c0e1bb0 --- /dev/null +++ b/shell.nix @@ -0,0 +1,10 @@ +{ pkgs ? import {} }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + cargo + rustc + rustfmt + pkg-config + ]; +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..ec2b555 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,29 @@ +/* Copyright (C) 2019 Akshay Oppiliappan + * Refer to LICENCE for more information. + * */ + +#[derive(Debug)] +pub enum CalcError { + Math(Math), + Syntax(String), + Parser(String), +} + +#[derive(Debug)] +pub enum Math { + DivideByZero, + OutOfBounds, + UnknownBase, +} + +pub fn handler(e: CalcError) -> String { + match e { + CalcError::Math(math_err) => match math_err { + Math::DivideByZero => "Math Error: Divide by zero error!".to_string(), + Math::OutOfBounds => "Domain Error: Out of bounds!".to_string(), + Math::UnknownBase => "Base too large! Accepted ranges: 0 - 36".to_string(), + }, + CalcError::Syntax(details) => format!("Syntax Error: {}", details), + CalcError::Parser(details) => format!("Parser Error: {}", details), + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs deleted file mode 100644 index ec2b555..0000000 --- a/src/error/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright (C) 2019 Akshay Oppiliappan - * Refer to LICENCE for more information. - * */ - -#[derive(Debug)] -pub enum CalcError { - Math(Math), - Syntax(String), - Parser(String), -} - -#[derive(Debug)] -pub enum Math { - DivideByZero, - OutOfBounds, - UnknownBase, -} - -pub fn handler(e: CalcError) -> String { - match e { - CalcError::Math(math_err) => match math_err { - Math::DivideByZero => "Math Error: Divide by zero error!".to_string(), - Math::OutOfBounds => "Domain Error: Out of bounds!".to_string(), - Math::UnknownBase => "Base too large! Accepted ranges: 0 - 36".to_string(), - }, - CalcError::Syntax(details) => format!("Syntax Error: {}", details), - CalcError::Parser(details) => format!("Parser Error: {}", details), - } -} diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..45673d7 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,97 @@ +extern crate num; +use num::{BigInt, FromPrimitive, ToPrimitive}; + +use crate::error::{CalcError, Math}; +use crate::CONFIGURATION; + +pub fn autobalance_parens(input: &str) -> Result { + let mut balanced = String::from(input); + let mut left_parens = 0; + let mut right_parens = 0; + for letter in input.chars() { + if letter == '(' { + left_parens += 1; + } else if letter == ')' { + right_parens += 1; + } + } + + if left_parens > right_parens { + let extras = ")".repeat(left_parens - right_parens); + balanced.push_str(&extras[..]); + Ok(balanced) + } else if left_parens < right_parens { + Err(CalcError::Syntax("Mismatched parentheses!".into())) + } else { + Ok(balanced) + } +} + +fn radix_fmt(number: f64, obase: usize) -> Result { + if obase > 36 { + return Err(CalcError::Math(Math::UnknownBase)); + } + + match (number.is_infinite(), number.is_sign_positive()) { + (true, true) => return Ok("inf".to_string()), + (true, false) => return Ok("-inf".to_string()), + _ => (), + } + + if number.is_nan() { + return Ok("nan".to_string()); + } + + let table: Vec = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect(); + + // format integral part of float + let mut integral = BigInt::from_f64(number.abs().trunc()).unwrap(); + let mut obase_int = String::new(); + let obaseb = BigInt::from_usize(obase).unwrap(); + + while integral >= obaseb { + obase_int.push(table[(&integral % &obaseb).to_usize().unwrap()]); + integral /= &obaseb; + } + obase_int.push(table[integral.to_usize().unwrap()]); + if number.is_sign_negative() { + obase_int.push('-'); + } + let obase_int = obase_int.chars().rev().collect::(); + + // format fractional part of float + let mut fract = number.abs().fract(); + let mut obase_fract = String::new(); + let mut i = 0; + loop { + fract *= obase as f64; + obase_fract.push(table[fract.trunc() as usize]); + i += 1; + if fract.fract() == 0. || i >= CONFIGURATION.fix { + break; + } + fract = fract.fract(); + } + Ok(format!("{}.{}", obase_int, obase_fract)) +} + +fn thousand_sep(inp: &str) -> String { + let mut result_string = String::new(); + for (i, c) in inp.to_string().chars().rev().enumerate() { + if i % 3 == 0 && i != 0 && c.to_string() != "-" { + result_string.push(','); + } + result_string.push(c) + } + result_string.chars().rev().collect::() +} + +pub fn pprint(ans: f64) { + let ans_string = radix_fmt(ans, CONFIGURATION.base).unwrap(); + let ans_vector: Vec<&str> = ans_string.split('.').collect(); + match ans_vector.len() { + 1 => println!("{:>10}", thousand_sep(ans_vector[0])), + 2 => println!("{:>10}.{}", thousand_sep(ans_vector[0]), ans_vector[1]), + _ => unreachable!("N-nani?!"), + } +} diff --git a/src/format/mod.rs b/src/format/mod.rs deleted file mode 100644 index 45673d7..0000000 --- a/src/format/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -extern crate num; -use num::{BigInt, FromPrimitive, ToPrimitive}; - -use crate::error::{CalcError, Math}; -use crate::CONFIGURATION; - -pub fn autobalance_parens(input: &str) -> Result { - let mut balanced = String::from(input); - let mut left_parens = 0; - let mut right_parens = 0; - for letter in input.chars() { - if letter == '(' { - left_parens += 1; - } else if letter == ')' { - right_parens += 1; - } - } - - if left_parens > right_parens { - let extras = ")".repeat(left_parens - right_parens); - balanced.push_str(&extras[..]); - Ok(balanced) - } else if left_parens < right_parens { - Err(CalcError::Syntax("Mismatched parentheses!".into())) - } else { - Ok(balanced) - } -} - -fn radix_fmt(number: f64, obase: usize) -> Result { - if obase > 36 { - return Err(CalcError::Math(Math::UnknownBase)); - } - - match (number.is_infinite(), number.is_sign_positive()) { - (true, true) => return Ok("inf".to_string()), - (true, false) => return Ok("-inf".to_string()), - _ => (), - } - - if number.is_nan() { - return Ok("nan".to_string()); - } - - let table: Vec = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect(); - - // format integral part of float - let mut integral = BigInt::from_f64(number.abs().trunc()).unwrap(); - let mut obase_int = String::new(); - let obaseb = BigInt::from_usize(obase).unwrap(); - - while integral >= obaseb { - obase_int.push(table[(&integral % &obaseb).to_usize().unwrap()]); - integral /= &obaseb; - } - obase_int.push(table[integral.to_usize().unwrap()]); - if number.is_sign_negative() { - obase_int.push('-'); - } - let obase_int = obase_int.chars().rev().collect::(); - - // format fractional part of float - let mut fract = number.abs().fract(); - let mut obase_fract = String::new(); - let mut i = 0; - loop { - fract *= obase as f64; - obase_fract.push(table[fract.trunc() as usize]); - i += 1; - if fract.fract() == 0. || i >= CONFIGURATION.fix { - break; - } - fract = fract.fract(); - } - Ok(format!("{}.{}", obase_int, obase_fract)) -} - -fn thousand_sep(inp: &str) -> String { - let mut result_string = String::new(); - for (i, c) in inp.to_string().chars().rev().enumerate() { - if i % 3 == 0 && i != 0 && c.to_string() != "-" { - result_string.push(','); - } - result_string.push(c) - } - result_string.chars().rev().collect::() -} - -pub fn pprint(ans: f64) { - let ans_string = radix_fmt(ans, CONFIGURATION.base).unwrap(); - let ans_vector: Vec<&str> = ans_string.split('.').collect(); - match ans_vector.len() { - 1 => println!("{:>10}", thousand_sep(ans_vector[0])), - 2 => println!("{:>10}.{}", thousand_sep(ans_vector[0]), ans_vector[1]), - _ => unreachable!("N-nani?!"), - } -} diff --git a/src/lex.rs b/src/lex.rs new file mode 100644 index 0000000..a2de98a --- /dev/null +++ b/src/lex.rs @@ -0,0 +1,282 @@ +/* Copyright (C) 2019 Akshay Oppiliappan + * Refer to LICENCE for more information. + * */ + +use lazy_static::lazy_static; +use std::collections::HashMap; + +use crate::error::{CalcError, Math}; +use crate::CONFIGURATION; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Operator { + token: char, + pub operation: fn(f64, f64) -> f64, + pub precedence: u8, + pub is_left_associative: bool, +} + +impl Operator { + fn token_from_op( + token: char, + operation: fn(f64, f64) -> f64, + precedence: u8, + is_left_associative: bool, + ) -> Token { + Token::Operator(Operator { + token, + operation, + precedence, + is_left_associative, + }) + } + pub fn operate(self, x: f64, y: f64) -> Result { + if self.token == '/' && y == 0. { + Err(CalcError::Math(Math::DivideByZero)) + } else { + Ok((self.operation)(x, y)) + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Function { + token: String, + relation: fn(f64) -> f64, +} + +impl Function { + fn token_from_fn(token: String, relation: fn(f64) -> f64) -> Token { + Token::Function(Function { token, relation }) + } + pub fn apply(self, arg: f64) -> Result { + let result = (self.relation)(arg); + if !result.is_finite() { + Err(CalcError::Math(Math::OutOfBounds)) + } else { + Ok(result) + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Token { + Operator(Operator), + Num(f64), + Function(Function), + LParen, + RParen, +} + +lazy_static! { + static ref CONSTANTS: HashMap<&'static str, Token> = { + let mut m = HashMap::new(); + m.insert("e", Token::Num(std::f64::consts::E)); + m.insert("pi", Token::Num(std::f64::consts::PI)); + m + }; + + static ref FUNCTIONS: HashMap<&'static str, Token> = { + let mut m = HashMap::new(); + m.insert("sin", Function::token_from_fn("sin".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).sin())); + m.insert("cos", Function::token_from_fn("cos".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).cos())); + m.insert("tan", Function::token_from_fn("tan".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).tan())); + m.insert("csc", Function::token_from_fn("csc".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).sin().recip())); + m.insert("sec", Function::token_from_fn("sec".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).cos().recip())); + m.insert("cot", Function::token_from_fn("cot".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).tan().recip())); + m.insert("sinh", Function::token_from_fn("sinh".into(), |x| x.sinh())); + m.insert("cosh", Function::token_from_fn("cosh".into(), |x| x.cosh())); + m.insert("tanh", Function::token_from_fn("tanh".into(), |x| x.tanh())); + m.insert("ln", Function::token_from_fn("ln".into(), |x| x.ln())); + m.insert("log", Function::token_from_fn("log".into(), |x| x.log10())); + m.insert("sqrt", Function::token_from_fn("sqrt".into(), |x| x.sqrt())); + m.insert("ceil", Function::token_from_fn("ceil".into(), |x| x.ceil())); + m.insert("floor", Function::token_from_fn("floor".into(), |x| x.floor())); + m.insert("rad", Function::token_from_fn("rad".into(), |x| x.to_radians())); + m.insert("deg", Function::token_from_fn("deg".into(), |x| x.to_degrees())); + m.insert("abs", Function::token_from_fn("abs".into(), |x| x.abs())); + m.insert("asin", Function::token_from_fn("asin".into(), |x| x.asin())); + m.insert("acos", Function::token_from_fn("acos".into(), |x| x.acos())); + m.insert("atan", Function::token_from_fn("atan".into(), |x| x.atan())); + m.insert("acsc", Function::token_from_fn("acsc".into(), |x| (1./x).asin())); + m.insert("asec", Function::token_from_fn("asec".into(), |x| (1./x).acos())); + m.insert("acot", Function::token_from_fn("acot".into(), |x| (1./x).atan())); + // single arg function s can be added here + m + }; + + static ref OPERATORS: HashMap = { + let mut m = HashMap::new(); + m.insert('+', Operator::token_from_op('+', |x, y| x + y, 2, true)); + m.insert('-', Operator::token_from_op('-', |x, y| x - y, 2, true)); + m.insert('*', Operator::token_from_op('*', |x, y| x * y, 3, true)); + m.insert('/', Operator::token_from_op('/', |x, y| x / y, 3, true)); + m.insert('%', Operator::token_from_op('%', |x, y| x % y, 3, true)); + m.insert('^', Operator::token_from_op('^', |x, y| x.powf(y) , 4, false)); + m.insert('!', Operator::token_from_op('!', |x, _| factorial(x) , 4, true)); + m + }; +} + +fn factorial(n: f64) -> f64 { + n.signum() * (1..=n.abs() as u64).fold(1, |p, n| p * n) as f64 +} + +pub fn lexer(input: &str, prev_ans: Option) -> Result, CalcError> { + let mut num_vec: String = String::new(); + let mut char_vec: String = String::new(); + let mut result: Vec = vec![]; + let mut last_char_is_op = true; + + let mut chars = input.chars().peekable(); + while let Some(mut letter) = chars.next() { + match letter { + '0'..='9' | '.' => { + if !char_vec.is_empty() { + if FUNCTIONS.get(&char_vec[..]).is_some() { + return Err(CalcError::Syntax(format!( + "Function '{}' expected parentheses", + char_vec + ))); + } else { + return Err(CalcError::Syntax(format!( + "Unexpected character '{}'", + char_vec + ))); + } + } + num_vec.push(letter); + last_char_is_op = false; + } + '_' => { + if prev_ans.is_none() { + return Err(CalcError::Syntax("No previous answer!".into())); + } + if !char_vec.is_empty() { + if FUNCTIONS.get(&char_vec[..]).is_some() { + return Err(CalcError::Syntax(format!( + "Function '{}' expected parentheses", + char_vec + ))); + } else { + return Err(CalcError::Syntax(format!( + "Unexpected character '{}'", + char_vec + ))); + } + } + let parse_num = num_vec.parse::().ok(); + if let Some(x) = parse_num { + result.push(Token::Num(x)); + result.push(OPERATORS.get(&'*').unwrap().clone()); + num_vec.clear(); + } + last_char_is_op = false; + result.push(Token::Num(prev_ans.unwrap())); + } + 'a'..='z' | 'A'..='Z' => { + let parse_num = num_vec.parse::().ok(); + if let Some(x) = parse_num { + result.push(Token::Num(x)); + result.push(OPERATORS.get(&'*').unwrap().clone()); + num_vec.clear(); + } + char_vec.push(letter); + last_char_is_op = false; + } + '+' | '-' => { + let op_token = OPERATORS.get(&letter).unwrap().clone(); + let parse_num = num_vec.parse::().ok(); + if !last_char_is_op { + if let Some(x) = parse_num { + result.push(Token::Num(x)); + num_vec.clear(); + last_char_is_op = true; + } else if let Some(token) = CONSTANTS.get(&char_vec[..]) { + result.push(token.clone()); + char_vec.clear(); + last_char_is_op = true; + } + result.push(op_token); + } else if last_char_is_op { + result.push(Token::LParen); + result.push(Token::Num( + (letter.to_string() + "1").parse::().unwrap(), + )); + result.push(Token::RParen); + result.push(Operator::token_from_op('*', |x, y| x * y, 10, true)); + } + } + '/' | '*' | '%' | '^' | '!' => { + drain_stack(&mut num_vec, &mut char_vec, &mut result); + if letter == '*' && chars.peek() == Some(&'*') { + // Accept `**` operator as meaning `^` (exponentation). + let _ = chars.next(); + letter = '^'; + } + let operator_token: Token = OPERATORS.get(&letter).unwrap().clone(); + result.push(operator_token); + last_char_is_op = true; + if letter == '!' { + result.push(Token::Num(1.)); + last_char_is_op = false; + } + } + '(' => { + if !char_vec.is_empty() { + if let Some(res) = FUNCTIONS.get(&char_vec[..]) { + result.push(res.clone()); + } else { + return Err(CalcError::Syntax(format!( + "Unknown function '{}'", + char_vec + ))); + } + char_vec.clear(); + } else { + let parse_num = num_vec.parse::().ok(); + if let Some(x) = parse_num { + result.push(Token::Num(x)); + result.push(OPERATORS.get(&'*').unwrap().clone()); + num_vec.clear(); + } + } + + if let Some(Token::RParen) = result.last() { + result.push(OPERATORS.get(&'*').unwrap().clone()); + } + result.push(Token::LParen); + last_char_is_op = true; + } + ')' => { + drain_stack(&mut num_vec, &mut char_vec, &mut result); + result.push(Token::RParen); + last_char_is_op = false; + } + ' ' => {} + _ => return Err(CalcError::Syntax(format!("Unexpected token: '{}'", letter))), + } + } + // println!("{:?}", result); + drain_stack(&mut num_vec, &mut char_vec, &mut result); + Ok(result) +} + +fn drain_stack(num_vec: &mut String, char_vec: &mut String, result: &mut Vec) { + let parse_num = num_vec.parse::().ok(); + if let Some(x) = parse_num { + result.push(Token::Num(x)); + num_vec.clear(); + } else if let Some(token) = CONSTANTS.get(&char_vec[..]) { + result.push(token.clone()); + char_vec.clear(); + } +} + +fn is_radian_mode(x: f64, is_radian: bool) -> f64 { + if is_radian { + x + } else { + x.to_radians() + } +} diff --git a/src/lex/mod.rs b/src/lex/mod.rs deleted file mode 100644 index 3222c12..0000000 --- a/src/lex/mod.rs +++ /dev/null @@ -1,283 +0,0 @@ -/* Copyright (C) 2019 Akshay Oppiliappan - * Refer to LICENCE for more information. - * */ - -use lazy_static::lazy_static; -use std::collections::HashMap; - -use crate::CONFIGURATION; - -use crate::error::{CalcError, Math}; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct Operator { - token: char, - pub operation: fn(f64, f64) -> f64, - pub precedence: u8, - pub is_left_associative: bool, -} - -impl Operator { - fn token_from_op( - token: char, - operation: fn(f64, f64) -> f64, - precedence: u8, - is_left_associative: bool, - ) -> Token { - Token::Operator(Operator { - token, - operation, - precedence, - is_left_associative, - }) - } - pub fn operate(self, x: f64, y: f64) -> Result { - if self.token == '/' && y == 0. { - Err(CalcError::Math(Math::DivideByZero)) - } else { - Ok((self.operation)(x, y)) - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Function { - token: String, - relation: fn(f64) -> f64, -} - -impl Function { - fn token_from_fn(token: String, relation: fn(f64) -> f64) -> Token { - Token::Function(Function { token, relation }) - } - pub fn apply(self, arg: f64) -> Result { - let result = (self.relation)(arg); - if !result.is_finite() { - Err(CalcError::Math(Math::OutOfBounds)) - } else { - Ok(result) - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Token { - Operator(Operator), - Num(f64), - Function(Function), - LParen, - RParen, -} - -lazy_static! { - static ref CONSTANTS: HashMap<&'static str, Token> = { - let mut m = HashMap::new(); - m.insert("e", Token::Num(std::f64::consts::E)); - m.insert("pi", Token::Num(std::f64::consts::PI)); - m - }; - - static ref FUNCTIONS: HashMap<&'static str, Token> = { - let mut m = HashMap::new(); - m.insert("sin", Function::token_from_fn("sin".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).sin())); - m.insert("cos", Function::token_from_fn("cos".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).cos())); - m.insert("tan", Function::token_from_fn("tan".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).tan())); - m.insert("csc", Function::token_from_fn("csc".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).sin().recip())); - m.insert("sec", Function::token_from_fn("sec".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).cos().recip())); - m.insert("cot", Function::token_from_fn("cot".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).tan().recip())); - m.insert("sinh", Function::token_from_fn("sinh".into(), |x| x.sinh())); - m.insert("cosh", Function::token_from_fn("cosh".into(), |x| x.cosh())); - m.insert("tanh", Function::token_from_fn("tanh".into(), |x| x.tanh())); - m.insert("ln", Function::token_from_fn("ln".into(), |x| x.ln())); - m.insert("log", Function::token_from_fn("log".into(), |x| x.log10())); - m.insert("sqrt", Function::token_from_fn("sqrt".into(), |x| x.sqrt())); - m.insert("ceil", Function::token_from_fn("ceil".into(), |x| x.ceil())); - m.insert("floor", Function::token_from_fn("floor".into(), |x| x.floor())); - m.insert("rad", Function::token_from_fn("rad".into(), |x| x.to_radians())); - m.insert("deg", Function::token_from_fn("deg".into(), |x| x.to_degrees())); - m.insert("abs", Function::token_from_fn("abs".into(), |x| x.abs())); - m.insert("asin", Function::token_from_fn("asin".into(), |x| x.asin())); - m.insert("acos", Function::token_from_fn("acos".into(), |x| x.acos())); - m.insert("atan", Function::token_from_fn("atan".into(), |x| x.atan())); - m.insert("acsc", Function::token_from_fn("acsc".into(), |x| (1./x).asin())); - m.insert("asec", Function::token_from_fn("asec".into(), |x| (1./x).acos())); - m.insert("acot", Function::token_from_fn("acot".into(), |x| (1./x).atan())); - // single arg function s can be added here - m - }; - - static ref OPERATORS: HashMap = { - let mut m = HashMap::new(); - m.insert('+', Operator::token_from_op('+', |x, y| x + y, 2, true)); - m.insert('-', Operator::token_from_op('-', |x, y| x - y, 2, true)); - m.insert('*', Operator::token_from_op('*', |x, y| x * y, 3, true)); - m.insert('/', Operator::token_from_op('/', |x, y| x / y, 3, true)); - m.insert('%', Operator::token_from_op('%', |x, y| x % y, 3, true)); - m.insert('^', Operator::token_from_op('^', |x, y| x.powf(y) , 4, false)); - m.insert('!', Operator::token_from_op('!', |x, _| factorial(x) , 4, true)); - m - }; -} - -fn factorial(n: f64) -> f64 { - n.signum() * (1..=n.abs() as u64).fold(1, |p, n| p * n) as f64 -} - -pub fn lexer(input: &str, prev_ans: Option) -> Result, CalcError> { - let mut num_vec: String = String::new(); - let mut char_vec: String = String::new(); - let mut result: Vec = vec![]; - let mut last_char_is_op = true; - - let mut chars = input.chars().peekable(); - while let Some(mut letter) = chars.next() { - match letter { - '0'..='9' | '.' => { - if !char_vec.is_empty() { - if FUNCTIONS.get(&char_vec[..]).is_some() { - return Err(CalcError::Syntax(format!( - "Function '{}' expected parentheses", - char_vec - ))); - } else { - return Err(CalcError::Syntax(format!( - "Unexpected character '{}'", - char_vec - ))); - } - } - num_vec.push(letter); - last_char_is_op = false; - } - '_' => { - if prev_ans.is_none() { - return Err(CalcError::Syntax("No previous answer!".into())); - } - if !char_vec.is_empty() { - if FUNCTIONS.get(&char_vec[..]).is_some() { - return Err(CalcError::Syntax(format!( - "Function '{}' expected parentheses", - char_vec - ))); - } else { - return Err(CalcError::Syntax(format!( - "Unexpected character '{}'", - char_vec - ))); - } - } - let parse_num = num_vec.parse::().ok(); - if let Some(x) = parse_num { - result.push(Token::Num(x)); - result.push(OPERATORS.get(&'*').unwrap().clone()); - num_vec.clear(); - } - last_char_is_op = false; - result.push(Token::Num(prev_ans.unwrap())); - } - 'a'..='z' | 'A'..='Z' => { - let parse_num = num_vec.parse::().ok(); - if let Some(x) = parse_num { - result.push(Token::Num(x)); - result.push(OPERATORS.get(&'*').unwrap().clone()); - num_vec.clear(); - } - char_vec.push(letter); - last_char_is_op = false; - } - '+' | '-' => { - let op_token = OPERATORS.get(&letter).unwrap().clone(); - let parse_num = num_vec.parse::().ok(); - if !last_char_is_op { - if let Some(x) = parse_num { - result.push(Token::Num(x)); - num_vec.clear(); - last_char_is_op = true; - } else if let Some(token) = CONSTANTS.get(&char_vec[..]) { - result.push(token.clone()); - char_vec.clear(); - last_char_is_op = true; - } - result.push(op_token); - } else if last_char_is_op { - result.push(Token::LParen); - result.push(Token::Num( - (letter.to_string() + "1").parse::().unwrap(), - )); - result.push(Token::RParen); - result.push(Operator::token_from_op('*', |x, y| x * y, 10, true)); - } - } - '/' | '*' | '%' | '^' | '!' => { - drain_stack(&mut num_vec, &mut char_vec, &mut result); - if letter == '*' && chars.peek() == Some(&'*') { - // Accept `**` operator as meaning `^` (exponentation). - let _ = chars.next(); - letter = '^'; - } - let operator_token: Token = OPERATORS.get(&letter).unwrap().clone(); - result.push(operator_token); - last_char_is_op = true; - if letter == '!' { - result.push(Token::Num(1.)); - last_char_is_op = false; - } - } - '(' => { - if !char_vec.is_empty() { - if let Some(res) = FUNCTIONS.get(&char_vec[..]) { - result.push(res.clone()); - } else { - return Err(CalcError::Syntax(format!( - "Unknown function '{}'", - char_vec - ))); - } - char_vec.clear(); - } else { - let parse_num = num_vec.parse::().ok(); - if let Some(x) = parse_num { - result.push(Token::Num(x)); - result.push(OPERATORS.get(&'*').unwrap().clone()); - num_vec.clear(); - } - } - - if let Some(Token::RParen) = result.last() { - result.push(OPERATORS.get(&'*').unwrap().clone()); - } - result.push(Token::LParen); - last_char_is_op = true; - } - ')' => { - drain_stack(&mut num_vec, &mut char_vec, &mut result); - result.push(Token::RParen); - last_char_is_op = false; - } - ' ' => {} - _ => return Err(CalcError::Syntax(format!("Unexpected token: '{}'", letter))), - } - } - // println!("{:?}", result); - drain_stack(&mut num_vec, &mut char_vec, &mut result); - Ok(result) -} - -fn drain_stack(num_vec: &mut String, char_vec: &mut String, result: &mut Vec) { - let parse_num = num_vec.parse::().ok(); - if let Some(x) = parse_num { - result.push(Token::Num(x)); - num_vec.clear(); - } else if let Some(token) = CONSTANTS.get(&char_vec[..]) { - result.push(token.clone()); - char_vec.clear(); - } -} - -fn is_radian_mode(x: f64, is_radian: bool) -> f64 { - if is_radian { - x - } else { - x.to_radians() - } -} diff --git a/src/main.rs b/src/main.rs index bc22ce2..5540da9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -192,8 +192,7 @@ fn parse_arguments() -> Configuration { } pub fn eval_math_expression(input: &str, prev_ans: Option) -> Result { - let input = input.trim(); - let input = input.replace(" ", ""); + let input = input.trim().replace(" ", ""); if input.is_empty() { return Ok(0.); } diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..aa8bd4b --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,105 @@ +/* Copyright (C) 2019 Akshay Oppiliappan + * Refer to LICENCE for more information. + * */ + +use crate::error::CalcError; +use crate::lex::Token; + +pub fn to_postfix(tokens: Vec) -> Result, CalcError> { + let mut postfixed: Vec = vec![]; + let mut op_stack: Vec = vec![]; + for token in tokens { + match token { + Token::Num(_) => { + postfixed.push(token); + } + Token::Function(_) => { + op_stack.push(token); + } + Token::Operator(current_op) => { + while let Some(top_op) = op_stack.last() { + match top_op { + Token::LParen => { + break; + } + Token::Operator(x) => { + let tp = x.precedence; + let cp = current_op.precedence; + if tp > cp || (tp == cp && x.is_left_associative) { + postfixed.push(op_stack.pop().unwrap()); + } else { + break; + } + } + Token::Function(_) => { + postfixed.push(op_stack.pop().unwrap()); + } + _ => { + unreachable!(); + } + } + } + op_stack.push(token); + } + Token::LParen => { + op_stack.push(token); + } + Token::RParen => { + let mut push_until_paren: bool = false; + while let Some(token) = op_stack.pop() { + if token == Token::LParen { + push_until_paren = true; + break; + } + postfixed.push(token) + } + if !push_until_paren { + return Err(CalcError::Syntax("Mismatched parentheses!".into())); + } + } + } + } + while let Some(op) = op_stack.pop() { + postfixed.push(op); + } + Ok(postfixed) +} + +pub fn eval_postfix(postfixed: Vec) -> Result { + let mut num_stack: Vec = vec![]; + for token in postfixed { + match token { + Token::Num(n) => { + num_stack.push(n); + } + Token::Operator(op) => { + if let Some(n2) = num_stack.pop() { + if let Some(n1) = num_stack.pop() { + num_stack.push(op.operate(n1, n2)?); + } else { + return Err(CalcError::Parser( + "Too many operators, Too little operands".to_string(), + )); + } + } else { + return Err(CalcError::Parser( + "Too many operators, Too little operands".to_string(), + )); + } + } + Token::Function(funct) => { + if let Some(arg) = num_stack.pop() { + num_stack.push(funct.apply(arg)?) + } + } + _ => unreachable!("wut"), + } + } + if num_stack.len() == 1 { + Ok(num_stack.pop().unwrap()) + } else { + Err(CalcError::Parser( + "Too many operators, Too little operands".to_string(), + )) + } +} diff --git a/src/parse/mod.rs b/src/parse/mod.rs deleted file mode 100644 index aa8bd4b..0000000 --- a/src/parse/mod.rs +++ /dev/null @@ -1,105 +0,0 @@ -/* Copyright (C) 2019 Akshay Oppiliappan - * Refer to LICENCE for more information. - * */ - -use crate::error::CalcError; -use crate::lex::Token; - -pub fn to_postfix(tokens: Vec) -> Result, CalcError> { - let mut postfixed: Vec = vec![]; - let mut op_stack: Vec = vec![]; - for token in tokens { - match token { - Token::Num(_) => { - postfixed.push(token); - } - Token::Function(_) => { - op_stack.push(token); - } - Token::Operator(current_op) => { - while let Some(top_op) = op_stack.last() { - match top_op { - Token::LParen => { - break; - } - Token::Operator(x) => { - let tp = x.precedence; - let cp = current_op.precedence; - if tp > cp || (tp == cp && x.is_left_associative) { - postfixed.push(op_stack.pop().unwrap()); - } else { - break; - } - } - Token::Function(_) => { - postfixed.push(op_stack.pop().unwrap()); - } - _ => { - unreachable!(); - } - } - } - op_stack.push(token); - } - Token::LParen => { - op_stack.push(token); - } - Token::RParen => { - let mut push_until_paren: bool = false; - while let Some(token) = op_stack.pop() { - if token == Token::LParen { - push_until_paren = true; - break; - } - postfixed.push(token) - } - if !push_until_paren { - return Err(CalcError::Syntax("Mismatched parentheses!".into())); - } - } - } - } - while let Some(op) = op_stack.pop() { - postfixed.push(op); - } - Ok(postfixed) -} - -pub fn eval_postfix(postfixed: Vec) -> Result { - let mut num_stack: Vec = vec![]; - for token in postfixed { - match token { - Token::Num(n) => { - num_stack.push(n); - } - Token::Operator(op) => { - if let Some(n2) = num_stack.pop() { - if let Some(n1) = num_stack.pop() { - num_stack.push(op.operate(n1, n2)?); - } else { - return Err(CalcError::Parser( - "Too many operators, Too little operands".to_string(), - )); - } - } else { - return Err(CalcError::Parser( - "Too many operators, Too little operands".to_string(), - )); - } - } - Token::Function(funct) => { - if let Some(arg) = num_stack.pop() { - num_stack.push(funct.apply(arg)?) - } - } - _ => unreachable!("wut"), - } - } - if num_stack.len() == 1 { - Ok(num_stack.pop().unwrap()) - } else { - Err(CalcError::Parser( - "Too many operators, Too little operands".to_string(), - )) - } -} diff --git a/src/readline.rs b/src/readline.rs new file mode 100644 index 0000000..ea195ee --- /dev/null +++ b/src/readline.rs @@ -0,0 +1,121 @@ +use std::borrow::Cow::{self, Owned}; +use std::path::PathBuf; + +use rustyline::completion::{Completer, FilenameCompleter, Pair}; +use rustyline::config::{Builder, ColorMode, CompletionType, EditMode}; +use rustyline::error::ReadlineError; +use rustyline::highlight::Highlighter; +use rustyline::hint::{Hinter, HistoryHinter}; +use rustyline::{Context, Editor, Helper}; + +use directories::ProjectDirs; + +use regex::Regex; + +use crate::eval_math_expression; + +pub struct RLHelper { + completer: FilenameCompleter, + highlighter: LineHighlighter, + hinter: HistoryHinter, +} + +struct LineHighlighter {} +impl Highlighter for LineHighlighter { + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + Owned(format!("\x1b[90m{}\x1b[0m", hint)) + } + fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> { + use std::fs::OpenOptions; + use std::io::{BufRead, BufReader}; + + let eva_dirs = ProjectDirs::from("com", "NerdyPepper", "eva").unwrap(); + let eva_data_dir = eva_dirs.data_dir(); + let mut previous_ans_path = PathBuf::from(eva_data_dir); + previous_ans_path.push("previous_ans.txt"); + + let file = OpenOptions::new() + .create(true) + .read(true) + .append(true) + .open(&previous_ans_path) + .unwrap(); + + let rdr = BufReader::new(file); + let lines = rdr.lines().map(|l| l.unwrap()); + let prev_ans = match lines.last() { + Some(val) => val.parse::().ok(), + None => None, + }; + let op = eval_math_expression(line, prev_ans); + match op { + Ok(_) => { + let constants = ["e", "pi"]; + let functions = [ + "sin", "cos", "tan", "csc", "sec", "cot", "sinh", "cosh", "tanh", "ln", "log", + "sqrt", "ceil", "floor", "rad", "deg", "abs", "asin", "acos", "atan", "acsc", + "asec", "acot", + ]; + let ops = Regex::new(r"(?P[\+-/\*%\^!])").unwrap(); + let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); + + for c in &constants { + coloured = coloured.replace(c, &format!("\x1b[33m{}\x1b[0m", c)); + } + for f in &functions { + coloured = coloured.replace(f, &format!("\x1b[34m{}\x1b[0m", f)); + } + Owned(coloured) + } + Err(_) => Owned(format!("\x1b[31m{}\x1b[0m", line)), + } + } +} + +impl Highlighter for RLHelper { + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + self.highlighter.highlight_hint(hint) + } + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + self.highlighter.highlight(line, pos) + } +} + +impl Completer for RLHelper { + type Candidate = Pair; + fn complete( + &self, + line: &str, + pos: usize, + ctx: &Context<'_>, + ) -> Result<(usize, Vec), ReadlineError> { + self.completer.complete(line, pos, ctx) + } +} + +impl Hinter for RLHelper { + fn hint(&self, line: &str, a: usize, b: &Context) -> Option { + self.hinter.hint(line, a, b) + } +} + +impl Helper for RLHelper {} + +pub fn create_readline() -> Editor { + let config_builder = Builder::new(); + let config = config_builder + .color_mode(ColorMode::Enabled) + .edit_mode(EditMode::Emacs) + .history_ignore_space(true) + .completion_type(CompletionType::Circular) + .max_history_size(1000) + .build(); + let mut rl = Editor::with_config(config); + let h = RLHelper { + completer: FilenameCompleter::new(), + highlighter: LineHighlighter {}, + hinter: HistoryHinter {}, + }; + rl.set_helper(Some(h)); + rl +} diff --git a/src/readline/mod.rs b/src/readline/mod.rs deleted file mode 100644 index ea195ee..0000000 --- a/src/readline/mod.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::borrow::Cow::{self, Owned}; -use std::path::PathBuf; - -use rustyline::completion::{Completer, FilenameCompleter, Pair}; -use rustyline::config::{Builder, ColorMode, CompletionType, EditMode}; -use rustyline::error::ReadlineError; -use rustyline::highlight::Highlighter; -use rustyline::hint::{Hinter, HistoryHinter}; -use rustyline::{Context, Editor, Helper}; - -use directories::ProjectDirs; - -use regex::Regex; - -use crate::eval_math_expression; - -pub struct RLHelper { - completer: FilenameCompleter, - highlighter: LineHighlighter, - hinter: HistoryHinter, -} - -struct LineHighlighter {} -impl Highlighter for LineHighlighter { - fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { - Owned(format!("\x1b[90m{}\x1b[0m", hint)) - } - fn highlight<'l>(&self, line: &'l str, _: usize) -> Cow<'l, str> { - use std::fs::OpenOptions; - use std::io::{BufRead, BufReader}; - - let eva_dirs = ProjectDirs::from("com", "NerdyPepper", "eva").unwrap(); - let eva_data_dir = eva_dirs.data_dir(); - let mut previous_ans_path = PathBuf::from(eva_data_dir); - previous_ans_path.push("previous_ans.txt"); - - let file = OpenOptions::new() - .create(true) - .read(true) - .append(true) - .open(&previous_ans_path) - .unwrap(); - - let rdr = BufReader::new(file); - let lines = rdr.lines().map(|l| l.unwrap()); - let prev_ans = match lines.last() { - Some(val) => val.parse::().ok(), - None => None, - }; - let op = eval_math_expression(line, prev_ans); - match op { - Ok(_) => { - let constants = ["e", "pi"]; - let functions = [ - "sin", "cos", "tan", "csc", "sec", "cot", "sinh", "cosh", "tanh", "ln", "log", - "sqrt", "ceil", "floor", "rad", "deg", "abs", "asin", "acos", "atan", "acsc", - "asec", "acot", - ]; - let ops = Regex::new(r"(?P[\+-/\*%\^!])").unwrap(); - let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); - - for c in &constants { - coloured = coloured.replace(c, &format!("\x1b[33m{}\x1b[0m", c)); - } - for f in &functions { - coloured = coloured.replace(f, &format!("\x1b[34m{}\x1b[0m", f)); - } - Owned(coloured) - } - Err(_) => Owned(format!("\x1b[31m{}\x1b[0m", line)), - } - } -} - -impl Highlighter for RLHelper { - fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { - self.highlighter.highlight_hint(hint) - } - fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { - self.highlighter.highlight(line, pos) - } -} - -impl Completer for RLHelper { - type Candidate = Pair; - fn complete( - &self, - line: &str, - pos: usize, - ctx: &Context<'_>, - ) -> Result<(usize, Vec), ReadlineError> { - self.completer.complete(line, pos, ctx) - } -} - -impl Hinter for RLHelper { - fn hint(&self, line: &str, a: usize, b: &Context) -> Option { - self.hinter.hint(line, a, b) - } -} - -impl Helper for RLHelper {} - -pub fn create_readline() -> Editor { - let config_builder = Builder::new(); - let config = config_builder - .color_mode(ColorMode::Enabled) - .edit_mode(EditMode::Emacs) - .history_ignore_space(true) - .completion_type(CompletionType::Circular) - .max_history_size(1000) - .build(); - let mut rl = Editor::with_config(config); - let h = RLHelper { - completer: FilenameCompleter::new(), - highlighter: LineHighlighter {}, - hinter: HistoryHinter {}, - }; - rl.set_helper(Some(h)); - rl -} -- cgit v1.2.3