handle null

This commit is contained in:
Robin Appelman 2020-12-02 23:23:01 +01:00
commit 27b1552c94
4 changed files with 33 additions and 30 deletions

View file

@ -13,6 +13,7 @@ fn main() {
"array" => [1,2,3,4], "array" => [1,2,3,4],
"bool" => false, "bool" => false,
"negative" => -1, "negative" => -1,
"null" => null,
) )
"###; "###;

View file

@ -6,6 +6,8 @@ pub enum Token {
Array, Array,
#[regex("true|false")] #[regex("true|false")]
Bool, Bool,
#[regex("null")]
Null,
#[token("=>")] #[token("=>")]
Arrow, Arrow,
#[token("(")] #[token("(")]
@ -43,6 +45,7 @@ fn test_lex() {
"array" => [1,2,3,4], "array" => [1,2,3,4],
"bool" => false, "bool" => false,
"negative" => -1, "negative" => -1,
"null" => null,
) )
"###; "###;
let mut lex = Token::lexer(source); let mut lex = Token::lexer(source);
@ -105,6 +108,11 @@ fn test_lex() {
assert_eq!(lex.next(), Some(Token::Integer)); assert_eq!(lex.next(), Some(Token::Integer));
assert_eq!(lex.next(), Some(Token::Comma)); assert_eq!(lex.next(), Some(Token::Comma));
assert_eq!(lex.next(), Some(Token::LiteralString));
assert_eq!(lex.next(), Some(Token::Arrow));
assert_eq!(lex.next(), Some(Token::Null));
assert_eq!(lex.next(), Some(Token::Comma));
assert_eq!(lex.next(), Some(Token::BracketClose)); assert_eq!(lex.next(), Some(Token::BracketClose));
assert_eq!(lex.next(), None); assert_eq!(lex.next(), None);

View file

@ -18,13 +18,13 @@
//! # } //! # }
//! ``` //! ```
//! //!
mod ast;
mod error; mod error;
mod lexer; mod lexer;
mod parser;
mod string; mod string;
pub use ast::parse;
pub use error::{ParseError, SpannedError}; pub use error::{ParseError, SpannedError};
pub use parser::parse;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -37,6 +37,7 @@ pub enum Value {
Float(f64), Float(f64),
String(String), String(String),
Array(HashMap<Key, Value>), Array(HashMap<Key, Value>),
Null,
} }
/// A php value, can be either a bool, int, float, string or array /// A php value, can be either a bool, int, float, string or array
@ -46,7 +47,8 @@ pub enum Value {
/// ///
/// ## Indexing /// ## Indexing
/// ///
/// If the value is a php array, you can directly index into the `Value`, this will panic if the `Value` is not an array /// If the value is a php array, you can directly index into the `Value`, this will null if the `Value` is not an array
/// or the key is not found
/// ///
/// ```rust /// ```rust
/// # use maplit::hashmap; /// # use maplit::hashmap;
@ -59,6 +61,7 @@ pub enum Value {
/// }); /// });
/// assert_eq!(value["key"], "value"); /// assert_eq!(value["key"], "value");
/// assert_eq!(value[10], false); /// assert_eq!(value[10], false);
/// assert!(value["not"]["found"].is_null());
/// # } /// # }
/// ``` /// ```
impl Value { impl Value {
@ -87,6 +90,11 @@ impl Value {
matches!(self, Value::Array(_)) matches!(self, Value::Array(_))
} }
/// Check if the value is null
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
/// Convert the value into a bool if it is one /// Convert the value into a bool if it is one
pub fn into_bool(self) -> Option<bool> { pub fn into_bool(self) -> Option<bool> {
match self { match self {
@ -282,8 +290,8 @@ where
fn index(&self, index: &Q) -> &Self::Output { fn index(&self, index: &Q) -> &Self::Output {
match self { match self {
Value::Array(map) => map.index(index), Value::Array(map) => map.get(index).unwrap_or(&Value::Null),
_ => panic!("index into non array value"), _ => &Value::Null,
} }
} }
} }
@ -293,8 +301,8 @@ impl Index<Key> for Value {
fn index(&self, index: Key) -> &Self::Output { fn index(&self, index: Key) -> &Self::Output {
match self { match self {
Value::Array(map) => map.index(&index), Value::Array(map) => map.get(&index).unwrap_or(&Value::Null),
_ => panic!("index into non array value"), _ => &Value::Null,
} }
} }
} }
@ -305,7 +313,7 @@ impl Index<i64> for Value {
fn index(&self, index: i64) -> &Self::Output { fn index(&self, index: i64) -> &Self::Output {
match self { match self {
Value::Array(map) => map.index(&Key::Int(index)), Value::Array(map) => map.index(&Key::Int(index)),
_ => panic!("index into non array value"), _ => &Value::Null,
} }
} }
} }

View file

@ -40,17 +40,19 @@ pub fn parse_lexer<'source>(
Token::Integer, Token::Integer,
Token::Float, Token::Float,
Token::LiteralString, Token::LiteralString,
Token::Null,
Token::Array, Token::Array,
Token::SquareOpen, Token::SquareOpen,
]) ])
.with_span(lexer.span(), source)?; .with_span(lexer.span(), source)?;
let value = match token { let value = match token {
Token::Bool => parse_literal(token, lexer.slice()).with_span(lexer.span(), source)?, Token::Bool => Value::Bool(lexer.slice().parse().with_span(lexer.span(), source)?),
Token::Integer => parse_literal(token, lexer.slice()).with_span(lexer.span(), source)?, Token::Integer => Value::Int(lexer.slice().parse().with_span(lexer.span(), source)?),
Token::Float => parse_literal(token, lexer.slice()).with_span(lexer.span(), source)?, Token::Float => Value::Float(lexer.slice().parse().with_span(lexer.span(), source)?),
Token::LiteralString => { Token::LiteralString => {
parse_literal(token, lexer.slice()).with_span(lexer.span(), source)? Value::String(parse_string(lexer.slice()).with_span(lexer.span(), source)?)
} }
Token::Null => Value::Null,
Token::Array => Value::Array(parse_array(source, lexer, ArraySyntax::Long)?), Token::Array => Value::Array(parse_array(source, lexer, ArraySyntax::Long)?),
Token::SquareOpen => Value::Array(parse_array(source, lexer, ArraySyntax::Short)?), Token::SquareOpen => Value::Array(parse_array(source, lexer, ArraySyntax::Short)?),
_ => unreachable!(), _ => unreachable!(),
@ -59,22 +61,6 @@ pub fn parse_lexer<'source>(
Ok(value) Ok(value)
} }
fn parse_literal(token: Token, slice: &str) -> Result<Value, ParseError> {
let token = token.expect_token(&[
Token::Bool,
Token::Integer,
Token::Float,
Token::LiteralString,
])?;
match token {
Token::Bool => Ok(Value::Bool(slice.parse()?)),
Token::Integer => Ok(Value::Int(slice.parse()?)),
Token::Float => Ok(Value::Float(slice.parse()?)),
Token::LiteralString => Ok(Value::String(parse_string(slice)?)),
_ => unreachable!(),
}
}
fn parse_string(literal: &str) -> Result<String, UnescapeError> { fn parse_string(literal: &str) -> Result<String, UnescapeError> {
let single_quote = literal.bytes().next().unwrap() == b'\''; let single_quote = literal.bytes().next().unwrap() == b'\'';
let inner = &literal[1..(literal.len()) - 1]; let inner = &literal[1..(literal.len()) - 1];
@ -273,9 +259,9 @@ fn test_parse() {
Value::Array(hashmap! { Value::Array(hashmap! {
Key::String("foo".into()) => Value::Bool(true), Key::String("foo".into()) => Value::Bool(true),
Key::String("nested".into()) => Value::Array(hashmap! { Key::String("nested".into()) => Value::Array(hashmap! {
Key::String("foo".into()) => Value::Bool(false), Key::String("foo".into()) => Value::Null,
}), }),
}), }),
parse(r#"["foo" => true, "nested" => ['foo' => false]]"#).unwrap() parse(r#"["foo" => true, "nested" => ['foo' => null]]"#).unwrap()
); );
} }