NodeJs native binding 추가

This commit is contained in:
freestrings
2019-03-14 22:30:42 +09:00
parent 1a3104c5db
commit 47e120f66d
69 changed files with 4967 additions and 1424 deletions

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] filter::array" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib filter::tests::array" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] filter::example" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib filter::tests::example" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] filter::op" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib filter::tests::op" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] filter::return_type" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib filter::tests::return_type" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] filter::step_in" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib filter::tests::step_in" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] parser::parse_array_float" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib parser::parser::tests::parse_array_float" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] parser::parse_array_sytax" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib parser::parser::tests::parse_array_sytax" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,14 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[trace] parser::parse_path" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib parser::parser::tests::parse_path" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs>
<env name="RUST_LOG" value="trace" />
</envs>
<method v="2" />
</configuration>
</component>

View File

@ -1,10 +1,10 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="all" type="CargoCommandRunConfiguration" factoryName="Cargo Command"> <configuration default="false" name="all" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />
<option name="command" value="test --lib --all" /> <option name="command" value="test --package jsonpath_lib" />
<option name="allFeatures" value="false" /> <option name="allFeatures" value="false" />
<option name="nocapture" value="true" /> <option name="nocapture" value="false" />
<option name="backtrace" value="NO" /> <option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs /> <envs />
<method v="2" /> <method v="2" />

12
.idea/runConfigurations/filter.xml generated Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="filter" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package jsonpath_lib --test filter &quot;&quot;" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="filter-all" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib filter::tests" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

12
.idea/runConfigurations/lib.xml generated Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="lib" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package jsonpath_lib --test lib &quot;&quot;" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

12
.idea/runConfigurations/parser.xml generated Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="parser" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package jsonpath_lib --test parser &quot;&quot;" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="parser-all" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib parser::parser" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tokenize-all" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --lib parser::tokenizer" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="true" />
<option name="backtrace" value="NO" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

12
.idea/runConfigurations/tokenizer.xml generated Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="tokenizer" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package jsonpath_lib --test tokenizer &quot;&quot;" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -13,9 +13,6 @@ matrix:
- rust: stable - rust: stable
env: RUST_BACKTRACE=1 env: RUST_BACKTRACE=1
# addons:
# firefox: latest
# chrome: stable
before_script: before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
@ -26,64 +23,21 @@ matrix:
- cargo test --verbose --all - cargo test --verbose --all
- cd wasm - cd wasm
- wasm-pack build - wasm-pack build
# - wasm-pack test --chrome --firefox --headless - language: node_js
node_js:
# Builds with wasm-pack. - 'node'
- rust: beta - '11'
env: RUST_BACKTRACE=1 - '10'
# addons: - '9'
# firefox: latest - '8'
# chrome: stable before_install:
before_script: - curl https://sh.rustup.rs -sSf > /tmp/rustup.sh
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - sh /tmp/rustup.sh -y
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - export PATH="$HOME/.cargo/bin:$PATH"
- cargo install-update -a - source "$HOME/.cargo/env"
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f - cd nodejs
script: - node -v
- cargo build --verbose --all - npm -v
- cargo test --verbose --all - npm install
- cd wasm script:
- wasm-pack build - npm test
# - wasm-pack test --chrome --firefox --headless
# Builds on nightly.
- rust: nightly
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo build --verbose --all
- cargo test --verbose --all
- cd wasm
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
# Builds on beta.
- rust: beta
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo build --verbose --all
- cargo test --verbose --all
- cd wasm
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
# Note: no enabling the `wee_alloc` feature here because it requires
# nightly for now.

View File

@ -30,5 +30,5 @@ name = "jsonpath_lib"
path = "src/lib.rs" path = "src/lib.rs"
[profile.release] [profile.release]
debug = true #debug = true
lto = false #lto = false

View File

@ -15,13 +15,39 @@ fn read_json(path: &str) -> String {
} }
#[bench] #[bench]
fn bench_reader(b: &mut Bencher) { fn bench_selector(b: &mut Bencher) {
let string = read_json("./benches/example.json"); let string = read_json("./benches/example.json");
let path = r#"$..book[?(@.price<30 && @.category=="fiction")]"#;
let json: Value = serde_json::from_str(string.as_str()).unwrap(); let json: Value = serde_json::from_str(string.as_str()).unwrap();
let mut reader = jsonpath::reader(json); let mut selector = jsonpath::selector(&json);
b.iter(move || { b.iter(move || {
for _ in 1..10000 { for _ in 1..1000 {
let _ = reader("$.store").unwrap(); let _ = selector(path).unwrap();
}
});
}
#[bench]
fn bench_select(b: &mut Bencher) {
let string = read_json("./benches/example.json");
let path = r#"$..book[?(@.price<30 && @.category=="fiction")]"#;
let json: Value = serde_json::from_str(string.as_str()).unwrap();
b.iter(move || {
for _ in 1..1000 {
let _ = jsonpath::select(&json, path).unwrap();
}
});
}
#[bench]
fn bench_compile(b: &mut Bencher) {
let string = read_json("./benches/example.json");
let path = r#"$..book[?(@.price<30 && @.category=="fiction")]"#;
let json: Value = serde_json::from_str(string.as_str()).unwrap();
let mut template = jsonpath::compile(path);
b.iter(move || {
for _ in 1..1000 {
let _ = template(&json).unwrap();
} }
}); });
} }

View File

@ -1,4 +1,5 @@
/target /target
**/*.rs.bk **/*.rs.bk
Cargo.lock Cargo.lock
bin/ bin/
.idea

15
benches/bench_bin/.idea/bench_bin.iml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

4
benches/bench_bin/.idea/encodings.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

9
benches/bench_bin/.idea/misc.xml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CargoProjects">
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="RustProjectSettings">
<option name="toolchainHomeDirectory" value="$USER_HOME$/.cargo/bin" />
</component>
</project>

8
benches/bench_bin/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/bench_bin.iml" filepath="$PROJECT_DIR$/.idea/bench_bin.iml" />
</modules>
</component>
</project>

6
benches/bench_bin/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

152
benches/bench_bin/.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="3fa6f740-0ee1-4afb-b0ae-9239bf5ced3d" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/../bench.rs" beforeDir="false" afterPath="$PROJECT_DIR$/../bench.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../bench_node_vs_rust.sh" beforeDir="false" afterPath="$PROJECT_DIR$/../bench_node_vs_rust.sh" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../javascript/bench.js" beforeDir="false" afterPath="$PROJECT_DIR$/../javascript/bench.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../../nodejs/lib/index.js" beforeDir="false" afterPath="$PROJECT_DIR$/../../nodejs/lib/index.js" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../../nodejs/native/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/../../nodejs/native/src/lib.rs" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../.." />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/../../src/lib.rs" />
<option value="$PROJECT_DIR$/src/main.rs" />
</list>
</option>
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="67" />
<option name="y" value="27" />
<option name="width" value="1533" />
<option name="height" value="1053" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="PackagesPane" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="bench_bin" type="b2602c69:ProjectViewProjectNode" />
<item name="bench_bin" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="bench_bin" type="b2602c69:ProjectViewProjectNode" />
<item name="bench_bin" type="462c0819:PsiDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="org.rust.cargo.project.model.PROJECT_DISCOVERY" value="true" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="3fa6f740-0ee1-4afb-b0ae-9239bf5ced3d" name="Default Changelist" comment="" />
<created>1552690262696</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1552690262696</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="67" y="25" width="1853" height="1055" extended-state="6" />
<layout>
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.23076923" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="Designer" order="2" />
<window_info id="Favorites" order="3" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="bottom" id="Run" order="2" weight="0.32829374" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="Version Control" order="7" />
<window_info anchor="bottom" id="Terminal" order="8" />
<window_info anchor="bottom" id="Event Log" order="9" side_tool="true" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="Maven" order="3" />
<window_info anchor="right" id="Cargo" order="4" />
<window_info anchor="right" id="Palette&#9;" order="5" />
</layout>
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/../../src/lib.rs">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="353">
<caret line="297" column="1" lean-forward="true" selection-start-line="297" selection-start-column="1" selection-end-line="297" selection-end-column="1" />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.39/src/de.rs">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="279">
<caret line="2311" column="47" selection-start-line="2311" selection-start-column="47" selection-end-line="2311" selection-end-column="47" />
</state>
</provider>
</entry>
<entry file="file://$USER_HOME$/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/str/mod.rs">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="197">
<caret line="3895" column="11" selection-start-line="3895" selection-start-column="11" selection-end-line="3895" selection-end-column="11" />
<folding>
<element signature="e#126082#126083#0" expanded="true" />
<element signature="e#126120#126121#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main.rs">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-680">
<caret line="6" column="13" selection-start-line="6" selection-start-column="13" selection-end-line="6" selection-end-column="13" />
</state>
</provider>
</entry>
</component>
</project>

View File

@ -2,7 +2,7 @@
set -e set -e
if [ -d "target/release" ]; then if [ -d "target/release" ]; then
./target/release/bench_bin ./target/release/bench_bin $1 $2
else else
echo "빌드먼저" echo "빌드먼저"
fi fi

View File

