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:
Sebastian Schmidt 2019-03-30 11:54:19 +01:00
parent e19770b517
commit 67d68cd974
10 changed files with 100 additions and 37 deletions

View File

@ -6,6 +6,8 @@
### Added
* String constants
### Removed
### Changed

View File

@ -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` |

View File

@ -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;

View File

@ -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),
}
}

View File

@ -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),
}

View File

@ -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;

View File

@ -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),
}
}
}

View File

@ -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,14 +154,52 @@ impl Token {
Token::Float(_) => true,
Token::Int(_) => true,
Token::Boolean(_) => true,
Token::String(_) => true,
}
}
}
/// 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!("\\"))),
}
}
/// 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();
while let Some(c) = iter.next() {
match c {
'"' => break,
'\\' => result.push(parse_escape_sequence(&mut iter)?),
c => result.push(c),
}
}
Ok(PartialToken::Token(Token::String(result)))
}
/// Converts a string to a vector of partial tokens.
fn str_to_tokens(string: &str) -> Vec<PartialToken> {
fn str_to_partial_tokens(string: &str) -> EvalexprResult<Vec<PartialToken>> {
let mut result = Vec::new();
for c in string.chars() {
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 =
@ -176,7 +216,8 @@ fn str_to_tokens(string: &str) -> Vec<PartialToken> {
result.push(partial_token);
}
}
result
}
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)?)
}

View File

@ -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 {

View File

@ -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() {