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.
|
||||
* 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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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"))]
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user