mirror of
https://codeberg.org/icewind/php-literal-parser.git
synced 2026-06-03 18:44:07 +02:00
accept more array keys
This commit is contained in:
parent
9ab5131b4c
commit
2352ed9fd1
3 changed files with 66 additions and 35 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::lexer::{SpannedToken, Token};
|
use crate::lexer::{SpannedToken, Token};
|
||||||
use crate::num::ParseIntError;
|
use crate::num::ParseIntError;
|
||||||
use crate::string::UnescapeError;
|
use crate::string::UnescapeError;
|
||||||
use crate::Value;
|
|
||||||
use logos::Span;
|
use logos::Span;
|
||||||
use source_span::{
|
use source_span::{
|
||||||
fmt::{Color, Formatter, Style},
|
fmt::{Color, Formatter, Style},
|
||||||
|
|
@ -101,8 +100,6 @@ fn get_position(text: &str, index: usize) -> Position {
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
UnexpectedToken(#[from] UnexpectedTokenError),
|
UnexpectedToken(#[from] UnexpectedTokenError),
|
||||||
#[error("{0}")]
|
|
||||||
InvalidArrayKey(#[from] InvalidArrayKeyError),
|
|
||||||
#[error("Invalid boolean literal: {0}")]
|
#[error("Invalid boolean literal: {0}")]
|
||||||
InvalidBoolLiteral(#[from] ParseBoolError),
|
InvalidBoolLiteral(#[from] ParseBoolError),
|
||||||
#[error("Invalid integer literal: {0}")]
|
#[error("Invalid integer literal: {0}")]
|
||||||
|
|
@ -158,10 +155,6 @@ impl Display for UnexpectedTokenError {
|
||||||
|
|
||||||
impl Error for UnexpectedTokenError {}
|
impl Error for UnexpectedTokenError {}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("Invalid array key {0:?} expected number or string")]
|
|
||||||
pub struct InvalidArrayKeyError(pub Value);
|
|
||||||
|
|
||||||
pub trait ExpectToken<'source> {
|
pub trait ExpectToken<'source> {
|
||||||
fn expect_token(
|
fn expect_token(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use crate::error::UnexpectedTokenError;
|
use crate::error::{ExpectToken, ParseError, ResultExt, SpannedError};
|
||||||
use crate::error::{ExpectToken, InvalidArrayKeyError, ParseError, ResultExt, SpannedError};
|
|
||||||
use crate::lexer::{SpannedToken, Token, TokenStream};
|
use crate::lexer::{SpannedToken, Token, TokenStream};
|
||||||
use crate::num::parse_int;
|
use crate::num::parse_int;
|
||||||
use crate::string::parse_string;
|
use crate::string::{is_array_key_numeric, parse_string};
|
||||||
use crate::{Key, Value};
|
use crate::{Key, Value};
|
||||||
use logos::{Lexer, Logos};
|
use logos::Logos;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
|
|
||||||
|
|
@ -27,7 +26,7 @@ use std::num::ParseFloatError;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
pub fn parse(source: &str) -> Result<Value, SpannedError<ParseError>> {
|
pub fn parse(source: &str) -> Result<Value, SpannedError<ParseError>> {
|
||||||
Parser::new(source).parse_any()
|
Parser::new(source).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Parser<'source> {
|
pub struct Parser<'source> {
|
||||||
|
|
@ -43,7 +42,7 @@ impl<'source> Parser<'source> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_any(&mut self) -> Result<Value, SpannedError<ParseError>> {
|
pub fn run(&mut self) -> Result<Value, SpannedError<ParseError>> {
|
||||||
let token = self.tokens.next().expect_token(&[
|
let token = self.tokens.next().expect_token(&[
|
||||||
Token::Bool,
|
Token::Bool,
|
||||||
Token::Integer,
|
Token::Integer,
|
||||||
|
|
@ -53,10 +52,10 @@ impl<'source> Parser<'source> {
|
||||||
Token::Array,
|
Token::Array,
|
||||||
Token::SquareOpen,
|
Token::SquareOpen,
|
||||||
])?;
|
])?;
|
||||||
self.parse_token(token)
|
self.parse_any(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_token(&mut self, token: SpannedToken) -> Result<Value, SpannedError<ParseError>> {
|
pub fn parse_any(&mut self, token: SpannedToken) -> Result<Value, SpannedError<ParseError>> {
|
||||||
let value = match token.token {
|
let value = match token.token {
|
||||||
Token::Bool => Value::Bool(self.parse_bool(token)?),
|
Token::Bool => Value::Bool(self.parse_bool(token)?),
|
||||||
Token::Integer => Value::Int(self.parse_int(token)?),
|
Token::Integer => Value::Int(self.parse_int(token)?),
|
||||||
|
|
@ -125,33 +124,19 @@ impl<'source> Parser<'source> {
|
||||||
|
|
||||||
match next.token {
|
match next.token {
|
||||||
Token::BracketClose => {
|
Token::BracketClose => {
|
||||||
builder.push_value(self.parse_token(key_value_or_close_token)?);
|
builder.push_value(self.parse_any(key_value_or_close_token)?);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Token::SquareClose => {
|
Token::SquareClose => {
|
||||||
builder.push_value(self.parse_token(key_value_or_close_token)?);
|
builder.push_value(self.parse_any(key_value_or_close_token)?);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Token::Comma => {
|
Token::Comma => {
|
||||||
builder.push_value(self.parse_token(key_value_or_close_token)?);
|
builder.push_value(self.parse_any(key_value_or_close_token)?);
|
||||||
}
|
}
|
||||||
Token::Arrow => {
|
Token::Arrow => {
|
||||||
let key_token = key_value_or_close_token.expect_token(&[
|
let key = self.parse_array_key(key_value_or_close_token)?;
|
||||||
Token::Bool,
|
let value = self.run()?;
|
||||||
Token::Integer,
|
|
||||||
Token::Float,
|
|
||||||
Token::LiteralString,
|
|
||||||
Token::Null,
|
|
||||||
])?;
|
|
||||||
let value = self.parse_any()?;
|
|
||||||
let key = match self.parse_token(key_token)? {
|
|
||||||
Value::Int(int) => Key::Int(int),
|
|
||||||
Value::Float(float) => Key::Int(float as i64),
|
|
||||||
Value::String(str) => Key::String(str),
|
|
||||||
Value::Bool(bool) => Key::Int(if bool { 1 } else { 0 }),
|
|
||||||
Value::Null => Key::String(String::from("")),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
builder.push_key_value(key, value);
|
builder.push_key_value(key, value);
|
||||||
|
|
||||||
match self
|
match self
|
||||||
|
|
@ -178,6 +163,28 @@ impl<'source> Parser<'source> {
|
||||||
|
|
||||||
Ok(builder.data)
|
Ok(builder.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_array_key(
|
||||||
|
&mut self,
|
||||||
|
token: SpannedToken,
|
||||||
|
) -> Result<Key, SpannedError<ParseError>> {
|
||||||
|
let token = token.expect_token(&[
|
||||||
|
Token::Bool,
|
||||||
|
Token::Integer,
|
||||||
|
Token::Float,
|
||||||
|
Token::LiteralString,
|
||||||
|
Token::Null,
|
||||||
|
])?;
|
||||||
|
Ok(match self.parse_any(token)? {
|
||||||
|
Value::Int(int) => Key::Int(int),
|
||||||
|
Value::Float(float) => Key::Int(float as i64),
|
||||||
|
Value::String(str) if is_array_key_numeric(&str) => Key::Int(parse_int(&str).unwrap()),
|
||||||
|
Value::String(str) => Key::String(str),
|
||||||
|
Value::Bool(bool) => Key::Int(if bool { 1 } else { 0 }),
|
||||||
|
Value::Null => Key::String(String::from("")),
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_float(literal: &str) -> Result<f64, ParseFloatError> {
|
fn parse_float(literal: &str) -> Result<f64, ParseFloatError> {
|
||||||
|
|
@ -207,7 +214,7 @@ impl ArrayBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
enum ArraySyntax {
|
pub enum ArraySyntax {
|
||||||
Short,
|
Short,
|
||||||
Long,
|
Long,
|
||||||
}
|
}
|
||||||
|
|
@ -306,4 +313,15 @@ fn test_parse() {
|
||||||
assert_eq!(Value::Float(1000.0), parse(r#"10e2"#).unwrap());
|
assert_eq!(Value::Float(1000.0), parse(r#"10e2"#).unwrap());
|
||||||
assert_eq!(Value::Float(1.0), parse(r#"10e-1"#).unwrap());
|
assert_eq!(Value::Float(1.0), parse(r#"10e-1"#).unwrap());
|
||||||
assert_eq!(Value::Float(1234.5), parse(r#"12_34.5"#).unwrap());
|
assert_eq!(Value::Float(1234.5), parse(r#"12_34.5"#).unwrap());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Value::Array(hashmap! {
|
||||||
|
Key::Int(2) => Value::Int(3),
|
||||||
|
Key::String("foo".into()) => Value::Int(4),
|
||||||
|
Key::String("".into()) => Value::Int(5),
|
||||||
|
Key::Int(1) => Value::Int(6),
|
||||||
|
Key::Int(0) => Value::Int(7),
|
||||||
|
}),
|
||||||
|
parse(r#"array("2"=>3,"foo" => 4, null => 5, true => 6, false => 7)"#).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,10 +198,30 @@ impl<'a> PeekableBytes<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_array_key_numeric(string: &str) -> bool {
|
||||||
|
let mut bytes = string.bytes();
|
||||||
|
if !matches!((bytes.next(), string.len()), (Some(b'-'), _) | (Some(b'0'..=b'9'), 1) | (Some(b'1'..=b'9'), _))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.all(|byte| matches!(byte, b'0'..=b'9'))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_is_numeric() {
|
||||||
|
assert_eq!(true, is_array_key_numeric("123"));
|
||||||
|
assert_eq!(true, is_array_key_numeric("-123"));
|
||||||
|
assert_eq!(true, is_array_key_numeric("0"));
|
||||||
|
assert_eq!(false, is_array_key_numeric("0123"));
|
||||||
|
assert_eq!(false, is_array_key_numeric("123asd"));
|
||||||
|
assert_eq!(false, is_array_key_numeric("+123"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unescape_single() {
|
fn test_unescape_single() {
|
||||||
assert_eq!(unescape::<SingleQuoteString>(&r#"abc"#), Ok("abc".into()));
|
assert_eq!(unescape::<SingleQuoteString>(&r#"abc"#), Ok("abc".into()));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue