Add parse_json.
This commit is contained in:
parent
6c777e68d3
commit
e8fd965eba
@ -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.
|
* 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.
|
* 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
|
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%.
|
* 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` is now serializable and deserializable via `serde`.
|
||||||
* `Scope` now contains a const generic parameter that allows specifying how many entries to be kept inline.
|
* `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
|
Version 1.10.1
|
||||||
|
@ -295,6 +295,6 @@ impl Engine {
|
|||||||
|
|
||||||
let mut peekable = stream.peekable();
|
let mut peekable = stream.peekable();
|
||||||
let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control);
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,7 @@ impl Engine {
|
|||||||
let ast = self.parse_global_expr(
|
let ast = self.parse_global_expr(
|
||||||
&mut stream.peekable(),
|
&mut stream.peekable(),
|
||||||
&mut state,
|
&mut state,
|
||||||
|
|_| {},
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
OptimizationLevel::None,
|
OptimizationLevel::None,
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
|
@ -23,7 +23,7 @@ impl Engine {
|
|||||||
/// JSON sub-objects are handled transparently.
|
/// JSON sub-objects are handled transparently.
|
||||||
///
|
///
|
||||||
/// This function can be used together with [`format_map_as_json`] to work with JSON texts
|
/// 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
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -122,6 +122,7 @@ impl Engine {
|
|||||||
let ast = self.parse_global_expr(
|
let ast = self.parse_global_expr(
|
||||||
&mut stream.peekable(),
|
&mut stream.peekable(),
|
||||||
&mut state,
|
&mut state,
|
||||||
|
|s| s.allow_unquoted_map_properties = false,
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
OptimizationLevel::None,
|
OptimizationLevel::None,
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
@ -137,7 +138,7 @@ impl Engine {
|
|||||||
/// Not available under `no_std`.
|
/// Not available under `no_std`.
|
||||||
///
|
///
|
||||||
/// This function can be used together with [`Engine::parse_json`] to work with JSON texts
|
/// 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
|
/// # Data types
|
||||||
///
|
///
|
||||||
|
@ -88,7 +88,6 @@ mod core_functions {
|
|||||||
#[cfg(feature = "f32_float")]
|
#[cfg(feature = "f32_float")]
|
||||||
std::thread::sleep(std::time::Duration::from_secs_f32(seconds));
|
std::thread::sleep(std::time::Duration::from_secs_f32(seconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block the current thread for a particular number of `seconds`.
|
/// Block the current thread for a particular number of `seconds`.
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
pub fn sleep(seconds: INT) {
|
pub fn sleep(seconds: INT) {
|
||||||
@ -97,6 +96,23 @@ mod core_functions {
|
|||||||
}
|
}
|
||||||
std::thread::sleep(std::time::Duration::from_secs(seconds as u64));
|
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"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::engine::OP_EQUALS;
|
use crate::engine::OP_EQUALS;
|
||||||
use crate::plugin::*;
|
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")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
|
||||||
@ -304,6 +304,9 @@ mod map_functions {
|
|||||||
/// print(m.to_json()); // prints {"a":1, "b":2, "c":3}
|
/// print(m.to_json()); // prints {"a":1, "b":2, "c":3}
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to_json(map: &mut Map) -> String {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,26 +287,28 @@ impl<'e> ParseState<'e> {
|
|||||||
|
|
||||||
/// A type that encapsulates all the settings for a particular parsing function.
|
/// A type that encapsulates all the settings for a particular parsing function.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
struct ParseSettings {
|
pub(crate) struct ParseSettings {
|
||||||
/// Is the construct being parsed located at global level?
|
/// 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?
|
/// Is the construct being parsed located inside a function definition?
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
in_fn_scope: bool,
|
pub in_fn_scope: bool,
|
||||||
/// Is the construct being parsed located inside a closure definition?
|
/// Is the construct being parsed located inside a closure definition?
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
in_closure: bool,
|
pub in_closure: bool,
|
||||||
/// Is the construct being parsed located inside a breakable loop?
|
/// Is the construct being parsed located inside a breakable loop?
|
||||||
is_breakable: bool,
|
pub is_breakable: bool,
|
||||||
/// Allow statements in blocks?
|
/// 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).
|
/// Language options in effect (overrides Engine options).
|
||||||
options: LangOptions,
|
pub options: LangOptions,
|
||||||
/// Current expression nesting level.
|
/// Current expression nesting level.
|
||||||
level: usize,
|
pub level: usize,
|
||||||
/// Current position.
|
/// Current position.
|
||||||
pos: Position,
|
pub pos: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseSettings {
|
impl ParseSettings {
|
||||||
@ -996,6 +998,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (name, pos) = match input.next().expect(NEVER_ENDS) {
|
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) => {
|
(Token::Identifier(s) | Token::StringConstant(s), pos) => {
|
||||||
if map.iter().any(|(p, ..)| **p == *s) {
|
if map.iter().any(|(p, ..)| **p == *s) {
|
||||||
return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos));
|
return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos));
|
||||||
@ -3326,6 +3331,7 @@ impl Engine {
|
|||||||
in_closure: false,
|
in_closure: false,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
allow_statements: true,
|
allow_statements: true,
|
||||||
|
allow_unquoted_map_properties: settings.allow_unquoted_map_properties,
|
||||||
level: 0,
|
level: 0,
|
||||||
options,
|
options,
|
||||||
pos,
|
pos,
|
||||||
@ -3793,6 +3799,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
|
process_settings: impl Fn(&mut ParseSettings),
|
||||||
_optimization_level: OptimizationLevel,
|
_optimization_level: OptimizationLevel,
|
||||||
) -> ParseResult<AST> {
|
) -> ParseResult<AST> {
|
||||||
let mut functions = StraightHashMap::default();
|
let mut functions = StraightHashMap::default();
|
||||||
@ -3802,7 +3809,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
options.remove(LangOptions::ANON_FN);
|
options.remove(LangOptions::ANON_FN);
|
||||||
|
|
||||||
let settings = ParseSettings {
|
let mut settings = ParseSettings {
|
||||||
at_global_level: true,
|
at_global_level: true,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
in_fn_scope: false,
|
in_fn_scope: false,
|
||||||
@ -3811,10 +3818,13 @@ impl Engine {
|
|||||||
in_closure: false,
|
in_closure: false,
|
||||||
is_breakable: false,
|
is_breakable: false,
|
||||||
allow_statements: false,
|
allow_statements: false,
|
||||||
|
allow_unquoted_map_properties: true,
|
||||||
level: 0,
|
level: 0,
|
||||||
options,
|
options,
|
||||||
pos: Position::NONE,
|
pos: Position::START,
|
||||||
};
|
};
|
||||||
|
process_settings(&mut settings);
|
||||||
|
|
||||||
let expr = self.parse_expr(input, state, &mut functions, settings)?;
|
let expr = self.parse_expr(input, state, &mut functions, settings)?;
|
||||||
|
|
||||||
assert!(functions.is_empty());
|
assert!(functions.is_empty());
|
||||||
@ -3853,25 +3863,27 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
|
process_settings: impl Fn(&mut ParseSettings),
|
||||||
) -> ParseResult<(StmtBlockContainer, StaticVec<Shared<ScriptFnDef>>)> {
|
) -> ParseResult<(StmtBlockContainer, StaticVec<Shared<ScriptFnDef>>)> {
|
||||||
let mut statements = StmtBlockContainer::new_const();
|
let mut statements = StmtBlockContainer::new_const();
|
||||||
let mut functions = StraightHashMap::default();
|
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() {
|
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)?;
|
let stmt = self.parse_stmt(input, state, &mut functions, settings)?;
|
||||||
|
|
||||||
if stmt.is_noop() {
|
if stmt.is_noop() {
|
||||||
@ -3918,7 +3930,7 @@ impl Engine {
|
|||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
_optimization_level: OptimizationLevel,
|
_optimization_level: OptimizationLevel,
|
||||||
) -> ParseResult<AST> {
|
) -> 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"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
return Ok(crate::optimizer::optimize_into_ast(
|
return Ok(crate::optimizer::optimize_into_ast(
|
||||||
|
@ -251,9 +251,9 @@ impl<T: AsRef<str>> From<T> for EvalAltResult {
|
|||||||
|
|
||||||
impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
|
impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
|
||||||
#[cold]
|
#[cold]
|
||||||
#[inline(never)]
|
#[inline(always)]
|
||||||
fn from(err: T) -> Self {
|
fn from(err: T) -> Self {
|
||||||
EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into()
|
Into::<EvalAltResult>::into(err).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +276,11 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
|
|||||||
EvalAltResult::ErrorMismatchOutputType(..)
|
EvalAltResult::ErrorMismatchOutputType(..)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine.parse_json("{a:42}", true).expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorParsing(..)
|
||||||
|
));
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
.parse_json("#{a:123}", true)
|
.parse_json("#{a:123}", true)
|
||||||
|
Loading…
Reference in New Issue
Block a user