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