Add parse_json.

This commit is contained in:
Stephen Chung 2022-09-29 22:46:59 +08:00
parent 6c777e68d3
commit e8fd965eba
9 changed files with 78 additions and 34 deletions

View File

@ -12,6 +12,11 @@ New features
* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher, via an environment variable, in order to force stable (i.e. deterministic) hashes for function signatures. This is necessary when using Rhai across shared-library boundaries.
* A build script is now used to extract the environment variable (`RHAI_AHASH_SEED`) and splice it into the source code before compilation.
Bug fixes
---------
* `Engine::parse_json` now returns an error on unquoted keys to be consistent with JSON specifications.
Enhancements
------------
@ -19,6 +24,7 @@ Enhancements
* Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%.
* `Scope` is now serializable and deserializable via `serde`.
* `Scope` now contains a const generic parameter that allows specifying how many entries to be kept inline.
* `parse_json` function is added to parse a JSON string into an object map.
Version 1.10.1

View File

@ -295,6 +295,6 @@ impl Engine {
let mut peekable = stream.peekable();
let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control);
self.parse_global_expr(&mut peekable, &mut state, self.optimization_level)
self.parse_global_expr(&mut peekable, &mut state, |_| {}, self.optimization_level)
}
}

View File

@ -122,6 +122,7 @@ impl Engine {
let ast = self.parse_global_expr(
&mut stream.peekable(),
&mut state,
|_| {},
#[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None,
#[cfg(feature = "no_optimize")]

View File

@ -23,7 +23,7 @@ impl Engine {
/// JSON sub-objects are handled transparently.
///
/// This function can be used together with [`format_map_as_json`] to work with JSON texts
/// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy).
/// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy).
///
/// # Example
///
@ -122,6 +122,7 @@ impl Engine {
let ast = self.parse_global_expr(
&mut stream.peekable(),
&mut state,
|s| s.allow_unquoted_map_properties = false,
#[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None,
#[cfg(feature = "no_optimize")]
@ -137,7 +138,7 @@ impl Engine {
/// Not available under `no_std`.
///
/// This function can be used together with [`Engine::parse_json`] to work with JSON texts
/// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy).
/// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy).
///
/// # Data types
///

View File

@ -88,7 +88,6 @@ mod core_functions {
#[cfg(feature = "f32_float")]
std::thread::sleep(std::time::Duration::from_secs_f32(seconds));
}
/// Block the current thread for a particular number of `seconds`.
#[cfg(not(feature = "no_std"))]
pub fn sleep(seconds: INT) {
@ -97,6 +96,23 @@ mod core_functions {
}
std::thread::sleep(std::time::Duration::from_secs(seconds as u64));
}
/// Parse a JSON string into a value.
///
/// # Example
///
/// ```rhai
/// let m = parse_json(`{"a":1, "b":2, "c":3}`);
///
/// print(m); // prints #{"a":1, "b":2, "c":3}
/// ```
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
#[cfg(feature = "metadata")]
#[rhai_fn(return_raw)]
pub fn parse_json(_ctx: NativeCallContext, json: &str) -> RhaiResultOf<Dynamic> {
serde_json::from_str(json).map_err(|err| err.to_string().into())
}
}
#[cfg(not(feature = "no_function"))]

View File

@ -2,7 +2,7 @@
use crate::engine::OP_EQUALS;
use crate::plugin::*;
use crate::{def_package, format_map_as_json, Dynamic, ImmutableString, Map, RhaiResultOf, INT};
use crate::{def_package, Dynamic, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -304,6 +304,9 @@ mod map_functions {
/// print(m.to_json()); // prints {"a":1, "b":2, "c":3}
/// ```
pub fn to_json(map: &mut Map) -> String {
format_map_as_json(map)
#[cfg(feature = "metadata")]
return serde_json::to_string(map).unwrap_or_else(|_| "ERROR".into());
#[cfg(not(feature = "metadata"))]
return crate::format_map_as_json(map);
}
}

View File

@ -287,26 +287,28 @@ impl<'e> ParseState<'e> {
/// A type that encapsulates all the settings for a particular parsing function.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct ParseSettings {
pub(crate) struct ParseSettings {
/// Is the construct being parsed located at global level?
at_global_level: bool,
pub at_global_level: bool,
/// Is the construct being parsed located inside a function definition?
#[cfg(not(feature = "no_function"))]
in_fn_scope: bool,
pub in_fn_scope: bool,
/// Is the construct being parsed located inside a closure definition?
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_closure"))]
in_closure: bool,
pub in_closure: bool,
/// Is the construct being parsed located inside a breakable loop?
is_breakable: bool,
pub is_breakable: bool,
/// Allow statements in blocks?
allow_statements: bool,
pub allow_statements: bool,
/// Allow unquoted map properties?
pub allow_unquoted_map_properties: bool,
/// Language options in effect (overrides Engine options).
options: LangOptions,
pub options: LangOptions,
/// Current expression nesting level.
level: usize,
pub level: usize,
/// Current position.
pos: Position,
pub pos: Position,
}
impl ParseSettings {
@ -996,6 +998,9 @@ impl Engine {
}
let (name, pos) = match input.next().expect(NEVER_ENDS) {
(Token::Identifier(..), pos) if !settings.allow_unquoted_map_properties => {
return Err(PERR::PropertyExpected.into_err(pos))
}
(Token::Identifier(s) | Token::StringConstant(s), pos) => {
if map.iter().any(|(p, ..)| **p == *s) {
return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos));
@ -3326,6 +3331,7 @@ impl Engine {
in_closure: false,
is_breakable: false,
allow_statements: true,
allow_unquoted_map_properties: settings.allow_unquoted_map_properties,
level: 0,
options,
pos,
@ -3793,6 +3799,7 @@ impl Engine {
&self,
input: &mut TokenStream,
state: &mut ParseState,
process_settings: impl Fn(&mut ParseSettings),
_optimization_level: OptimizationLevel,
) -> ParseResult<AST> {
let mut functions = StraightHashMap::default();
@ -3802,7 +3809,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
options.remove(LangOptions::ANON_FN);
let settings = ParseSettings {
let mut settings = ParseSettings {
at_global_level: true,
#[cfg(not(feature = "no_function"))]
in_fn_scope: false,
@ -3811,10 +3818,13 @@ impl Engine {
in_closure: false,
is_breakable: false,
allow_statements: false,
allow_unquoted_map_properties: true,
level: 0,
options,
pos: Position::NONE,
pos: Position::START,
};
process_settings(&mut settings);
let expr = self.parse_expr(input, state, &mut functions, settings)?;
assert!(functions.is_empty());
@ -3853,25 +3863,27 @@ impl Engine {
&self,
input: &mut TokenStream,
state: &mut ParseState,
process_settings: impl Fn(&mut ParseSettings),
) -> ParseResult<(StmtBlockContainer, StaticVec<Shared<ScriptFnDef>>)> {
let mut statements = StmtBlockContainer::new_const();
let mut functions = StraightHashMap::default();
let mut settings = ParseSettings {
at_global_level: true,
#[cfg(not(feature = "no_function"))]
in_fn_scope: false,
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_closure"))]
in_closure: false,
is_breakable: false,
allow_statements: true,
allow_unquoted_map_properties: true,
options: self.options,
level: 0,
pos: Position::START,
};
process_settings(&mut settings);
while !input.peek().expect(NEVER_ENDS).0.is_eof() {
let settings = ParseSettings {
at_global_level: true,
#[cfg(not(feature = "no_function"))]
in_fn_scope: false,
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_closure"))]
in_closure: false,
is_breakable: false,
allow_statements: true,
options: self.options,
level: 0,
pos: Position::NONE,
};
let stmt = self.parse_stmt(input, state, &mut functions, settings)?;
if stmt.is_noop() {
@ -3918,7 +3930,7 @@ impl Engine {
state: &mut ParseState,
_optimization_level: OptimizationLevel,
) -> ParseResult<AST> {
let (statements, _lib) = self.parse_global_level(input, state)?;
let (statements, _lib) = self.parse_global_level(input, state, |_| {})?;
#[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimizer::optimize_into_ast(

View File

@ -251,9 +251,9 @@ impl<T: AsRef<str>> From<T> for EvalAltResult {
impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
#[cold]
#[inline(never)]
#[inline(always)]
fn from(err: T) -> Self {
EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into()
Into::<EvalAltResult>::into(err).into()
}
}

View File

@ -276,6 +276,11 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
EvalAltResult::ErrorMismatchOutputType(..)
));
assert!(matches!(
*engine.parse_json("{a:42}", true).expect_err("should error"),
EvalAltResult::ErrorParsing(..)
));
assert!(matches!(
*engine
.parse_json("#{a:123}", true)