Add string constants
* Implement string parsing in tokenizer * Implement escape sequences `\"` and `\\` * Document changes * Update change log Relates to #35
This commit is contained in:
parent
e19770b517
commit
67d68cd974
@ -6,6 +6,8 @@
|
||||
|
||||
### Added
|
||||
|
||||
* String constants
|
||||
|
||||
### Removed
|
||||
|
||||
### Changed
|
||||
|
@ -221,12 +221,12 @@ They return the result as the type it was passed into the function.
|
||||
### Values
|
||||
|
||||
Operators take values as arguments and produce values as results.
|
||||
Values can be boolean, integer or floating point numbers, tuples or the empty type.
|
||||
Strings are supported as well, but there are no operations defined for them yet.
|
||||
Values can be boolean, integer or floating point numbers, strings, tuples or the empty type.
|
||||
Values are denoted as displayed in the following table.
|
||||
|
||||
| Value type | Example |
|
||||
|------------|---------|
|
||||
| `Value::String` | `"abc"`, `""`, `"a\"b\\c"` |
|
||||
| `Value::Boolean` | `true`, `false` |
|
||||
| `Value::Int` | `3`, `-9`, `0`, `135412` |
|
||||
| `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554` |
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use EvalexprError;
|
||||
use EvalexprResult;
|
||||
use function::Function;
|
||||
use value::value_type::ValueType;
|
||||
use EvalexprError;
|
||||
use EvalexprResult;
|
||||
|
||||
use crate::value::Value;
|
||||
|
||||
|
@ -82,6 +82,7 @@ impl fmt::Display for EvalexprError {
|
||||
write!(f, "Error modulating {} % {}", dividend, divisor)
|
||||
},
|
||||
ContextNotManipulable => write!(f, "Cannot manipulate context"),
|
||||
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
|
||||
CustomMessage(message) => write!(f, "Error: {}", message),
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
//! They are meant as shortcuts to not write the same error checking code everywhere.
|
||||
|
||||
use token::PartialToken;
|
||||
use value::{TupleType, value_type::ValueType};
|
||||
use value::{value_type::ValueType, TupleType};
|
||||
|
||||
use crate::value::Value;
|
||||
|
||||
@ -163,6 +163,9 @@ pub enum EvalexprError {
|
||||
/// A modification was attempted on a `Context` that does not allow modifications.
|
||||
ContextNotManipulable,
|
||||
|
||||
/// An escape sequence within a string literal is illegal.
|
||||
IllegalEscapeSequence(String),
|
||||
|
||||
/// A custom error explained by its message.
|
||||
CustomMessage(String),
|
||||
}
|
||||
|
@ -208,12 +208,12 @@
|
||||
//! ### Values
|
||||
//!
|
||||
//! Operators take values as arguments and produce values as results.
|
||||
//! Values can be boolean, integer or floating point numbers, tuples or the empty type.
|
||||
//! Strings are supported as well, but there are no operations defined for them yet.
|
||||
//! Values can be boolean, integer or floating point numbers, strings, tuples or the empty type.
|
||||
//! Values are denoted as displayed in the following table.
|
||||
//!
|
||||
//! | Value type | Example |
|
||||
//! |------------|---------|
|
||||
//! | `Value::String` | `"abc"`, `""`, `"a\"b\\c"` |
|
||||
//! | `Value::Boolean` | `true`, `false` |
|
||||
//! | `Value::Int` | `3`, `-9`, `0`, `135412` |
|
||||
//! | `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554` |
|
||||
@ -361,7 +361,7 @@ pub use function::Function;
|
||||
pub use interface::*;
|
||||
pub use tree::Node;
|
||||
pub use value::{
|
||||
EMPTY_VALUE, EmptyType, FloatType, IntType, TupleType, Value, value_type::ValueType,
|
||||
value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE,
|
||||
};
|
||||
|
||||
mod context;
|
||||
|
@ -38,6 +38,7 @@ impl fmt::Display for Token {
|
||||
Float(float) => float.fmt(f),
|
||||
Int(int) => int.fmt(f),
|
||||
Boolean(boolean) => boolean.fmt(f),
|
||||
String(string) => fmt::Debug::fmt(string, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ pub enum Token {
|
||||
Float(FloatType),
|
||||
Int(IntType),
|
||||
Boolean(bool),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -63,6 +64,12 @@ fn char_to_partial_token(c: char) -> PartialToken {
|
||||
'%' => PartialToken::Token(Token::Percent),
|
||||
'^' => PartialToken::Token(Token::Hat),
|
||||
|
||||
'(' => PartialToken::Token(Token::LBrace),
|
||||
')' => PartialToken::Token(Token::RBrace),
|
||||
|
||||
',' => PartialToken::Token(Token::Comma),
|
||||
';' => PartialToken::Token(Token::Semicolon),
|
||||
|
||||
'=' => PartialToken::Eq,
|
||||
'!' => PartialToken::ExclamationMark,
|
||||
'>' => PartialToken::Gt,
|
||||
@ -70,12 +77,6 @@ fn char_to_partial_token(c: char) -> PartialToken {
|
||||
'&' => PartialToken::Ampersand,
|
||||
'|' => PartialToken::VerticalBar,
|
||||
|
||||
'(' => PartialToken::Token(Token::LBrace),
|
||||
')' => PartialToken::Token(Token::RBrace),
|
||||
|
||||
',' => PartialToken::Token(Token::Comma),
|
||||
';' => PartialToken::Token(Token::Semicolon),
|
||||
|
||||
c => {
|
||||
if c.is_whitespace() {
|
||||
PartialToken::Whitespace
|
||||
@ -118,6 +119,7 @@ impl Token {
|
||||
Token::Float(_) => true,
|
||||
Token::Int(_) => true,
|
||||
Token::Boolean(_) => true,
|
||||
Token::String(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,31 +154,70 @@ impl Token {
|
||||
Token::Float(_) => true,
|
||||
Token::Int(_) => true,
|
||||
Token::Boolean(_) => true,
|
||||
Token::String(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a string to a vector of partial tokens.
|
||||
fn str_to_tokens(string: &str) -> Vec<PartialToken> {
|
||||
let mut result = Vec::new();
|
||||
for c in string.chars() {
|
||||
let partial_token = char_to_partial_token(c);
|
||||
/// Parses an escape sequence within a string literal.
|
||||
fn parse_escape_sequence<Iter: Iterator<Item = char>>(iter: &mut Iter) -> EvalexprResult<char> {
|
||||
match iter.next() {
|
||||
Some('"') => Ok('"'),
|
||||
Some('\\') => Ok('\\'),
|
||||
Some(c) => Err(EvalexprError::IllegalEscapeSequence(format!("\\{}", c))),
|
||||
None => Err(EvalexprError::IllegalEscapeSequence(format!("\\"))),
|
||||
}
|
||||
}
|
||||
|
||||
let if_let_successful =
|
||||
if let (Some(PartialToken::Literal(last)), PartialToken::Literal(literal)) =
|
||||
(result.last_mut(), &partial_token)
|
||||
{
|
||||
last.push_str(literal);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
/// Parses a string value from the given character iterator.
|
||||
///
|
||||
/// The first character from the iterator is interpreted as first character of the string.
|
||||
/// The string is terminated by a double quote `"`.
|
||||
/// Occurrences of `"` within the string can be escaped with `\`.
|
||||
/// The backslash needs to be escaped with another backslash `\`.
|
||||
fn parse_string_literal<Iter: Iterator<Item = char>>(
|
||||
mut iter: &mut Iter,
|
||||
) -> EvalexprResult<PartialToken> {
|
||||
let mut result = String::new();
|
||||
|
||||
if !if_let_successful {
|
||||
result.push(partial_token);
|
||||
while let Some(c) = iter.next() {
|
||||
match c {
|
||||
'"' => break,
|
||||
'\\' => result.push(parse_escape_sequence(&mut iter)?),
|
||||
c => result.push(c),
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
Ok(PartialToken::Token(Token::String(result)))
|
||||
}
|
||||
|
||||
/// Converts a string to a vector of partial tokens.
|
||||
fn str_to_partial_tokens(string: &str) -> EvalexprResult<Vec<PartialToken>> {
|
||||
let mut result = Vec::new();
|
||||
let mut iter = string.chars().peekable();
|
||||
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '"' {
|
||||
result.push(parse_string_literal(&mut iter)?);
|
||||
} else {
|
||||
let partial_token = char_to_partial_token(c);
|
||||
|
||||
let if_let_successful =
|
||||
if let (Some(PartialToken::Literal(last)), PartialToken::Literal(literal)) =
|
||||
(result.last_mut(), &partial_token)
|
||||
{
|
||||
last.push_str(literal);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !if_let_successful {
|
||||
result.push(partial_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Resolves all partial tokens by converting them to complex tokens.
|
||||
@ -255,5 +296,5 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<T
|
||||
}
|
||||
|
||||
pub(crate) fn tokenize(string: &str) -> EvalexprResult<Vec<Token>> {
|
||||
partial_tokens_to_tokens(&str_to_tokens(string))
|
||||
partial_tokens_to_tokens(&str_to_partial_tokens(string)?)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use token::Token;
|
||||
use value::{TupleType, EMPTY_VALUE};
|
||||
use EmptyContext;
|
||||
use EmptyType;
|
||||
use FloatType;
|
||||
use IntType;
|
||||
use token::Token;
|
||||
use value::{EMPTY_VALUE, TupleType};
|
||||
|
||||
use crate::{
|
||||
context::Context,
|
||||
@ -397,9 +397,10 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec<Token>) -> EvalexprResult<Node
|
||||
}
|
||||
result
|
||||
},
|
||||
Token::Float(number) => Some(Node::new(Const::new(Value::Float(number)))),
|
||||
Token::Int(number) => Some(Node::new(Const::new(Value::Int(number)))),
|
||||
Token::Float(float) => Some(Node::new(Const::new(Value::Float(float)))),
|
||||
Token::Int(int) => Some(Node::new(Const::new(Value::Int(int)))),
|
||||
Token::Boolean(boolean) => Some(Node::new(Const::new(Value::Boolean(boolean)))),
|
||||
Token::String(string) => Some(Node::new(Const::new(Value::String(string)))),
|
||||
};
|
||||
|
||||
if let Some(node) = node {
|
||||
|
@ -1,6 +1,6 @@
|
||||
extern crate evalexpr;
|
||||
|
||||
use evalexpr::{*, error::*};
|
||||
use evalexpr::{error::*, *};
|
||||
|
||||
#[test]
|
||||
fn test_unary_examples() {
|
||||
@ -539,6 +539,20 @@ fn test_expression_chaining() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strings() {
|
||||
let mut context = HashMapContext::new();
|
||||
assert_eq!(eval("\"string\""), Ok(Value::from("string")));
|
||||
assert_eq!(
|
||||
eval_with_context_mut("a = \"a string\"", &mut context),
|
||||
Ok(Value::Empty)
|
||||
);
|
||||
assert_eq!(
|
||||
eval_boolean_with_context("a == \"a string\"", &context),
|
||||
Ok(true)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn test_serde() {
|
||||
|
Reference in New Issue
Block a user