@ -4,6 +4,8 @@ extern crate serde_json;
use serde_json::Value; use serde_json::Value;
use std::io::Read; use std::io::Read;
use std::env;
fn read_json(path: &str) -> String { fn read_json(path: &str) -> String {
let mut f = std::fs::File::open(path).unwrap(); let mut f = std::fs::File::open(path).unwrap();
let mut contents = String::new(); let mut contents = String::new();
@ -14,8 +16,33 @@ fn read_json(path: &str) -> String {
fn main() { fn main() {
let string = read_json("../example.json"); let string = read_json("../example.json");
let json: Value = serde_json::from_str(string.as_str()).unwrap(); let json: Value = serde_json::from_str(string.as_str()).unwrap();
let mut selector = jsonpath::selector(json); let path = r#"$..book[?(@.price<30 && @.category=="fiction")]"#;
for _ in 1..100000 {
let _ = selector(r#"$..book[?(@.price<30 && @.category=="fiction")]"#).unwrap(); let args: Vec<String> = env::args().collect();
let iter = match &args[2].as_str().parse::<usize>() {
Ok(iter) => *iter,
_ => 100000
};
match &args[1].as_str() {
&"compile" => {
let mut template = jsonpath::compile(path);
for _ in 1..iter {
let _ = template(&json).unwrap();
}
}
&"selector" => {
let mut selector = jsonpath::selector(&json);
for _ in 1..iter {
let _ = selector(path).unwrap();
}
}
&"select" => {
let json: Value = serde_json::from_str(string.as_str()).unwrap();
for _ in 1..iter {
let _ = jsonpath::select(&json, path).unwrap();
}
}
_ => panic!("Invalid argument")
} }
} }

View File

@ -5,19 +5,42 @@ DIR="$(pwd)"
cd "${DIR}"/bench_bin && cargo build --release cd "${DIR}"/bench_bin && cargo build --release
printf "\n\n$..book[?(@.price<30 && @.category=="fiction")] (loop 100,000)" ITER=100000
printf "\n\n$..book[?(@.price<30 && @.category=="fiction")] (loop ${ITER})"
printf "\n\n" printf "\n\n"
echo "Rust: " && time ./bench.sh #echo "Rust - compile: " && time ./bench.sh compile ${ITER}
#printf "\n"
#sleep 1
#echo "Rust - selector: " && time ./bench.sh selector ${ITER}
#printf "\n"
#sleep 1
echo "Rust - select: " && time ./bench.sh select ${ITER}
printf "\n" printf "\n"
cd "${DIR}"/javascript && echo "NodeJs - jsonpath module: " && time ./bench.sh jsonpath sleep 1
cd "${DIR}"/javascript && echo "NodeJs - jsonpath: " && time ./bench.sh jsonpath ${ITER}
printf "\n" printf "\n"
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm module - selector: " && time ./bench.sh wasmSelector sleep 1
#cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - selector: " && time ./bench.sh wasmSelector ${ITER}
#printf "\n"
#sleep 1
#cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - compile: " && time ./bench.sh wasmCompile ${ITER}
#printf "\n"
#sleep 1
#cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - compile-alloc: " && time ./bench.sh wasmCompileAlloc ${ITER}
#printf "\n"
#sleep 1
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - select:" && time ./bench.sh wasmSelect ${ITER}
printf "\n" printf "\n"
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm module - compile: " && time ./bench.sh wasmCompile sleep 1
printf "\n" #cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - select-alloc:" && time ./bench.sh wasmSelectAlloc ${ITER}
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm module - compile-alloc: " && time ./bench.sh wasmCompileAlloc #printf "\n"
printf "\n" #sleep 1
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm module - select:" && time ./bench.sh wasmSelect #cd "${DIR}"/javascript && echo "NodeJs - jsonpath-rs - compile:" && time ./bench.sh nativeCompile ${ITER}
printf "\n" #printf "\n"
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm module - select-alloc:" && time ./bench.sh wasmSelectAlloc #sleep 1
#cd "${DIR}"/javascript && echo "NodeJs - jsonpath-rs - selector:" && time ./bench.sh nativeSelector ${ITER}
#printf "\n"
#sleep 1
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-rs - select:" && time ./bench.sh nativeSelect ${ITER}

View File

@ -35,40 +35,65 @@ let json = {
}, },
'expensive': 10, 'expensive': 10,
}; };
let jsonStr = JSON.stringify(json);
function getJson() {
return JSON.parse(jsonStr);
}
const path = '$..book[?(@.price<30 && @.category=="fiction")]';
const jp = require('jsonpath'); const jp = require('jsonpath');
const jpw = require('@nodejs/jsonpath-wasm'); const jpw = require('@nodejs/jsonpath-wasm');
const iter = 100000; const jpwRs = require('jsonpath-rs');
function jsonpath() { function jsonpath() {
for (var i = 0; i < iter; i++) { for (var i = 0; i < iter; i++) {
let _ = jp.query(json, '$..book[?(@.price<30 && @.category=="fiction")]'); let _ = jp.query(getJson(), path);
}
}
function nativeCompile() {
let template = jpwRs.compile(path);
for (var i = 0; i < iter; i++) {
let _ = template(JSON.stringify(json));
}
}
function nativeSelector() {
let selector = jpwRs.selector(getJson());
for (var i = 0; i < iter; i++) {
let _ = selector(path);
}
}
function nativeSelect() {
for (var i = 0; i < iter; i++) {
let _ = jpwRs.select(JSON.stringify(json), path);
} }
} }
function wasmSelector() { function wasmSelector() {
let selector = jpw.selector(json); let selector = jpw.selector(getJson());
for (var i = 0; i < iter; i++) { for (var i = 0; i < iter; i++) {
let _ = selector('$..book[?(@.price<30 && @.category=="fiction")]'); let _ = selector(path);
} }
} }
function wasmCompile() { function wasmCompile() {
let template = jpw.compile('$..book[?(@.price<30 && @.category=="fiction")]'); let template = jpw.compile(path);
for (var i = 0; i < iter; i++) { for (var i = 0; i < iter; i++) {
let _ = template(json); let _ = template(getJson());
} }
} }
function wasmCompileAlloc() { function wasmCompileAlloc() {
let ptr = jpw.alloc_json(json); let ptr = jpw.alloc_json(getJson());
if (ptr == 0) { if (ptr == 0) {
console.error('Invalid pointer'); console.error('Invalid pointer');
return; return;
} }
try { try {
let template = jpw.compile('$..book[?(@.price<30 && @.category=="fiction")]'); let template = jpw.compile(path);
for (var i = 0; i < iter; i++) { for (var i = 0; i < iter; i++) {
let _ = template(ptr); let _ = template(ptr);
} }
@ -79,12 +104,12 @@ function wasmCompileAlloc() {
function wasmSelect() { function wasmSelect() {
for (var i = 0; i < iter; i++) { for (var i = 0; i < iter; i++) {
let _ = jpw.select(json, '$..book[?(@.price<30 && @.category=="fiction")]'); let _ = jpw.select(getJson(), path);
} }
} }
function wasmSelectAlloc() { function wasmSelectAlloc() {
let ptr = jpw.alloc_json(json); let ptr = jpw.alloc_json(getJson());
if (ptr == 0) { if (ptr == 0) {
console.error('Invalid pointer'); console.error('Invalid pointer');
return; return;
@ -92,33 +117,13 @@ function wasmSelectAlloc() {
try { try {
for (var i = 0; i < iter; i++) { for (var i = 0; i < iter; i++) {
let _ = jpw.select(ptr, '$..book[?(@.price<30 && @.category=="fiction")]'); let _ = jpw.select(ptr, path);
} }
} finally { } finally {
jpw.dealloc_json(ptr); jpw.dealloc_json(ptr);
} }
} }
let functionName = process.argv[2]; const functionName = process.argv[2];
const iter = parseInt(process.argv[3], 10);
switch (functionName) { eval(functionName + "()");
case 'jsonpath':
jsonpath();
break;
case 'wasmSelector':
wasmSelector();
break;
case 'wasmCompile':
wasmCompile();
break;
case 'wasmSelect':
wasmSelect();
break;
case 'wasmCompileAlloc':
wasmCompileAlloc();
break;
case 'wasmSelectAlloc':
wasmSelectAlloc();
default:
console.error('Invalid function name');
}

View File

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
node bench.js $1 node bench.js $1 $2

88
build.sh Executable file
View File

@ -0,0 +1,88 @@
#!/bin/bash
set -e
# project_root
DIR="$(pwd)"
WASM="${DIR}"/wasm
WASM_WWW="${WASM}"/www
WASM_WWW_BENCH="${WASM}"/www_bench
WASM_BROWSER_PKG="${WASM}"/browser_pkg
WASM_NODEJS_PKG="${WASM}"/nodejs_pkg
BENCHES="${DIR}"/benches
BENCHES_JS="${BENCHES}"/javascript
NODEJS="${DIR}"/nodejs
DOCS="${DIR}"/docs
DOCS_BENCH="${DOCS}"/bench
__msg () {
echo ">>>>>>>>>>$1<<<<<<<<<<"
}
__cargo_clean () {
cd "${BENCHES}"/bench_bin && cargo clean && \
cd "${NODEJS}"/native && cargo clean && \
cd "${WASM}" && cargo clean && \
cd "${DIR}" && cargo clean
}
echo
__msg "clean"
rm -rf \
"${WASM_NODEJS_PKG}" \
"${WASM_BROWSER_PKG}" \
"${BENCHES_JS}"/node_modules \
"${NODEJS}"/node_modules \
"${WASM_WWW}"/node_modules \
"${WASM_WWW_BENCH}"/node_modules \
"${WASM_WWW}"/dist \
"${WASM_WWW_BENCH}"/dist
if [ "$1" = "all" ]; then
__msg "clean targets"
__cargo_clean
fi
__msg "npm install"
echo
cd "${WASM_WWW}" && npm install
cd "${WASM_WWW_BENCH}" && npm install
cd "${NODEJS}" && npm install
cd "${BENCHES_JS}" && npm install
echo
echo
__msg "wasm-pack"
cd "${WASM}" && \
wasm-pack build --target=nodejs --scope nodejs --out-dir nodejs_pkg && \
cd "${WASM_NODEJS_PKG}" && npm link
cd "${WASM}" && \
wasm-pack build --target=browser --scope browser --out-dir browser_pkg && \
cd "${WASM_BROWSER_PKG}" && npm link
echo
__msg "link"
cd "${WASM_WWW}" && \
npm link @browser/jsonpath-wasm
cd "${WASM_WWW_BENCH}" && \
npm link @browser/jsonpath-wasm
cd "${BENCHES_JS}" && \
npm link @nodejs/jsonpath-wasm && \
npm link jsonpath-rs
echo
__msg "docs"
cd "${WASM_WWW}" && \
npm run build &&
rm -f "${DOCS}"/*.js "${DOCS}"/*.wasm "${DOCS}"/*.html && \
cp "${WASM_WWW}"/dist/*.* "${DOCS}"/
cd "${WASM_WWW_BENCH}" && \
npm run build &&
rm -f "${DOCS_BENCH}"/*.js "${DOCS_BENCH}"/*.wasm "${DOCS_BENCH}"/*.html && \
cp "${WASM_WWW_BENCH}"/dist/*.* "${DOCS_BENCH}"/
__msg "done"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -61,9 +61,6 @@
/******/ "__wbindgen_object_drop_ref": function(p0i32) { /******/ "__wbindgen_object_drop_ref": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_drop_ref"](p0i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_drop_ref"](p0i32);
/******/ }, /******/ },
/******/ "__wbindgen_object_clone_ref": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_clone_ref"](p0i32);
/******/ },
/******/ "__wbindgen_cb_forget": function(p0i32) { /******/ "__wbindgen_cb_forget": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_cb_forget"](p0i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_cb_forget"](p0i32);
/******/ }, /******/ },
@ -85,14 +82,17 @@
/******/ "__wbindgen_string_get": function(p0i32,p1i32) { /******/ "__wbindgen_string_get": function(p0i32,p1i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_string_get"](p0i32,p1i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_string_get"](p0i32,p1i32);
/******/ }, /******/ },
/******/ "__wbindgen_object_clone_ref": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_clone_ref"](p0i32);
/******/ },
/******/ "__wbindgen_throw": function(p0i32,p1i32) { /******/ "__wbindgen_throw": function(p0i32,p1i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32);
/******/ }, /******/ },
/******/ "__wbindgen_closure_wrapper99": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper99"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbindgen_closure_wrapper101": function(p0i32,p1i32,p2i32) { /******/ "__wbindgen_closure_wrapper101": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper101"](p0i32,p1i32,p2i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper101"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbindgen_closure_wrapper103": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper103"](p0i32,p1i32,p2i32);
/******/ } /******/ }
/******/ } /******/ }
/******/ }; /******/ };
@ -192,7 +192,7 @@
/******/ promises.push(installedWasmModuleData); /******/ promises.push(installedWasmModuleData);
/******/ else { /******/ else {
/******/ var importObject = wasmImportObjects[wasmModuleId](); /******/ var importObject = wasmImportObjects[wasmModuleId]();
/******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"9a826648f4cbc2bc8591"}[wasmModuleId] + ".module.wasm"); /******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"db8564aae9d99ec41b79"}[wasmModuleId] + ".module.wasm");
/******/ var promise; /******/ var promise;
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') { /******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) { /******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {

Binary file not shown.

14
docs/bootstrap.js vendored
View File

@ -61,9 +61,6 @@
/******/ "__wbindgen_object_drop_ref": function(p0i32) { /******/ "__wbindgen_object_drop_ref": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_drop_ref"](p0i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_drop_ref"](p0i32);
/******/ }, /******/ },
/******/ "__wbindgen_object_clone_ref": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_clone_ref"](p0i32);
/******/ },
/******/ "__wbindgen_cb_forget": function(p0i32) { /******/ "__wbindgen_cb_forget": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_cb_forget"](p0i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_cb_forget"](p0i32);
/******/ }, /******/ },
@ -85,14 +82,17 @@
/******/ "__wbindgen_string_get": function(p0i32,p1i32) { /******/ "__wbindgen_string_get": function(p0i32,p1i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_string_get"](p0i32,p1i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_string_get"](p0i32,p1i32);
/******/ }, /******/ },
/******/ "__wbindgen_object_clone_ref": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_object_clone_ref"](p0i32);
/******/ },
/******/ "__wbindgen_throw": function(p0i32,p1i32) { /******/ "__wbindgen_throw": function(p0i32,p1i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32);
/******/ }, /******/ },
/******/ "__wbindgen_closure_wrapper99": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper99"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbindgen_closure_wrapper101": function(p0i32,p1i32,p2i32) { /******/ "__wbindgen_closure_wrapper101": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper101"](p0i32,p1i32,p2i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper101"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbindgen_closure_wrapper103": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper103"](p0i32,p1i32,p2i32);
/******/ } /******/ }
/******/ } /******/ }
/******/ }; /******/ };
@ -192,7 +192,7 @@
/******/ promises.push(installedWasmModuleData); /******/ promises.push(installedWasmModuleData);
/******/ else { /******/ else {
/******/ var importObject = wasmImportObjects[wasmModuleId](); /******/ var importObject = wasmImportObjects[wasmModuleId]();
/******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"9a826648f4cbc2bc8591"}[wasmModuleId] + ".module.wasm"); /******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"db8564aae9d99ec41b79"}[wasmModuleId] + ".module.wasm");
/******/ var promise; /******/ var promise;
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') { /******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) { /******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {

Binary file not shown.

6
nodejs/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
native/target
native/index.node
native/artifacts.json
**/*~
**/node_modules
.idea

3
nodejs/README.md Normal file
View File

@ -0,0 +1,3 @@
# jsonpath-rs
JsonPath engine for NodeJs with Rust native implementation.

34
nodejs/lib/index.js Normal file
View File

@ -0,0 +1,34 @@
const { Compile, Selector, selectStr } = require('../native');
function compile(path) {
let compile = new Compile(path);
return (json) => {
if(typeof json != 'string') {
json = JSON.stringify(json)
}
return compile.template(json);
};
}
function selector(json) {
if(typeof json != 'string') {
json = JSON.stringify(json)
}
let selector = new Selector(json);
return (path) => {
return selector.selector(path);
}
}
function select(json, path) {
if(typeof json != 'string') {
json = JSON.stringify(json)
}
return selectStr(json, path);
}
module.exports = {
compile,
selector,
select
};

5
nodejs/native/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.idea/*
.vscode
!.idea/runConfigurations/
/target/
Cargo.lock

24
nodejs/native/Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "jsonpath-rs"
version = "0.1.0"
authors = ["Changseok Han <freestrings@gmail.com>"]
description = "JsonPath engine for NodeJs with Rust native implementation."
keywords = ["library", "jsonpath", "json"]
repository = "https://github.com/freestrings/jsonpath"
license = "MIT"
build = "build.rs"
exclude = ["artifacts.json", "index.node"]
[build-dependencies]
neon-build = "0.2.0"
[dependencies]
jsonpath_lib = { path = "../../" }
neon = "0.2.0"
neon-serde = "0.1.1"
serde_json = { version = "1.0", features = ["preserve_order"] }
[lib]
name = "jsonpath_rs"
crate-type = ["dylib"]

7
nodejs/native/build.rs Normal file
View File

@ -0,0 +1,7 @@
extern crate neon_build;
fn main() {
neon_build::setup(); // must be called in build.rs
// add project-specific build logic here...
}

126
nodejs/native/src/lib.rs Normal file
View File

@ -0,0 +1,126 @@
extern crate jsonpath_lib as jsonpath;
#[macro_use]
extern crate neon;
extern crate neon_serde;
extern crate serde_json;
use jsonpath::prelude::*;
use neon::prelude::*;
use serde_json::Value;
///
/// `neon_serde::from_value` has very poor performance.
///
fn select(mut ctx: FunctionContext) -> JsResult<JsValue> {
let json_val = ctx.argument::<JsValue>(0)?;
let json: Value = neon_serde::from_value(&mut ctx, json_val)?;
let path = ctx.argument::<JsString>(1)?.value();
match jsonpath::select(&json, path.as_str()) {
Ok(value) => Ok(neon_serde::to_value(&mut ctx, &value)?),
Err(e) => panic!("{:?}", e)
}
}
fn select_str(mut ctx: FunctionContext) -> JsResult<JsValue> {
let json_val = ctx.argument::<JsString>(0)?.value();
let json: Value = match serde_json::from_str(json_val.as_str()) {
Ok(json) => json,
Err(e) => panic!("{:?}", e)
};
let path = ctx.argument::<JsString>(1)?.value();
match jsonpath::select(&json, path.as_str()) {
Ok(value) => Ok(neon_serde::to_value(&mut ctx, &value)?),
Err(e) => panic!("{:?}", e)
}
}
pub struct Compile {
node: Node
}
pub struct Selector {
json: RefValueWrapper
}
declare_types! {
pub class JsCompile for Compile {
init(mut ctx) {
let path = ctx.argument::<JsString>(0)?.value();
let mut parser = Parser::new(path.as_str());
let node = match parser.compile() {
Ok(node) => node,
Err(e) => panic!("{:?}", e)
};
Ok(Compile { node })
}
method template(mut ctx) {
let this = ctx.this();
let node = {
let guard = ctx.lock();
let this = this.borrow(&guard);
this.node.clone()
};
// let o = ctx.argument::<JsValue>(0)?;
// let json: Value = neon_serde::from_value(&mut ctx, o)?;
let json_str = ctx.argument::<JsString>(0)?.value();
let json: Value = match serde_json::from_str(&json_str) {
Ok(json) => json,
Err(e) => panic!("{:?}", e)
};
let mut jf = JsonValueFilter::new_from_value((&json).into());
jf.visit(node);
let v = jf.take_value().into_value();
Ok(neon_serde::to_value(&mut ctx, &v)?)
}
}
pub class JsSelector for Selector {
init(mut ctx) {
// let o = ctx.argument::<JsValue>(0)?;
// let json: Value = neon_serde::from_value(&mut ctx, o)?;
let json_str = ctx.argument::<JsString>(0)?.value();
let json: Value = match serde_json::from_str(&json_str) {
Ok(json) => json,
Err(e) => panic!("{:?}", e)
};
Ok(Selector { json: (&json).into() })
}
method selector(mut ctx) {
let this = ctx.this();
let json = {
let guard = ctx.lock();
let this = this.borrow(&guard);
this.json.clone()
};
let path = ctx.argument::<JsString>(0)?.value();
let mut parser = Parser::new(path.as_str());
let node = match parser.compile() {
Ok(node) => node,
Err(e) => panic!("{:?}", e)
};
let mut jf = JsonValueFilter::new_from_value(json);
jf.visit(node);
let v = jf.take_value().into_value();
Ok(neon_serde::to_value(&mut ctx, &v)?)
}
}
}
register_module!(mut m, {
m.export_class::<JsCompile>("Compile").expect("Compile class error");
m.export_class::<JsSelector>("Selector").expect("Selector class error");
m.export_function("select", select)?;
m.export_function("selectStr", select_str)?;
Ok(())
});

3172
nodejs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
nodejs/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "jsonpath-rs",
"version": "0.1.0",
"description": "JsonPath engine for NodeJs with Rust native implementation.",
"author": "Changseok Han <freestrings@gmail.com>",
"repository": "git+https://github.com/freestrings/jsonpath",
"license": "MIT",
"main": "lib/index.js",
"dependencies": {
"neon-cli": "^0.2.0",
"node-pre-gyp": "0.6"
},
"scripts": {
"install": "neon build --release",
"test": "mocha"
},
"devDependencies": {
"mocha": "^6.0.2"
}
}

30
nodejs/test/index.spec.js Normal file
View File

@ -0,0 +1,30 @@
const jsonpath = require('../lib/index.js');
describe('compile test', () => {
it('basic', (done) => {
let template = jsonpath.compile('$.a');
let result = template({'a': 1});
if (result == 1) {
done();
}
});
});
describe('selector test', () => {
it('basic', (done) => {
let selector = jsonpath.selector({'a': 1});
let result = selector('$.a');
if (result == 1) {
done();
}
});
});
describe('select test', () => {
it('basic', (done) => {
let result = jsonpath.select({'a': 1}, '$.a');
if (result == 1) {
done();
}
});
});

View File

@ -1,353 +1,5 @@
use super::parser::*;
mod cmp; mod cmp;
mod term; mod term;
pub mod value_filter; mod value_filter;
mod value_wrapper; mod value_wrapper;
pub mod prelude;
#[cfg(test)]
mod tests {
extern crate env_logger;
use std::io::Read;
use serde_json::Value;
use super::parser::Parser;
use super::value_filter::*;
fn setup() {
let _ = env_logger::try_init();
}
fn new_value_filter(file: &str) -> ValueFilter {
let string = read_json(file);
let json: Value = serde_json::from_str(string.as_str()).unwrap();
ValueFilter::new(json.into(), false, false)
}
fn do_filter(path: &str, file: &str) -> JsonValueFilter {
let string = read_json(file);
let mut jf = JsonValueFilter::new(string.as_str()).unwrap();
let mut parser = Parser::new(path);
parser.parse(&mut jf).unwrap();
jf
}
fn read_json(path: &str) -> String {
let mut f = std::fs::File::open(path).unwrap();
let mut contents = String::new();
f.read_to_string(&mut contents).unwrap();
contents
}
#[test]
fn step_in() {
setup();
let mut jf = new_value_filter("./benches/data_obj.json");
{
let current = jf.step_in_str("friends");
assert_eq!(current.is_array(), true);
}
let mut jf = new_value_filter("./benches/data_array.json");
{
let current = jf.step_in_num(&1.0);
assert_eq!(current.get_val().is_object(), true);
}
{
let current = jf.step_in_str("friends");
assert_eq!(current.is_array(), true);
}
let mut jf = new_value_filter("./benches/data_obj.json");
{
jf.step_in_str("school");
jf.step_in_str("friends");
jf.step_in_all();
let current = jf.step_in_str("name");
let friends = json!([
"Millicent Norman",
"Vincent Cannon",
"Gray Berry"
]);
assert_eq!(friends, current.get_val().into_value());
}
let mut jf = new_value_filter("./benches/data_obj.json");
{
let current = jf.step_leaves_str("name");
let names = json!([
"Leonor Herman",
"Millicent Norman",
"Vincent Cannon",
"Gray Berry",
"Vincent Cannon",
"Gray Berry"
]);
assert_eq!(names, current.get_val().into_value());
}
}
#[test]
fn array() {
setup();
let friends = json!([
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]);
let jf = do_filter("$.school.friends[1, 2]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school.friends[1:]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school.friends[:-2]", "./benches/data_obj.json");
let friends = json!([
{"id": 0, "name": "Millicent Norman"}
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..friends[2].name", "./benches/data_obj.json");
let friends = json!(["Gray Berry", "Gray Berry"]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..friends[*].name", "./benches/data_obj.json");
let friends = json!(["Vincent Cannon","Gray Berry","Millicent Norman","Vincent Cannon","Gray Berry"]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$['school']['friends'][*].['name']", "./benches/data_obj.json");
let friends = json!(["Millicent Norman","Vincent Cannon","Gray Berry"]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$['school']['friends'][0].['name']", "./benches/data_obj.json");
let friends = json!("Millicent Norman");
assert_eq!(friends, jf.current_value().into_value());
}
#[test]
fn return_type() {
setup();
let friends = json!({
"friends": [
{"id": 0, "name": "Millicent Norman"},
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]
});
let jf = do_filter("$.school", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school[?(@.friends[0])]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school[?(@.friends[10])]", "./benches/data_obj.json");
assert_eq!(Value::Null, jf.current_value().into_value());
let jf = do_filter("$.school[?(1==1)]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school.friends[?(1==1)]", "./benches/data_obj.json");
let friends = json!([
{"id": 0, "name": "Millicent Norman"},
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]);
assert_eq!(friends, jf.current_value().into_value());
}
#[test]
fn op() {
setup();
let jf = do_filter("$.school[?(@.friends == @.friends)]", "./benches/data_obj.json");
let friends = json!({
"friends": [
{"id": 0, "name": "Millicent Norman"},
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]
});
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?(@.name)]", "./benches/data_obj.json");
let friends = json!([
{ "id" : 1, "name" : "Vincent Cannon" },
{ "id" : 2, "name" : "Gray Berry" }
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?(@.id >= 2)]", "./benches/data_obj.json");
let friends = json!([
{ "id" : 2, "name" : "Gray Berry" }
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?(@.id >= 2 || @.id == 1)]", "./benches/data_obj.json");
let friends = json!([
{ "id" : 2, "name" : "Gray Berry" },
{ "id" : 1, "name" : "Vincent Cannon" }
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?( (@.id >= 2 || @.id == 1) && @.id == 0)]", "./benches/data_obj.json");
assert_eq!(Value::Null, jf.current_value().into_value());
let jf = do_filter("$..friends[?(@.id == $.index)].id", "./benches/data_obj.json");
let friends = json!([0, 0]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..book[?($.store.bicycle.price < @.price)].price", "./benches/example.json");
let friends = json!([22.99]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..book[?( (@.price == 12.99 || @.category == 'reference') && @.price > 10)].price", "./benches/example.json");
let friends = json!([12.99]);
assert_eq!(friends, jf.current_value().into_value());
}
#[test]
fn example() {
setup();
let jf = do_filter("$.store.book[*].author", "./benches/example.json");
let ret = json!(["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..author", "./benches/example.json");
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$.store.*", "./benches/example.json");
let ret = json!([
[
{"category" : "reference", "author" : "Nigel Rees","title" : "Sayings of the Century", "price" : 8.95},
{"category" : "fiction", "author" : "Evelyn Waugh","title" : "Sword of Honour","price" : 12.99},
{"category" : "fiction", "author" : "Herman Melville","title" : "Moby Dick","isbn" : "0-553-21311-3","price" : 8.99},
{"category" : "fiction", "author" : "J. R. R. Tolkien","title" : "The Lord of the Rings","isbn" : "0-395-19395-8","price" : 22.99}
],
{"color" : "red","price" : 19.95},
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$.store..price", "./benches/example.json");
let ret = json!([8.95, 12.99, 8.99, 22.99, 19.95]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[2]", "./benches/example.json");
let ret = json!([{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[-2]", "./benches/example.json");
let ret = json!([{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[0,1]", "./benches/example.json");
let ret = json!([
{
"category" : "reference",
"author" : "Nigel Rees",
"title" : "Sayings of the Century",
"price" : 8.95
},
{
"category" : "fiction",
"author" : "Evelyn Waugh",
"title" : "Sword of Honour",
"price" : 12.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[:2]", "./benches/example.json");
let ret = json!([
{
"category" : "reference",
"author" : "Nigel Rees",
"title" : "Sayings of the Century",
"price" : 8.95
},
{
"category" : "fiction",
"author" : "Evelyn Waugh",
"title" : "Sword of Honour",
"price" : 12.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[2:]", "./benches/example.json");
let ret = json!([
{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
},
{
"category" : "fiction",
"author" : "J. R. R. Tolkien",
"title" : "The Lord of the Rings",
"isbn" : "0-395-19395-8",
"price" : 22.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[?(@.isbn)]", "./benches/example.json");
let ret = json!([
{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
},
{
"category" : "fiction",
"author" : "J. R. R. Tolkien",
"title" : "The Lord of the Rings",
"isbn" : "0-395-19395-8",
"price" : 22.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$.store.book[?(@.price < 10)]", "./benches/example.json");
let ret = json!([
{
"category" : "reference",
"author" : "Nigel Rees",
"title" : "Sayings of the Century",
"price" : 8.95
},
{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..*", "./benches/example.json");
let json: Value = serde_json::from_str(read_json("./benches/giveme_every_thing_result.json").as_str()).unwrap();
assert_eq!(json, jf.current_value().into_value());
}
}

1
src/filter/prelude.rs Normal file
View File

@ -0,0 +1 @@
pub use super::value_filter::*;

View File

@ -1,13 +1,13 @@
use std::error::Error; use std::error::Error;
use std::result; use std::result::Result;
use ref_value::*;
use serde_json::Value; use serde_json::Value;
use super::parser::*; use ref_value::*;
use super::term::*;
use super::value_wrapper::*; use parser::prelude::*;
use filter::term::*;
use filter::value_wrapper::*;
trait ArrayIndex { trait ArrayIndex {
fn index(&self, v: &RefValueWrapper) -> usize; fn index(&self, v: &RefValueWrapper) -> usize;
@ -277,10 +277,10 @@ pub struct JsonValueFilter {
} }
impl JsonValueFilter { impl JsonValueFilter {
pub fn new(json: &str) -> result::Result<Self, String> { pub fn new(json: &str) -> Result<Self, String> {
let json: Value = serde_json::from_str(json) let json: Value = serde_json::from_str(json)
.map_err(|e| e.description().to_string())?; .map_err(|e| e.description().to_string())?;
Ok(JsonValueFilter::new_from_value(json.into())) Ok(JsonValueFilter::new_from_value((&json).into()))
} }
pub fn new_from_value(json: RefValueWrapper) -> Self { pub fn new_from_value(json: RefValueWrapper) -> Self {

View File

@ -1,4 +1,5 @@
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
use ref_value::*; use ref_value::*;
use super::cmp::*; use super::cmp::*;

View File

@ -43,7 +43,7 @@
//! "expensive": 10 //! "expensive": 10
//! }); //! });
//! //!
//! let mut selector = jsonpath::selector(json_obj); //! let mut selector = jsonpath::selector(&json_obj);
//! //!
//! // //! //
//! // $.store.book[*].author //! // $.store.book[*].author
@ -158,28 +158,22 @@
//! assert_eq!(ret, json); //! assert_eq!(ret, json);
//! ``` //! ```
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate env_logger; extern crate env_logger;
#[cfg(test)]
#[macro_use] extern crate serde_json;
#[cfg(not(test))]
extern crate serde_json; extern crate serde_json;
extern crate core;
extern crate indexmap; extern crate indexmap;
#[doc(hidden)] #[doc(hidden)]
pub mod parser; mod parser;
#[doc(hidden)] #[doc(hidden)]
pub mod filter; mod filter;
#[doc(hidden)] #[doc(hidden)]
pub mod ref_value; mod ref_value;
pub mod prelude;
use parser::parser::*; use parser::prelude::*;
use filter::value_filter::*; use filter::prelude::*;
use std::result; use std::result;
use serde_json::Value; use serde_json::Value;
@ -204,7 +198,7 @@ type Result = result::Result<Value, String>;
/// "friends": [ {"id": 0}, {"id": 1} ] /// "friends": [ {"id": 0}, {"id": 1} ]
/// }); /// });
/// ///
/// let json = template(json_obj).unwrap(); /// let json = template(&json_obj).unwrap();
/// let ret = json!([ {"id": 0}, {"id": 0} ]); /// let ret = json!([ {"id": 0}, {"id": 0} ]);
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ///
@ -216,11 +210,11 @@ type Result = result::Result<Value, String>;
/// "friends": [ {"id": 0}, {"id": 1} ] /// "friends": [ {"id": 0}, {"id": 1} ]
/// }); /// });
/// ///
/// let json = template(json_obj).unwrap(); /// let json = template(&json_obj).unwrap();
/// let ret = json!([ {"id": 0}, {"name": "Millicent Norman"} ]); /// let ret = json!([ {"id": 0}, {"name": "Millicent Norman"} ]);
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ``` /// ```
pub fn compile<'a>(path: &'a str) -> impl FnMut(Value) -> Result + 'a { pub fn compile<'a>(path: &'a str) -> impl FnMut(&Value) -> Result + 'a {
let mut parser = Parser::new(path); let mut parser = Parser::new(path);
let node = parser.compile(); let node = parser.compile();
move |json| { move |json| {
@ -249,7 +243,7 @@ pub fn compile<'a>(path: &'a str) -> impl FnMut(Value) -> Result + 'a {
/// "friends": [{"id": 0},{"id": 1}] /// "friends": [{"id": 0},{"id": 1}]
/// }); /// });
/// ///
/// let mut selector = jsonpath::selector(json_obj); /// let mut selector = jsonpath::selector(&json_obj);
/// ///
/// let json = selector("$..friends[0]").unwrap(); /// let json = selector("$..friends[0]").unwrap();
/// let ret = json!([ {"id": 0}, {"id": 0} ]); /// let ret = json!([ {"id": 0}, {"id": 0} ]);
@ -259,7 +253,7 @@ pub fn compile<'a>(path: &'a str) -> impl FnMut(Value) -> Result + 'a {
/// let ret = json!([ {"id": 1}, {"id": 1} ]); /// let ret = json!([ {"id": 1}, {"id": 1} ]);
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ``` /// ```
pub fn selector(json: Value) -> impl FnMut(&str) -> Result { pub fn selector(json: &Value) -> impl FnMut(&str) -> Result {
let wrapper: RefValueWrapper = json.into(); let wrapper: RefValueWrapper = json.into();
move |path: &str| { move |path: &str| {
let mut jf = JsonValueFilter::new_from_value(wrapper.clone()); let mut jf = JsonValueFilter::new_from_value(wrapper.clone());
@ -270,7 +264,7 @@ pub fn selector(json: Value) -> impl FnMut(&str) -> Result {
} }
/// # Read the same Json multiple times using different JsonPath - Deprecated. use selector /// # Read the same Json multiple times using different JsonPath - Deprecated. use selector
pub fn reader(json: Value) -> impl FnMut(&str) -> Result { pub fn reader(json: &Value) -> impl FnMut(&str) -> Result {
selector(json) selector(json)
} }
@ -286,11 +280,11 @@ pub fn reader(json: Value) -> impl FnMut(&str) -> Result {
/// }, /// },
/// "friends": [{"id": 0}, {"id": 1}] /// "friends": [{"id": 0}, {"id": 1}]
/// }); /// });
/// let json = jsonpath::select(json_obj, "$..friends[0]").unwrap(); /// let json = jsonpath::select(&json_obj, "$..friends[0]").unwrap();
/// let ret = json!([ {"id": 0}, {"id": 0} ]); /// let ret = json!([ {"id": 0}, {"id": 0} ]);
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ``` /// ```
pub fn select(json: Value, path: &str) -> Result { pub fn select(json: &Value, path: &str) -> Result {
let mut jf = JsonValueFilter::new_from_value(json.into()); let mut jf = JsonValueFilter::new_from_value(json.into());
let mut parser = Parser::new(path); let mut parser = Parser::new(path);
parser.parse(&mut jf)?; parser.parse(&mut jf)?;
@ -298,73 +292,7 @@ pub fn select(json: Value, path: &str) -> Result {
} }
/// # Read Json using JsonPath - Deprecated. use select /// # Read Json using JsonPath - Deprecated. use select
pub fn read(json: Value, path: &str) -> Result { pub fn read(json: &Value, path: &str) -> Result {
select(json, path) select(json, path)
} }
#[cfg(test)]
mod tests {
use super::*;
use std::io::Read;
fn read_json(path: &str) -> Value {
let mut f = std::fs::File::open(path).unwrap();
let mut contents = String::new();
f.read_to_string(&mut contents).unwrap();
serde_json::from_str(contents.as_str()).unwrap()
}
#[test]
fn compile() {
let mut template = super::compile("$..friends[2]");
let json_obj = read_json("./benches/data_obj.json");
let json = template(json_obj).unwrap();
let ret = json!([
{"id": 2,"name": "Gray Berry"},
{"id": 2,"name": "Gray Berry"}
]);
assert_eq!(json, ret);
let json_obj = read_json("./benches/data_array.json");
let json = template(json_obj).unwrap();
let ret = json!([
{"id": 2,"name": "Gray Berry"},
{"id": 2,"name": "Rosetta Erickson"}
]);
assert_eq!(json, ret);
}
#[test]
fn selector() {
let json_obj = read_json("./benches/data_obj.json");
let mut reader = super::selector(json_obj);
let json = reader("$..friends[2]").unwrap();
let ret = json!([
{"id": 2,"name": "Gray Berry"},
{"id": 2,"name": "Gray Berry"}
]);
assert_eq!(json, ret);
let json = reader("$..friends[0]").unwrap();
let ret = json!([
{"id": 0},
{"id": 0,"name": "Millicent Norman"}
]);
assert_eq!(json, ret);
}
#[test]
fn select() {
let json_obj = read_json("./benches/example.json");
let json = super::select(json_obj, "$..book[2]").unwrap();
let ret = json!([{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}]);
assert_eq!(json, ret);
}
}

View File

@ -1,3 +1,4 @@
mod path_reader; mod path_reader;
mod tokenizer; mod tokenizer;
pub mod parser; mod parser;
pub mod prelude;

View File

@ -1,19 +1,14 @@
use std::result; use std::result::Result;
use super::tokenizer::{ use super::tokenizer::*;
PreloadedTokenizer,
Token,
TokenError,
};
const DUMMY: usize = 0; const DUMMY: usize = 0;
type Result<T> = result::Result<T, String>; type ParseResult<T> = Result<T, String>;
mod utils { mod utils {
use std::result;
pub fn string_to_isize<F>(string: &String, msg_handler: F) -> result::Result<isize, String> pub fn string_to_isize<F>(string: &String, msg_handler: F) -> Result<isize, String>
where F: Fn() -> String { where F: Fn() -> String {
match string.as_str().parse::<isize>() { match string.as_str().parse::<isize>() {
Ok(n) => Ok(n), Ok(n) => Ok(n),
@ -21,7 +16,7 @@ mod utils {
} }
} }
pub fn string_to_f64<F>(string: &String, msg_handler: F) -> result::Result<f64, String> pub fn string_to_f64<F>(string: &String, msg_handler: F) -> Result<f64, String>
where F: Fn() -> String { where F: Fn() -> String {
match string.as_str().parse::<f64>() { match string.as_str().parse::<f64>() {
Ok(n) => Ok(n), Ok(n) => Ok(n),
@ -88,17 +83,17 @@ impl<'a> Parser<'a> {
Parser { tokenizer: PreloadedTokenizer::new(input) } Parser { tokenizer: PreloadedTokenizer::new(input) }
} }
pub fn compile(&mut self) -> Result<Node> { pub fn compile(&mut self) -> ParseResult<Node> {
Ok(self.json_path()?) Ok(self.json_path()?)
} }
pub fn parse<V: NodeVisitor>(&mut self, visitor: &mut V) -> Result<()> { pub fn parse<V: NodeVisitor>(&mut self, visitor: &mut V) -> ParseResult<()> {
let node = self.json_path()?; let node = self.json_path()?;
visitor.visit(node); visitor.visit(node);
Ok(()) Ok(())
} }
fn json_path(&mut self) -> Result<Node> { fn json_path(&mut self) -> ParseResult<Node> {
debug!("#json_path"); debug!("#json_path");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::Absolute(_)) => { Ok(Token::Absolute(_)) => {
@ -111,7 +106,7 @@ impl<'a> Parser<'a> {
} }
} }
fn paths(&mut self, prev: Node) -> Result<Node> { fn paths(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#paths"); debug!("#paths");
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
Ok(Token::Dot(_)) => { Ok(Token::Dot(_)) => {
@ -130,7 +125,7 @@ impl<'a> Parser<'a> {
} }
} }
fn paths_dot(&mut self, prev: Node) -> Result<Node> { fn paths_dot(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#paths_dot"); debug!("#paths_dot");
let node = self.path(prev)?; let node = self.path(prev)?;
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
@ -150,7 +145,7 @@ impl<'a> Parser<'a> {
} }
} }
fn path(&mut self, prev: Node) -> Result<Node> { fn path(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#path"); debug!("#path");
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
Ok(Token::Dot(_)) => { Ok(Token::Dot(_)) => {
@ -172,7 +167,7 @@ impl<'a> Parser<'a> {
} }
} }
fn path_leaves(&mut self, prev: Node) -> Result<Node> { fn path_leaves(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#path_leaves"); debug!("#path_leaves");
self.eat_token(); self.eat_token();
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
@ -190,7 +185,7 @@ impl<'a> Parser<'a> {
} }
} }
fn path_leaves_key(&mut self, prev: Node) -> Result<Node> { fn path_leaves_key(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#path_leaves_key"); debug!("#path_leaves_key");
Ok(Node { Ok(Node {
token: ParseToken::Leaves, token: ParseToken::Leaves,
@ -199,7 +194,7 @@ impl<'a> Parser<'a> {
}) })
} }
fn path_leaves_all(&mut self, prev: Node) -> Result<Node> { fn path_leaves_all(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#path_leaves_all"); debug!("#path_leaves_all");
self.eat_token(); self.eat_token();
Ok(Node { Ok(Node {
@ -209,7 +204,7 @@ impl<'a> Parser<'a> {
}) })
} }
fn path_in_all(&mut self, prev: Node) -> Result<Node> { fn path_in_all(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#path_in_all"); debug!("#path_in_all");
self.eat_token(); self.eat_token();
Ok(Node { Ok(Node {
@ -219,7 +214,7 @@ impl<'a> Parser<'a> {
}) })
} }
fn path_in_key(&mut self, prev: Node) -> Result<Node> { fn path_in_key(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#path_in_key"); debug!("#path_in_key");
Ok(Node { Ok(Node {
token: ParseToken::In, token: ParseToken::In,
@ -228,7 +223,7 @@ impl<'a> Parser<'a> {
}) })
} }
fn key(&mut self) -> Result<Node> { fn key(&mut self) -> ParseResult<Node> {
debug!("#key"); debug!("#key");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::Key(_, v)) => { Ok(Token::Key(_, v)) => {
@ -240,7 +235,7 @@ impl<'a> Parser<'a> {
} }
} }
fn array_quota_value(&mut self) -> Result<Node> { fn array_quota_value(&mut self) -> ParseResult<Node> {
debug!("#array_quota_value"); debug!("#array_quota_value");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::SingleQuoted(_, val)) Ok(Token::SingleQuoted(_, val))
@ -256,7 +251,7 @@ impl<'a> Parser<'a> {
} }
} }
fn array_start(&mut self, prev: Node) -> Result<Node> { fn array_start(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#array_start"); debug!("#array_start");
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
Ok(Token::Question(_)) => { Ok(Token::Question(_)) => {
@ -285,14 +280,14 @@ impl<'a> Parser<'a> {
} }
} }
fn array(&mut self, prev: Node) -> Result<Node> { fn array(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#array"); debug!("#array");
let ret = self.array_start(prev)?; let ret = self.array_start(prev)?;
self.eat_whitespace(); self.eat_whitespace();
self.close_token(ret, Token::CloseArray(DUMMY)) self.close_token(ret, Token::CloseArray(DUMMY))
} }
fn array_value_key(&mut self) -> Result<Node> { fn array_value_key(&mut self) -> ParseResult<Node> {
debug!("#array_value_key"); debug!("#array_value_key");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::Key(pos, ref val)) => { Ok(Token::Key(pos, ref val)) => {
@ -318,7 +313,7 @@ impl<'a> Parser<'a> {
} }
fn array_value(&mut self) -> Result<Node> { fn array_value(&mut self) -> ParseResult<Node> {
debug!("#array_value"); debug!("#array_value");
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
Ok(Token::Key(_, _)) => { Ok(Token::Key(_, _)) => {
@ -342,7 +337,7 @@ impl<'a> Parser<'a> {
} }
} }
fn union(&mut self, num: isize) -> Result<Node> { fn union(&mut self, num: isize) -> ParseResult<Node> {
debug!("#union"); debug!("#union");
let mut values = vec![num]; let mut values = vec![num];
while match self.tokenizer.peek_token() { while match self.tokenizer.peek_token() {
@ -364,7 +359,7 @@ impl<'a> Parser<'a> {
Ok(self.node(ParseToken::Union(values))) Ok(self.node(ParseToken::Union(values)))
} }
fn range_from(&mut self, num: isize) -> Result<Node> { fn range_from(&mut self, num: isize) -> ParseResult<Node> {
debug!("#range_from"); debug!("#range_from");
self.eat_token(); self.eat_token();
self.eat_whitespace(); self.eat_whitespace();
@ -378,7 +373,7 @@ impl<'a> Parser<'a> {
} }
} }
fn range_to(&mut self) -> Result<Node> { fn range_to(&mut self) -> ParseResult<Node> {
debug!("#range_to"); debug!("#range_to");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::Key(pos, ref val)) => { Ok(Token::Key(pos, ref val)) => {
@ -391,7 +386,7 @@ impl<'a> Parser<'a> {
} }
} }
fn range(&mut self, num: isize) -> Result<Node> { fn range(&mut self, num: isize) -> ParseResult<Node> {
debug!("#range"); debug!("#range");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::Key(pos, ref val)) => { Ok(Token::Key(pos, ref val)) => {
@ -404,7 +399,7 @@ impl<'a> Parser<'a> {
} }
} }
fn filter(&mut self) -> Result<Node> { fn filter(&mut self) -> ParseResult<Node> {
debug!("#filter"); debug!("#filter");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::OpenParenthesis(_)) => { Ok(Token::OpenParenthesis(_)) => {
@ -421,7 +416,7 @@ impl<'a> Parser<'a> {
} }
} }
fn exprs(&mut self) -> Result<Node> { fn exprs(&mut self) -> ParseResult<Node> {
self.eat_whitespace(); self.eat_whitespace();
debug!("#exprs"); debug!("#exprs");
let node = match self.tokenizer.peek_token() { let node = match self.tokenizer.peek_token() {
@ -441,7 +436,7 @@ impl<'a> Parser<'a> {
self.condition_expr(node) self.condition_expr(node)
} }
fn condition_expr(&mut self, prev: Node) -> Result<Node> { fn condition_expr(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#condition_expr"); debug!("#condition_expr");
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
Ok(Token::And(_)) => { Ok(Token::And(_)) => {
@ -466,7 +461,7 @@ impl<'a> Parser<'a> {
} }
} }
fn expr(&mut self) -> Result<Node> { fn expr(&mut self) -> ParseResult<Node> {
debug!("#expr"); debug!("#expr");
let has_prop_candidate = match self.tokenizer.peek_token() { let has_prop_candidate = match self.tokenizer.peek_token() {
@ -494,7 +489,7 @@ impl<'a> Parser<'a> {
} }
} }
fn term_num(&mut self) -> Result<Node> { fn term_num(&mut self) -> ParseResult<Node> {
debug!("#term_num"); debug!("#term_num");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(Token::Key(pos, val)) => { Ok(Token::Key(pos, val)) => {
@ -517,7 +512,7 @@ impl<'a> Parser<'a> {
} }
} }
fn term_num_float(&mut self, mut num: &str) -> Result<Node> { fn term_num_float(&mut self, mut num: &str) -> ParseResult<Node> {
debug!("#term_num_float"); debug!("#term_num_float");
self.eat_token(); self.eat_token();
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
@ -535,7 +530,7 @@ impl<'a> Parser<'a> {
} }
} }
fn term(&mut self) -> Result<Node> { fn term(&mut self) -> ParseResult<Node> {
debug!("#term"); debug!("#term");
match self.tokenizer.peek_token() { match self.tokenizer.peek_token() {
Ok(Token::At(_)) => { Ok(Token::At(_)) => {
@ -568,7 +563,7 @@ impl<'a> Parser<'a> {
} }
} }
fn op(&mut self, prev: Node) -> Result<Node> { fn op(&mut self, prev: Node) -> ParseResult<Node> {
debug!("#op"); debug!("#op");
let token = match self.tokenizer.next_token() { let token = match self.tokenizer.next_token() {
Ok(Token::Equal(_)) => { Ok(Token::Equal(_)) => {
@ -620,7 +615,7 @@ impl<'a> Parser<'a> {
Node { left: None, right: None, token: token } Node { left: None, right: None, token: token }
} }
fn close_token(&mut self, ret: Node, token: Token) -> Result<Node> { fn close_token(&mut self, ret: Node, token: Token) -> ParseResult<Node> {
debug!("#close_token"); debug!("#close_token");
match self.tokenizer.next_token() { match self.tokenizer.next_token() {
Ok(ref t) if t.partial_eq(token) => { Ok(ref t) if t.partial_eq(token) => {
@ -679,319 +674,4 @@ pub trait NodeVisitor {
fn visit_token(&mut self, token: ParseToken); fn visit_token(&mut self, token: ParseToken);
fn end_term(&mut self) {} fn end_term(&mut self) {}
}
#[cfg(test)]
mod tests {
extern crate env_logger;
use super::*;
struct NodeVisitorTestImpl<'a> {
input: &'a str,
stack: Vec<ParseToken>,
}
impl<'a> NodeVisitorTestImpl<'a> {
fn new(input: &'a str) -> Self {
NodeVisitorTestImpl { input, stack: Vec::new() }
}
fn visit(&mut self) -> result::Result<Vec<ParseToken>, String> {
let tokenizer = PreloadedTokenizer::new(self.input);
let mut parser = Parser { tokenizer };
parser.parse(self)?;
Ok(self.stack.split_off(0))
}
}
impl<'a> NodeVisitor for NodeVisitorTestImpl<'a> {
fn visit_token(&mut self, token: ParseToken) {
self.stack.push(token);
}
}
fn setup() {
let _ = env_logger::try_init();
}
fn run(input: &str) -> result::Result<Vec<ParseToken>, String> {
let mut interpreter = NodeVisitorTestImpl::new(input);
interpreter.visit()
}
#[test]
fn parse_path() {
setup();
assert_eq!(run("$.aa"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("aa".to_owned())
]));
assert_eq!(run("$.00.a"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("00".to_owned()),
ParseToken::In,
ParseToken::Key("a".to_owned())
]));
assert_eq!(run("$.00.韓창.seok"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("00".to_owned()),
ParseToken::In,
ParseToken::Key("韓창".to_owned()),
ParseToken::In,
ParseToken::Key("seok".to_owned())
]));
assert_eq!(run("$.*"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::All
]));
assert_eq!(run("$..*"), Ok(vec![
ParseToken::Absolute,
ParseToken::Leaves,
ParseToken::All
]));
assert_eq!(run("$..[0]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Leaves,
ParseToken::Array,
ParseToken::Number(0.0),
ParseToken::ArrayEof
]));
match run("$.") {
Ok(_) => panic!(),
_ => {}
}
match run("$..") {
Ok(_) => panic!(),
_ => {}
}
match run("$. a") {
Ok(_) => panic!(),
_ => {}
}
}
#[test]
fn parse_array_sytax() {
setup();
assert_eq!(run("$.book[?(@.isbn)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("book".to_string()),
ParseToken::Array,
ParseToken::Relative,
ParseToken::In,
ParseToken::Key("isbn".to_string()),
ParseToken::ArrayEof
]));
//
// Array도 컨텍스트 In으로 간주 할거라서 중첩되면 하나만
//
assert_eq!(run("$.[*]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[*]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[*].가"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof,
ParseToken::In, ParseToken::Key("".to_owned())
]));
assert_eq!(run("$.a[0][1]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Number(0_f64),
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::Number(1_f64),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[1,2]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Union(vec![1, 2]),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[10:]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Range(Some(10), None),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[:11]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Range(None, Some(11)),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[-12:13]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Range(Some(-12), Some(13)),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[?(1>2)]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Number(1_f64), ParseToken::Number(2_f64), ParseToken::Filter(FilterToken::Greater),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[?($.b>3)]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Absolute, ParseToken::In, ParseToken::Key("b".to_owned()), ParseToken::Number(3_f64), ParseToken::Filter(FilterToken::Greater),
ParseToken::ArrayEof
]));
assert_eq!(run("$[?($.c>@.d && 1==2)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Absolute, ParseToken::In, ParseToken::Key("c".to_owned()),
ParseToken::Relative, ParseToken::In, ParseToken::Key("d".to_owned()),
ParseToken::Filter(FilterToken::Greater),
ParseToken::Number(1_f64), ParseToken::Number(2_f64), ParseToken::Filter(FilterToken::Equal),
ParseToken::Filter(FilterToken::And),
ParseToken::ArrayEof
]));
assert_eq!(run("$[?($.c>@.d&&(1==2||3>=4))]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Absolute, ParseToken::In, ParseToken::Key("c".to_owned()),
ParseToken::Relative, ParseToken::In, ParseToken::Key("d".to_owned()),
ParseToken::Filter(FilterToken::Greater),
ParseToken::Number(1_f64), ParseToken::Number(2_f64), ParseToken::Filter(FilterToken::Equal),
ParseToken::Number(3_f64), ParseToken::Number(4_f64), ParseToken::Filter(FilterToken::GreaterOrEqual),
ParseToken::Filter(FilterToken::Or),
ParseToken::Filter(FilterToken::And),
ParseToken::ArrayEof
]));
assert_eq!(run("$[?(@.a<@.b)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Relative, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Relative, ParseToken::In, ParseToken::Key("b".to_owned()),
ParseToken::Filter(FilterToken::Little),
ParseToken::ArrayEof
]));
assert_eq!(run("$[*][*][*]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof
]));
assert_eq!(run("$['a']['bb']"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Key("a".to_string()),
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::Key("bb".to_string()),
ParseToken::ArrayEof
]));
match run("$[") {
Ok(_) => panic!(),
_ => {}
}
match run("$[]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[a]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?($.a)]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(@.a > @.b]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(@.a < @.b&&(@.c < @.d)]") {
Ok(_) => panic!(),
_ => {}
}
}
#[test]
fn parse_array_float() {
setup();
assert_eq!(run("$[?(1.1<2.1)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Number(1.1), ParseToken::Number(2.1), ParseToken::Filter(FilterToken::Little),
ParseToken::ArrayEof
]));
match run("$[1.1]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(1.1<.2)]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(1.1<2.)]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(1.1<2.a)]") {
Ok(_) => panic!(),
_ => {}
}
}
} }

View File

@ -1,4 +1,4 @@
use std::result; use std::result::Result;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ReaderError { pub enum ReaderError {
@ -18,12 +18,12 @@ impl<'a> PathReader<'a> {
} }
} }
pub fn peek_char(&self) -> result::Result<(usize, char), ReaderError> { pub fn peek_char(&self) -> Result<(usize, char), ReaderError> {
let ch = self.input.chars().next().ok_or(ReaderError::Eof)?; let ch = self.input.chars().next().ok_or(ReaderError::Eof)?;
Ok((self.pos + ch.len_utf8(), ch)) Ok((self.pos + ch.len_utf8(), ch))
} }
pub fn take_while<F>(&mut self, fun: F) -> result::Result<(usize, String), ReaderError> pub fn take_while<F>(&mut self, fun: F) -> Result<(usize, String), ReaderError>
where where
F: Fn(&char) -> bool F: Fn(&char) -> bool
{ {
@ -42,7 +42,7 @@ impl<'a> PathReader<'a> {
Ok((self.pos, ret)) Ok((self.pos, ret))
} }
pub fn next_char(&mut self) -> result::Result<(usize, char), ReaderError> { pub fn next_char(&mut self) -> Result<(usize, char), ReaderError> {
let (_, ch) = self.peek_char()?; let (_, ch) = self.peek_char()?;
self.input = &self.input[ch.len_utf8()..]; self.input = &self.input[ch.len_utf8()..];
let ret = Ok((self.pos, ch)); let ret = Ok((self.pos, ch));

2
src/parser/prelude.rs Normal file
View File

@ -0,0 +1,2 @@
pub use super::parser::*;
pub use super::tokenizer::*;

View File

@ -1,10 +1,7 @@
use std::result;
use std::io::Write; use std::io::Write;
use std::result::Result;
use super::path_reader::{ use super::path_reader::{PathReader, ReaderError};
ReaderError,
PathReader,
};
const ABSOLUTE: &'static str = "$"; const ABSOLUTE: &'static str = "$";
const DOT: &'static str = "."; const DOT: &'static str = ".";
@ -90,7 +87,6 @@ pub enum Token {
} }
impl Token { impl Token {
pub fn partial_eq(&self, other: Token) -> bool { pub fn partial_eq(&self, other: Token) -> bool {
self.to_simple() == other.to_simple() self.to_simple() == other.to_simple()
} }
@ -152,19 +148,19 @@ impl<'a> Tokenizer<'a> {
} }
} }
fn single_quota(&mut self, pos: usize, ch: char) -> result::Result<Token, TokenError> { fn single_quota(&mut self, pos: usize, ch: char) -> Result<Token, TokenError> {
let (_, val) = self.input.take_while(|c| *c != ch).map_err(to_token_error)?; let (_, val) = self.input.take_while(|c| *c != ch).map_err(to_token_error)?;
self.input.next_char().map_err(to_token_error)?; self.input.next_char().map_err(to_token_error)?;
Ok(Token::SingleQuoted(pos, val)) Ok(Token::SingleQuoted(pos, val))
} }
fn double_quota(&mut self, pos: usize, ch: char) -> result::Result<Token, TokenError> { fn double_quota(&mut self, pos: usize, ch: char) -> Result<Token, TokenError> {
let (_, val) = self.input.take_while(|c| *c != ch).map_err(to_token_error)?; let (_, val) = self.input.take_while(|c| *c != ch).map_err(to_token_error)?;
self.input.next_char().map_err(to_token_error)?; self.input.next_char().map_err(to_token_error)?;
Ok(Token::DoubleQuoted(pos, val)) Ok(Token::DoubleQuoted(pos, val))
} }
fn equal(&mut self, pos: usize, _: char) -> result::Result<Token, TokenError> { fn equal(&mut self, pos: usize, _: char) -> Result<Token, TokenError> {
let (_, ch) = self.input.peek_char().map_err(to_token_error)?; let (_, ch) = self.input.peek_char().map_err(to_token_error)?;
match ch { match ch {
CH_EQUAL => { CH_EQUAL => {
@ -175,7 +171,7 @@ impl<'a> Tokenizer<'a> {
} }
} }
fn not_equal(&mut self, pos: usize, _: char) -> result::Result<Token, TokenError> { fn not_equal(&mut self, pos: usize, _: char) -> Result<Token, TokenError> {
let (_, ch) = self.input.peek_char().map_err(to_token_error)?; let (_, ch) = self.input.peek_char().map_err(to_token_error)?;
match ch { match ch {
CH_EQUAL => { CH_EQUAL => {
@ -186,7 +182,7 @@ impl<'a> Tokenizer<'a> {
} }
} }
fn little(&mut self, pos: usize, _: char) -> result::Result<Token, TokenError> { fn little(&mut self, pos: usize, _: char) -> Result<Token, TokenError> {
let (_, ch) = self.input.peek_char().map_err(to_token_error)?; let (_, ch) = self.input.peek_char().map_err(to_token_error)?;
match ch { match ch {
CH_EQUAL => { CH_EQUAL => {
@ -197,7 +193,7 @@ impl<'a> Tokenizer<'a> {
} }
} }
fn greater(&mut self, pos: usize, _: char) -> result::Result<Token, TokenError> { fn greater(&mut self, pos: usize, _: char) -> Result<Token, TokenError> {
let (_, ch) = self.input.peek_char().map_err(to_token_error)?; let (_, ch) = self.input.peek_char().map_err(to_token_error)?;
match ch { match ch {
CH_EQUAL => { CH_EQUAL => {
@ -208,7 +204,7 @@ impl<'a> Tokenizer<'a> {
} }
} }
fn and(&mut self, pos: usize, _: char) -> result::Result<Token, TokenError> { fn and(&mut self, pos: usize, _: char) -> Result<Token, TokenError> {
let (_, ch) = self.input.peek_char().map_err(to_token_error)?; let (_, ch) = self.input.peek_char().map_err(to_token_error)?;
match ch { match ch {
CH_AMPERSAND => { CH_AMPERSAND => {
@ -219,7 +215,7 @@ impl<'a> Tokenizer<'a> {
} }
} }
fn or(&mut self, pos: usize, _: char) -> result::Result<Token, TokenError> { fn or(&mut self, pos: usize, _: char) -> Result<Token, TokenError> {
let (_, ch) = self.input.peek_char().map_err(to_token_error)?; let (_, ch) = self.input.peek_char().map_err(to_token_error)?;
match ch { match ch {
CH_PIPE => { CH_PIPE => {
@ -230,12 +226,12 @@ impl<'a> Tokenizer<'a> {
} }
} }
fn whitespace(&mut self, pos: usize, _: char) -> result::Result<Token, TokenError> { fn whitespace(&mut self, pos: usize, _: char) -> Result<Token, TokenError> {
let (_, vec) = self.input.take_while(|c| c.is_whitespace()).map_err(to_token_error)?; let (_, vec) = self.input.take_while(|c| c.is_whitespace()).map_err(to_token_error)?;
Ok(Token::Whitespace(pos, vec.len())) Ok(Token::Whitespace(pos, vec.len()))
} }
fn other(&mut self, pos: usize, ch: char) -> result::Result<Token, TokenError> { fn other(&mut self, pos: usize, ch: char) -> Result<Token, TokenError> {
let fun = |c: &char| { let fun = |c: &char| {
match simple_matched_token(*c, pos) { match simple_matched_token(*c, pos) {
Some(_) => false, Some(_) => false,
@ -253,7 +249,7 @@ impl<'a> Tokenizer<'a> {
Ok(Token::Key(pos, vec)) Ok(Token::Key(pos, vec))
} }
pub fn next_token(&mut self) -> result::Result<Token, TokenError> { pub fn next_token(&mut self) -> Result<Token, TokenError> {
let (pos, ch) = self.input.next_char().map_err(to_token_error)?; let (pos, ch) = self.input.next_char().map_err(to_token_error)?;
match simple_matched_token(ch, pos) { match simple_matched_token(ch, pos) {
Some(t) => Ok(t), Some(t) => Ok(t),
@ -309,7 +305,7 @@ impl<'a> PreloadedTokenizer<'a> {
} }
} }
pub fn peek_token(&self) -> result::Result<&Token, TokenError> { pub fn peek_token(&self) -> Result<&Token, TokenError> {
match self.tokens.last() { match self.tokens.last() {
Some((_, t)) => { Some((_, t)) => {
trace!("%{:?}", t); trace!("%{:?}", t);
@ -322,7 +318,7 @@ impl<'a> PreloadedTokenizer<'a> {
} }
} }
pub fn next_token(&mut self) -> result::Result<Token, TokenError> { pub fn next_token(&mut self) -> Result<Token, TokenError> {
match self.tokens.pop() { match self.tokens.pop() {
Some((pos, t)) => { Some((pos, t)) => {
self.curr_pos = Some(pos); self.curr_pos = Some(pos);
@ -356,205 +352,4 @@ impl<'a> PreloadedTokenizer<'a> {
} }
} }
} }
}
#[cfg(test)]
mod tests {
use super::TokenError;
use super::{
Token,
Tokenizer,
PreloadedTokenizer,
};
fn collect_token(input: &str) -> (Vec<Token>, Option<TokenError>) {
let mut tokenizer = Tokenizer::new(input);
let mut vec = vec![];
loop {
match tokenizer.next_token() {
Ok(t) => vec.push(t),
Err(e) => return (vec, Some(e)),
}
}
}
fn run(input: &str, expected: (Vec<Token>, Option<TokenError>)) {
let (vec, err) = collect_token(input.clone());
assert_eq!((vec, err), expected, "\"{}\"", input);
}
#[test]
fn peek() {
let mut tokenizer = PreloadedTokenizer::new("$.a");
match tokenizer.next_token() {
Ok(t) => assert_eq!(Token::Absolute(0), t),
_ => panic!()
}
match tokenizer.peek_token() {
Ok(t) => assert_eq!(&Token::Dot(1), t),
_ => panic!()
}
match tokenizer.peek_token() {
Ok(t) => assert_eq!(&Token::Dot(1), t),
_ => panic!()
}
match tokenizer.next_token() {
Ok(t) => assert_eq!(Token::Dot(1), t),
_ => panic!()
}
}
#[test]
fn token() {
run("$.01.a",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Key(2, "01".to_string()),
Token::Dot(4),
Token::Key(5, "a".to_string())
]
, Some(TokenError::Eof)
));
run("$. []",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Whitespace(2, 2),
Token::OpenArray(5),
Token::CloseArray(6)
]
, Some(TokenError::Eof)
));
run("$..",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Dot(2),
]
, Some(TokenError::Eof)
));
run("$..ab",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Dot(2),
Token::Key(3, "ab".to_string())
]
, Some(TokenError::Eof)
));
run("$..가 [",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Dot(2),
Token::Key(3, "".to_string()),
Token::Whitespace(6, 0),
Token::OpenArray(7),
]
, Some(TokenError::Eof)
));
run("[-1, 2 ]",
(
vec![
Token::OpenArray(0),
Token::Key(1, "-1".to_string()),
Token::Comma(3),
Token::Whitespace(4, 0),
Token::Key(5, "2".to_string()),
Token::Whitespace(6, 0),
Token::CloseArray(7),
]
, Some(TokenError::Eof)
));
run("[ 1 2 , 3 \"abc\" : -10 ]",
(
vec![
Token::OpenArray(0),
Token::Whitespace(1, 0),
Token::Key(2, "1".to_string()),
Token::Whitespace(3, 0),
Token::Key(4, "2".to_string()),
Token::Whitespace(5, 0),
Token::Comma(6),
Token::Whitespace(7, 0),
Token::Key(8, "3".to_string()),
Token::Whitespace(9, 0),
Token::DoubleQuoted(10, "abc".to_string()),
Token::Whitespace(15, 0),
Token::Split(16),
Token::Whitespace(17, 0),
Token::Key(18, "-10".to_string()),
Token::Whitespace(21, 0),
Token::CloseArray(22),
]
, Some(TokenError::Eof)
));
run("?(@.a가 <41.01)",
(
vec![
Token::Question(0),
Token::OpenParenthesis(1),
Token::At(2),
Token::Dot(3),
Token::Key(4, "a가".to_string()),
Token::Whitespace(8, 0),
Token::Little(9),
Token::Key(10, "41".to_string()),
Token::Dot(12),
Token::Key(13, "01".to_string()),
Token::CloseParenthesis(15),
]
, Some(TokenError::Eof)
));
run("?(@.a <4a.01)",
(
vec![
Token::Question(0),
Token::OpenParenthesis(1),
Token::At(2),
Token::Dot(3),
Token::Key(4, "a".to_string()),
Token::Whitespace(5, 0),
Token::Little(6),
Token::Key(7, "4a".to_string()),
Token::Dot(9),
Token::Key(10, "01".to_string()),
Token::CloseParenthesis(12),
]
, Some(TokenError::Eof)
));
run("?($.c>@.d)", (
vec![
Token::Question(0),
Token::OpenParenthesis(1),
Token::Absolute(2),
Token::Dot(3),
Token::Key(4, "c".to_string()),
Token::Greater(5),
Token::At(6),
Token::Dot(7),
Token::Key(8, "d".to_string()),
Token::CloseParenthesis(9)
]
, Some(TokenError::Eof)
));
}
} }

3
src/prelude.rs Normal file
View File

@ -0,0 +1,3 @@
pub use parser::prelude::*;
pub use filter::prelude::*;
pub use ref_value::*;

View File

@ -5,8 +5,7 @@ use std::sync::Arc;
use std::convert::Into; use std::convert::Into;
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
use serde_json::Number; use serde_json::{Number, Value};
use serde_json::Value;
pub type TypeRefValue = Arc<Box<RefValue>>; pub type TypeRefValue = Arc<Box<RefValue>>;
@ -81,7 +80,7 @@ impl RefIndex for str {
match *v { match *v {
RefValue::Object(ref mut map) => { RefValue::Object(ref mut map) => {
map.entry(self.to_owned()).or_insert(RefValueWrapper::wrap(RefValue::Null)) map.entry(self.to_owned()).or_insert(RefValueWrapper::wrap(RefValue::Null))
}, }
_ => panic!("cannot access key {:?} in JSON {:?}", self, v), _ => panic!("cannot access key {:?} in JSON {:?}", self, v),
} }
} }
@ -182,7 +181,7 @@ impl RefValueWrapper {
} }
} }
impl Into<RefValueWrapper> for Value { impl Into<RefValueWrapper> for &Value {
fn into(self) -> RefValueWrapper { fn into(self) -> RefValueWrapper {
let ref_val = RefValueConverter::new(self); let ref_val = RefValueConverter::new(self);
RefValueWrapper::new(ref_val) RefValueWrapper::new(ref_val)
@ -275,7 +274,6 @@ impl RefValue {
} }
impl Into<RefValueWrapper> for RefValue { impl Into<RefValueWrapper> for RefValue {
fn into(self) -> RefValueWrapper { fn into(self) -> RefValueWrapper {
let wrap = RefValueWrapper::wrap(self); let wrap = RefValueWrapper::wrap(self);
RefValueWrapper::new(wrap) RefValueWrapper::new(wrap)
@ -285,11 +283,11 @@ impl Into<RefValueWrapper> for RefValue {
struct RefValueConverter; struct RefValueConverter;
impl RefValueConverter { impl RefValueConverter {
fn new(value: Value) -> TypeRefValue { fn new(value: &Value) -> TypeRefValue {
RefValueConverter {}.visit_value(value) RefValueConverter {}.visit_value(value)
} }
fn visit_value(&self, value: Value) -> TypeRefValue { fn visit_value(&self, value: &Value) -> TypeRefValue {
match value { match value {
Value::Null => self.visit_null(), Value::Null => self.visit_null(),
Value::Bool(v) => self.visit_bool(v), Value::Bool(v) => self.visit_bool(v),
@ -302,29 +300,29 @@ impl RefValueConverter {
fn visit_null(&self) -> TypeRefValue { fn visit_null(&self) -> TypeRefValue {
RefValueWrapper::wrap(RefValue::Null) RefValueWrapper::wrap(RefValue::Null)
} }
fn visit_bool(&self, value: bool) -> TypeRefValue { fn visit_bool(&self, value: &bool) -> TypeRefValue {
RefValueWrapper::wrap(RefValue::Bool(value)) RefValueWrapper::wrap(RefValue::Bool(*value))
} }
fn visit_number(&self, value: serde_json::Number) -> TypeRefValue { fn visit_number(&self, value: &serde_json::Number) -> TypeRefValue {
RefValueWrapper::wrap(RefValue::Number(value)) RefValueWrapper::wrap(RefValue::Number(value.clone()))
} }
fn visit_string(&self, value: String) -> TypeRefValue { fn visit_string(&self, value: &String) -> TypeRefValue {
RefValueWrapper::wrap(RefValue::String(value.to_string())) RefValueWrapper::wrap(RefValue::String(value.to_string()))
} }
fn visit_array(&self, value: Vec<Value>) -> TypeRefValue { fn visit_array(&self, value: &Vec<Value>) -> TypeRefValue {
let mut values = Vec::new(); let mut values = Vec::new();
for v in value { for v in value {
values.push(self.visit_value(v)); values.push(self.visit_value(v));
} }
RefValueWrapper::wrap(RefValue::Array(values)) RefValueWrapper::wrap(RefValue::Array(values))
} }
fn visit_object(&self, mut value: serde_json::Map<String, Value>) -> TypeRefValue { fn visit_object(&self, value: &serde_json::Map<String, Value>) -> TypeRefValue {
let mut map = IndexMap::new(); let mut map = IndexMap::new();
let keys: Vec<String> = value.keys().into_iter().map(|k| k.to_string()).collect(); let keys: Vec<String> = value.keys().into_iter().map(|k| k.to_string()).collect();
for k in keys { for k in keys {
let value = self.visit_value(match value.get_mut(&k) { let value = self.visit_value(match value.get(&k) {
Some(v) => v.take(), Some(v) => v,
_ => Value::Null _ => &Value::Null
}); });
map.insert(k, value); map.insert(k, value);
} }

345
tests/filter.rs Normal file
View File

@ -0,0 +1,345 @@
extern crate env_logger;
extern crate jsonpath_lib as jsonpath;
#[macro_use]
extern crate serde_json;
use std::io::Read;
use serde_json::Value;
use jsonpath::prelude::*;
fn setup() {
let _ = env_logger::try_init();
}
fn new_value_filter(file: &str) -> ValueFilter {
let string = read_json(file);
let json: Value = serde_json::from_str(string.as_str()).unwrap();
ValueFilter::new((&json).into(), false, false)
}
fn do_filter(path: &str, file: &str) -> JsonValueFilter {
let string = read_json(file);
let mut jf = JsonValueFilter::new(string.as_str()).unwrap();
let mut parser = Parser::new(path);
parser.parse(&mut jf).unwrap();
jf
}
fn read_json(path: &str) -> String {
let mut f = std::fs::File::open(path).unwrap();
let mut contents = String::new();
f.read_to_string(&mut contents).unwrap();
contents
}
#[test]
fn step_in() {
setup();
let mut jf = new_value_filter("./benches/data_obj.json");
{
let current = jf.step_in_str("friends");
assert_eq!(current.is_array(), true);
}
let mut jf = new_value_filter("./benches/data_array.json");
{
let current = jf.step_in_num(&1.0);
assert_eq!(current.get_val().is_object(), true);
}
{
let current = jf.step_in_str("friends");
assert_eq!(current.is_array(), true);
}
let mut jf = new_value_filter("./benches/data_obj.json");
{
jf.step_in_str("school");
jf.step_in_str("friends");
jf.step_in_all();
let current = jf.step_in_str("name");
let friends = json!([
"Millicent Norman",
"Vincent Cannon",
"Gray Berry"
]);
assert_eq!(friends, current.get_val().into_value());
}
let mut jf = new_value_filter("./benches/data_obj.json");
{
let current = jf.step_leaves_str("name");
let names = json!([
"Leonor Herman",
"Millicent Norman",
"Vincent Cannon",
"Gray Berry",
"Vincent Cannon",
"Gray Berry"
]);
assert_eq!(names, current.get_val().into_value());
}
}
#[test]
fn array() {
setup();
let friends = json!([
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]);
let jf = do_filter("$.school.friends[1, 2]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school.friends[1:]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school.friends[:-2]", "./benches/data_obj.json");
let friends = json!([
{"id": 0, "name": "Millicent Norman"}
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..friends[2].name", "./benches/data_obj.json");
let friends = json!(["Gray Berry", "Gray Berry"]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..friends[*].name", "./benches/data_obj.json");
let friends = json!(["Vincent Cannon","Gray Berry","Millicent Norman","Vincent Cannon","Gray Berry"]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$['school']['friends'][*].['name']", "./benches/data_obj.json");
let friends = json!(["Millicent Norman","Vincent Cannon","Gray Berry"]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$['school']['friends'][0].['name']", "./benches/data_obj.json");
let friends = json!("Millicent Norman");
assert_eq!(friends, jf.current_value().into_value());
}
#[test]
fn return_type() {
setup();
let friends = json!({
"friends": [
{"id": 0, "name": "Millicent Norman"},
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]
});
let jf = do_filter("$.school", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school[?(@.friends[0])]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school[?(@.friends[10])]", "./benches/data_obj.json");
assert_eq!(Value::Null, jf.current_value().into_value());
let jf = do_filter("$.school[?(1==1)]", "./benches/data_obj.json");
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.school.friends[?(1==1)]", "./benches/data_obj.json");
let friends = json!([
{"id": 0, "name": "Millicent Norman"},
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]);
assert_eq!(friends, jf.current_value().into_value());
}
#[test]
fn op() {
setup();
let jf = do_filter("$.school[?(@.friends == @.friends)]", "./benches/data_obj.json");
let friends = json!({
"friends": [
{"id": 0, "name": "Millicent Norman"},
{"id": 1, "name": "Vincent Cannon" },
{"id": 2, "name": "Gray Berry"}
]
});
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?(@.name)]", "./benches/data_obj.json");
let friends = json!([
{ "id" : 1, "name" : "Vincent Cannon" },
{ "id" : 2, "name" : "Gray Berry" }
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?(@.id >= 2)]", "./benches/data_obj.json");
let friends = json!([
{ "id" : 2, "name" : "Gray Berry" }
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?(@.id >= 2 || @.id == 1)]", "./benches/data_obj.json");
let friends = json!([
{ "id" : 2, "name" : "Gray Berry" },
{ "id" : 1, "name" : "Vincent Cannon" }
]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$.friends[?( (@.id >= 2 || @.id == 1) && @.id == 0)]", "./benches/data_obj.json");
assert_eq!(Value::Null, jf.current_value().into_value());
let jf = do_filter("$..friends[?(@.id == $.index)].id", "./benches/data_obj.json");
let friends = json!([0, 0]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..book[?($.store.bicycle.price < @.price)].price", "./benches/example.json");
let friends = json!([22.99]);
assert_eq!(friends, jf.current_value().into_value());
let jf = do_filter("$..book[?( (@.price == 12.99 || @.category == 'reference') && @.price > 10)].price", "./benches/example.json");
let friends = json!([12.99]);
assert_eq!(friends, jf.current_value().into_value());
}
#[test]
fn example() {
setup();
let jf = do_filter("$.store.book[*].author", "./benches/example.json");
let ret = json!(["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..author", "./benches/example.json");
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$.store.*", "./benches/example.json");
let ret = json!([
[
{"category" : "reference", "author" : "Nigel Rees","title" : "Sayings of the Century", "price" : 8.95},
{"category" : "fiction", "author" : "Evelyn Waugh","title" : "Sword of Honour","price" : 12.99},
{"category" : "fiction", "author" : "Herman Melville","title" : "Moby Dick","isbn" : "0-553-21311-3","price" : 8.99},
{"category" : "fiction", "author" : "J. R. R. Tolkien","title" : "The Lord of the Rings","isbn" : "0-395-19395-8","price" : 22.99}
],
{"color" : "red","price" : 19.95},
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$.store..price", "./benches/example.json");
let ret = json!([8.95, 12.99, 8.99, 22.99, 19.95]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[2]", "./benches/example.json");
let ret = json!([{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[-2]", "./benches/example.json");
let ret = json!([{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[0,1]", "./benches/example.json");
let ret = json!([
{
"category" : "reference",
"author" : "Nigel Rees",
"title" : "Sayings of the Century",
"price" : 8.95
},
{
"category" : "fiction",
"author" : "Evelyn Waugh",
"title" : "Sword of Honour",
"price" : 12.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[:2]", "./benches/example.json");
let ret = json!([
{
"category" : "reference",
"author" : "Nigel Rees",
"title" : "Sayings of the Century",
"price" : 8.95
},
{
"category" : "fiction",
"author" : "Evelyn Waugh",
"title" : "Sword of Honour",
"price" : 12.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[2:]", "./benches/example.json");
let ret = json!([
{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
},
{
"category" : "fiction",
"author" : "J. R. R. Tolkien",
"title" : "The Lord of the Rings",
"isbn" : "0-395-19395-8",
"price" : 22.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..book[?(@.isbn)]", "./benches/example.json");
let ret = json!([
{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
},
{
"category" : "fiction",
"author" : "J. R. R. Tolkien",
"title" : "The Lord of the Rings",
"isbn" : "0-395-19395-8",
"price" : 22.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$.store.book[?(@.price < 10)]", "./benches/example.json");
let ret = json!([
{
"category" : "reference",
"author" : "Nigel Rees",
"title" : "Sayings of the Century",
"price" : 8.95
},
{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}
]);
assert_eq!(ret, jf.current_value().into_value());
let jf = do_filter("$..*", "./benches/example.json");
let json: Value = serde_json::from_str(read_json("./benches/giveme_every_thing_result.json").as_str()).unwrap();
assert_eq!(json, jf.current_value().into_value());
}

69
tests/lib.rs Normal file
View File

@ -0,0 +1,69 @@
extern crate env_logger;
extern crate jsonpath_lib as jsonpath;
extern crate log;
#[macro_use]
extern crate serde_json;
use std::io::Read;
use serde_json::Value;
fn read_json(path: &str) -> Value {
let mut f = std::fs::File::open(path).unwrap();
let mut contents = String::new();
f.read_to_string(&mut contents).unwrap();
serde_json::from_str(contents.as_str()).unwrap()
}
#[test]
fn compile() {
let mut template = jsonpath::compile("$..friends[2]");
let json_obj = read_json("./benches/data_obj.json");
let json = template(&json_obj).unwrap();
let ret = json!([
{"id": 2,"name": "Gray Berry"},
{"id": 2,"name": "Gray Berry"}
]);
assert_eq!(json, ret);
let json_obj = read_json("./benches/data_array.json");
let json = template(&json_obj).unwrap();
let ret = json!([
{"id": 2,"name": "Gray Berry"},
{"id": 2,"name": "Rosetta Erickson"}
]);
assert_eq!(json, ret);
}
#[test]
fn selector() {
let json_obj = read_json("./benches/data_obj.json");
let mut reader = jsonpath::selector(&json_obj);
let json = reader("$..friends[2]").unwrap();
let ret = json!([
{"id": 2,"name": "Gray Berry"},
{"id": 2,"name": "Gray Berry"}
]);
assert_eq!(json, ret);
let json = reader("$..friends[0]").unwrap();
let ret = json!([
{"id": 0},
{"id": 0,"name": "Millicent Norman"}
]);
assert_eq!(json, ret);
}
#[test]
fn select() {
let json_obj = read_json("./benches/example.json");
let json = jsonpath::select(&json_obj, "$..book[2]").unwrap();
let ret = json!([{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}]);
assert_eq!(json, ret);
}

313
tests/parser.rs Normal file
View File

@ -0,0 +1,313 @@
extern crate env_logger;
extern crate jsonpath_lib as jsonpath;
use std::result;
use jsonpath::prelude::*;
struct NodeVisitorTestImpl<'a> {
input: &'a str,
stack: Vec<ParseToken>,
}
impl<'a> NodeVisitorTestImpl<'a> {
fn new(input: &'a str) -> Self {
NodeVisitorTestImpl { input, stack: Vec::new() }
}
fn visit(&mut self) -> result::Result<Vec<ParseToken>, String> {
let mut parser = Parser::new(self.input);
parser.parse(self)?;
Ok(self.stack.split_off(0))
}
}
impl<'a> NodeVisitor for NodeVisitorTestImpl<'a> {
fn visit_token(&mut self, token: ParseToken) {
self.stack.push(token);
}
}
fn setup() {
let _ = env_logger::try_init();
}
fn run(input: &str) -> result::Result<Vec<ParseToken>, String> {
let mut interpreter = NodeVisitorTestImpl::new(input);
interpreter.visit()
}
#[test]
fn parse_path() {
setup();
assert_eq!(run("$.aa"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("aa".to_owned())
]));
assert_eq!(run("$.00.a"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("00".to_owned()),
ParseToken::In,
ParseToken::Key("a".to_owned())
]));
assert_eq!(run("$.00.韓창.seok"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("00".to_owned()),
ParseToken::In,
ParseToken::Key("韓창".to_owned()),
ParseToken::In,
ParseToken::Key("seok".to_owned())
]));
assert_eq!(run("$.*"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::All
]));
assert_eq!(run("$..*"), Ok(vec![
ParseToken::Absolute,
ParseToken::Leaves,
ParseToken::All
]));
assert_eq!(run("$..[0]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Leaves,
ParseToken::Array,
ParseToken::Number(0.0),
ParseToken::ArrayEof
]));
match run("$.") {
Ok(_) => panic!(),
_ => {}
}
match run("$..") {
Ok(_) => panic!(),
_ => {}
}
match run("$. a") {
Ok(_) => panic!(),
_ => {}
}
}
#[test]
fn parse_array_sytax() {
setup();
assert_eq!(run("$.book[?(@.isbn)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::In,
ParseToken::Key("book".to_string()),
ParseToken::Array,
ParseToken::Relative,
ParseToken::In,
ParseToken::Key("isbn".to_string()),
ParseToken::ArrayEof
]));
//
// Array도 컨텍스트 In으로 간주 할거라서 중첩되면 하나만
//
assert_eq!(run("$.[*]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[*]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[*].가"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof,
ParseToken::In, ParseToken::Key("".to_owned())
]));
assert_eq!(run("$.a[0][1]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Number(0_f64),
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::Number(1_f64),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[1,2]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Union(vec![1, 2]),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[10:]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Range(Some(10), None),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[:11]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Range(None, Some(11)),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[-12:13]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Range(Some(-12), Some(13)),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[?(1>2)]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Number(1_f64), ParseToken::Number(2_f64), ParseToken::Filter(FilterToken::Greater),
ParseToken::ArrayEof
]));
assert_eq!(run("$.a[?($.b>3)]"), Ok(vec![
ParseToken::Absolute, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Array,
ParseToken::Absolute, ParseToken::In, ParseToken::Key("b".to_owned()), ParseToken::Number(3_f64), ParseToken::Filter(FilterToken::Greater),
ParseToken::ArrayEof
]));
assert_eq!(run("$[?($.c>@.d && 1==2)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Absolute, ParseToken::In, ParseToken::Key("c".to_owned()),
ParseToken::Relative, ParseToken::In, ParseToken::Key("d".to_owned()),
ParseToken::Filter(FilterToken::Greater),
ParseToken::Number(1_f64), ParseToken::Number(2_f64), ParseToken::Filter(FilterToken::Equal),
ParseToken::Filter(FilterToken::And),
ParseToken::ArrayEof
]));
assert_eq!(run("$[?($.c>@.d&&(1==2||3>=4))]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Absolute, ParseToken::In, ParseToken::Key("c".to_owned()),
ParseToken::Relative, ParseToken::In, ParseToken::Key("d".to_owned()),
ParseToken::Filter(FilterToken::Greater),
ParseToken::Number(1_f64), ParseToken::Number(2_f64), ParseToken::Filter(FilterToken::Equal),
ParseToken::Number(3_f64), ParseToken::Number(4_f64), ParseToken::Filter(FilterToken::GreaterOrEqual),
ParseToken::Filter(FilterToken::Or),
ParseToken::Filter(FilterToken::And),
ParseToken::ArrayEof
]));
assert_eq!(run("$[?(@.a<@.b)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Relative, ParseToken::In, ParseToken::Key("a".to_owned()),
ParseToken::Relative, ParseToken::In, ParseToken::Key("b".to_owned()),
ParseToken::Filter(FilterToken::Little),
ParseToken::ArrayEof
]));
assert_eq!(run("$[*][*][*]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::All,
ParseToken::ArrayEof
]));
assert_eq!(run("$['a']['bb']"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Key("a".to_string()),
ParseToken::ArrayEof,
ParseToken::Array,
ParseToken::Key("bb".to_string()),
ParseToken::ArrayEof
]));
match run("$[") {
Ok(_) => panic!(),
_ => {}
}
match run("$[]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[a]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?($.a)]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(@.a > @.b]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(@.a < @.b&&(@.c < @.d)]") {
Ok(_) => panic!(),
_ => {}
}
}
#[test]
fn parse_array_float() {
setup();
assert_eq!(run("$[?(1.1<2.1)]"), Ok(vec![
ParseToken::Absolute,
ParseToken::Array,
ParseToken::Number(1.1), ParseToken::Number(2.1), ParseToken::Filter(FilterToken::Little),
ParseToken::ArrayEof
]));
match run("$[1.1]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(1.1<.2)]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(1.1<2.)]") {
Ok(_) => panic!(),
_ => {}
}
match run("$[?(1.1<2.a)]") {
Ok(_) => panic!(),
_ => {}
}
}

194
tests/tokenizer.rs Normal file
View File

@ -0,0 +1,194 @@
extern crate jsonpath_lib as jsonpath;
use jsonpath::prelude::*;
fn collect_token(input: &str) -> (Vec<Token>, Option<TokenError>) {
let mut tokenizer = Tokenizer::new(input);
let mut vec = vec![];
loop {
match tokenizer.next_token() {
Ok(t) => vec.push(t),
Err(e) => return (vec, Some(e)),
}
}
}
fn run(input: &str, expected: (Vec<Token>, Option<TokenError>)) {
let (vec, err) = collect_token(input.clone());
assert_eq!((vec, err), expected, "\"{}\"", input);
}
#[test]
fn peek() {
let mut tokenizer = PreloadedTokenizer::new("$.a");
match tokenizer.next_token() {
Ok(t) => assert_eq!(Token::Absolute(0), t),
_ => panic!()
}
match tokenizer.peek_token() {
Ok(t) => assert_eq!(&Token::Dot(1), t),
_ => panic!()
}
match tokenizer.peek_token() {
Ok(t) => assert_eq!(&Token::Dot(1), t),
_ => panic!()
}
match tokenizer.next_token() {
Ok(t) => assert_eq!(Token::Dot(1), t),
_ => panic!()
}
}
#[test]
fn token() {
run("$.01.a",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Key(2, "01".to_string()),
Token::Dot(4),
Token::Key(5, "a".to_string())
]
, Some(TokenError::Eof)
));
run("$. []",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Whitespace(2, 2),
Token::OpenArray(5),
Token::CloseArray(6)
]
, Some(TokenError::Eof)
));
run("$..",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Dot(2),
]
, Some(TokenError::Eof)
));
run("$..ab",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Dot(2),
Token::Key(3, "ab".to_string())
]
, Some(TokenError::Eof)
));
run("$..가 [",
(
vec![
Token::Absolute(0),
Token::Dot(1),
Token::Dot(2),
Token::Key(3, "".to_string()),
Token::Whitespace(6, 0),
Token::OpenArray(7),
]
, Some(TokenError::Eof)
));
run("[-1, 2 ]",
(
vec![
Token::OpenArray(0),
Token::Key(1, "-1".to_string()),
Token::Comma(3),
Token::Whitespace(4, 0),
Token::Key(5, "2".to_string()),
Token::Whitespace(6, 0),
Token::CloseArray(7),
]
, Some(TokenError::Eof)
));
run("[ 1 2 , 3 \"abc\" : -10 ]",
(
vec![
Token::OpenArray(0),
Token::Whitespace(1, 0),
Token::Key(2, "1".to_string()),
Token::Whitespace(3, 0),
Token::Key(4, "2".to_string()),
Token::Whitespace(5, 0),
Token::Comma(6),
Token::Whitespace(7, 0),
Token::Key(8, "3".to_string()),
Token::Whitespace(9, 0),
Token::DoubleQuoted(10, "abc".to_string()),
Token::Whitespace(15, 0),
Token::Split(16),
Token::Whitespace(17, 0),
Token::Key(18, "-10".to_string()),
Token::Whitespace(21, 0),
Token::CloseArray(22),
]
, Some(TokenError::Eof)
));
run("?(@.a가 <41.01)",
(
vec![
Token::Question(0),
Token::OpenParenthesis(1),
Token::At(2),
Token::Dot(3),
Token::Key(4, "a가".to_string()),
Token::Whitespace(8, 0),
Token::Little(9),
Token::Key(10, "41".to_string()),
Token::Dot(12),
Token::Key(13, "01".to_string()),
Token::CloseParenthesis(15),
]
, Some(TokenError::Eof)
));
run("?(@.a <4a.01)",
(
vec![
Token::Question(0),
Token::OpenParenthesis(1),
Token::At(2),
Token::Dot(3),
Token::Key(4, "a".to_string()),
Token::Whitespace(5, 0),
Token::Little(6),
Token::Key(7, "4a".to_string()),
Token::Dot(9),
Token::Key(10, "01".to_string()),
Token::CloseParenthesis(12),
]
, Some(TokenError::Eof)
));
run("?($.c>@.d)", (
vec![
Token::Question(0),
Token::OpenParenthesis(1),
Token::Absolute(2),
Token::Dot(3),
Token::Key(4, "c".to_string()),
Token::Greater(5),
Token::At(6),
Token::Dot(7),
Token::Key(8, "d".to_string()),
Token::CloseParenthesis(9)
]
, Some(TokenError::Eof)
));
}

View File

@ -2,11 +2,8 @@
name = "jsonpath-wasm" name = "jsonpath-wasm"
version = "0.1.3" version = "0.1.3"
authors = ["Changseok Han <freestrings@gmail.com>"] authors = ["Changseok Han <freestrings@gmail.com>"]
description = "JsonPath Webassembly version compiled by Rust - Demo: https://freestrings.github.io/jsonpath" description = "JsonPath Webassembly version compiled by Rust - Demo: https://freestrings.github.io/jsonpath"
keywords = ["library", "jsonpath", "json", "webassembly"] keywords = ["library", "jsonpath", "json", "webassembly"]
repository = "https://github.com/freestrings/jsonpath" repository = "https://github.com/freestrings/jsonpath"
license = "MIT" license = "MIT"

View File

@ -1,79 +0,0 @@
#!/bin/bash
set -e
# project_root/wasm
DIR="$(pwd)"
cd "${DIR}"/www && \
rm -rf "${DIR}"/www/dist && \
rm -rf "${DIR}"/www/node_modules && \
rm -rf "${DIR}"/www_bench/dist && \
rm -rf "${DIR}"/www_bench/node_modules && \
npm install && \
cd "${DIR}"
echo "-------------------- start build nodejs pkg --------------------"
echo
rm -rf "${DIR}"/wasm/nodejs_pkg && \
wasm-pack build --target=nodejs --scope nodejs --out-dir nodejs_pkg && \
cd "${DIR}"/nodejs_pkg && npm link && \
rm -rf "${DIR}"/../benches/javascript/node_modules && \
cd "${DIR}"/../benches/javascript && npm install && \
npm link @nodejs/jsonpath-wasm
echo "-------------------- build nodejs pkg done --------------------"
cd "${DIR}"
echo
echo
echo
echo
echo
echo
echo "-------------------- start build browser pkg --------------------"
echo
rm -rf "${DIR}"/wasm/browser_pkg && \
wasm-pack build --target=browser --scope browser --out-dir browser_pkg && \
cd "${DIR}"/browser_pkg && npm link && \
cd "${DIR}"/www && npm link @browser/jsonpath-wasm
echo "-------------------- build browser pkg done --------------------"
echo
echo
echo
echo
echo
echo
echo "-------------------- start build browser bench pkg --------------------"
echo
rm -rf "${DIR}"/www_bench/node_modules && \
cd "${DIR}"/www_bench && npm install && npm link @browser/jsonpath-wasm
echo "-------------------- build browser bench pkg done --------------------"
echo
echo
echo
echo
echo
echo
echo "-------------------- start build docs --------------------"
cd "${DIR}"/www && \
npm run build && \
rm -f "${DIR}"/../docs/*.js && rm -f "${DIR}"/../docs/*.wasm && rm -f "${DIR}"/../docs/*.html && \
cp "${DIR}"/www/dist/*.* "${DIR}"/../docs/
echo "-------------------- build docs done --------------------"
echo
echo
echo
echo
echo
echo
echo "-------------------- start build docs bench --------------------"
cd "${DIR}"/www_bench && \
npm run build && \
rm -f "${DIR}"/../docs/bench/*.js && rm -f "${DIR}"/../docs/bench/*.wasm && rm -f "${DIR}"/../docs/bench/*.html && \
cp "${DIR}"/www_bench/dist/*.* "${DIR}"/../docs/bench/
echo "-------------------- build docs bench done --------------------"

View File

@ -11,9 +11,7 @@ use std::result::Result;
use std::sync::Mutex; use std::sync::Mutex;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use jsonpath::filter::value_filter::*; use jsonpath::prelude::*;
use jsonpath::parser::parser::*;
use jsonpath::ref_value::*;
use serde_json::Value; use serde_json::Value;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::console; use web_sys::console;
@ -54,7 +52,7 @@ fn into_serde_json(js_value: &JsValue) -> Result<Value, String> {
fn into_ref_value(js_value: &JsValue, node: Node) -> JsValue { fn into_ref_value(js_value: &JsValue, node: Node) -> JsValue {
match into_serde_json(js_value) { match into_serde_json(js_value) {
Ok(json) => filter_ref_value(json.into(), node), Ok(json) => filter_ref_value((&json).into(), node),
Err(e) => JsValue::from_str(&format!("Json serialize error: {}", e)) Err(e) => JsValue::from_str(&format!("Json serialize error: {}", e))
} }
} }
@ -87,7 +85,7 @@ pub fn alloc_json(js_value: JsValue) -> usize {
let mut idx = CACHE_JSON_IDX.lock().unwrap(); let mut idx = CACHE_JSON_IDX.lock().unwrap();
*idx += 1; *idx += 1;
map.insert(*idx, json.into()); map.insert(*idx, (&json).into());
*idx *idx
} }
Err(e) => { Err(e) => {
@ -138,7 +136,7 @@ pub fn selector(js_value: JsValue) -> JsValue {
} }
_ => { _ => {
match into_serde_json(&js_value) { match into_serde_json(&js_value) {
Ok(json) => json.into(), Ok(json) => (&json).into(),
Err(e) => return JsValue::from_str(e.as_str()) Err(e) => return JsValue::from_str(e.as_str())
} }
} }