├── tests ├── .gitignore ├── filelist.f ├── golden_json │ ├── video_indent0.json │ ├── pizza_indent0.json │ ├── recipes_indent0.json │ ├── video_indent2.json │ ├── video.json │ ├── video_indent4.json │ ├── pizza_indent2.json │ ├── pizza.json │ ├── json_gen.py │ ├── pizza_indent4.json │ ├── recipes_indent2.json │ ├── recipes.json │ └── recipes_indent4.json ├── Makefile ├── test_utils_macros.svh ├── json_enum_unit_test.sv ├── json_int_unit_test.sv ├── json_bool_unit_test.sv ├── json_real_unit_test.sv ├── test_utils_pkg.sv ├── json_string_unit_test.sv ├── json_load_err_unit_test.sv ├── json_dump_unit_test.sv ├── json_error_unit_test.sv ├── json_load_ok_unit_test.sv ├── json_array_unit_test.sv └── json_object_unit_test.sv ├── .gitignore ├── docs ├── modules │ └── ROOT │ │ ├── pages │ │ ├── classref.adoc │ │ ├── changelog.adoc │ │ ├── developer.adoc │ │ └── welcome.adoc │ │ ├── nav.adoc │ │ └── examples │ │ ├── selftest │ │ ├── Makefile │ │ └── examples_unit_test.sv │ │ ├── decoder0.svh │ │ ├── encoder1.svh │ │ ├── encodable.svh │ │ ├── encoder0.svh │ │ └── decoder1.svh ├── ECMA-404_2nd_edition_december_2017.pdf └── antora.yml ├── src ├── filelist.f ├── encodable │ ├── json_value_encodable.sv │ ├── json_bool_encodable.sv │ ├── json_string_encodable.sv │ ├── json_real_encodable.sv │ ├── json_int_encodable.sv │ ├── json_array_encodable.sv │ └── json_object_encodable.sv ├── values │ ├── json_bool.sv │ ├── json_string.sv │ ├── json_real.sv │ ├── json_int.sv │ ├── json_enum.sv │ ├── json_bits.sv │ ├── json_array.sv │ ├── json_object.sv │ └── json_value.sv ├── json_pkg.sv ├── json_result.sv ├── json_error.sv ├── json_encoder.sv └── json_decoder.sv ├── .gitmodules ├── scripts └── common.mk ├── antora-playbook.yml ├── .github └── workflows │ ├── docs.yaml │ └── tests.yaml ├── LICENSE ├── Makefile └── README.adoc /tests/.gitignore: -------------------------------------------------------------------------------- 1 | work -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | work_* -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/classref.adoc: -------------------------------------------------------------------------------- 1 | = Class reference manual 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /tests/filelist.f: -------------------------------------------------------------------------------- 1 | +incdir+${SVJSON_ROOT}/tests 2 | ${SVJSON_ROOT}/tests/test_utils_pkg.sv 3 | -------------------------------------------------------------------------------- /docs/ECMA-404_2nd_edition_december_2017.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esynr3z/svjson/HEAD/docs/ECMA-404_2nd_edition_december_2017.pdf -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:welcome.adoc[] 2 | * xref:user.adoc[] 3 | * xref:classref.adoc[] 4 | * xref:developer.adoc[] 5 | * xref:changelog.adoc[] 6 | -------------------------------------------------------------------------------- /src/filelist.f: -------------------------------------------------------------------------------- 1 | +incdir+${SVJSON_ROOT}/src 2 | +incdir+${SVJSON_ROOT}/src/values 3 | +incdir+${SVJSON_ROOT}/src/encodable 4 | ${SVJSON_ROOT}/src/json_pkg.sv 5 | -------------------------------------------------------------------------------- /src/encodable/json_value_encodable.sv: -------------------------------------------------------------------------------- 1 | // Generic interface for a class that can be encoded as JSON value 2 | interface class json_value_encodable; 3 | endclass : json_value_encodable 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "svunit"] 2 | path = contrib/svunit 3 | url = git@github.com:svunit/svunit.git 4 | [submodule "contrib/json_test_suite"] 5 | path = contrib/json_test_suite 6 | url = git@github.com:nst/JSONTestSuite.git 7 | -------------------------------------------------------------------------------- /scripts/common.mk: -------------------------------------------------------------------------------- 1 | # Generic rule to check for binary and execute multiple commands 2 | define run_if_exist 3 | @if command -v $(1) >/dev/null 2>&1; then \ 4 | $(2); \ 5 | else \ 6 | echo "$(1) was not found!"; \ 7 | fi 8 | endef -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: json_pkg 2 | title: JSON Package 3 | version: true 4 | start_page: welcome.adoc 5 | asciidoc: 6 | attributes: 7 | source-language: asciidoc@ 8 | table-caption: false 9 | nav: 10 | - modules/ROOT/nav.adoc 11 | -------------------------------------------------------------------------------- /src/encodable/json_bool_encodable.sv: -------------------------------------------------------------------------------- 1 | // Interface for a class that can be encoded as JSON bool 2 | interface class json_bool_encodable extends json_value_encodable; 3 | // Get value encodable as JSON bool 4 | pure virtual function bit to_json_encodable(); 5 | endclass : json_bool_encodable 6 | -------------------------------------------------------------------------------- /src/encodable/json_string_encodable.sv: -------------------------------------------------------------------------------- 1 | // Interface for a class that can be encoded as JSON string 2 | interface class json_string_encodable extends json_value_encodable; 3 | // Get value encodable as JSON string 4 | pure virtual function string to_json_encodable(); 5 | endclass : json_string_encodable 6 | -------------------------------------------------------------------------------- /src/encodable/json_real_encodable.sv: -------------------------------------------------------------------------------- 1 | // Interface for a class that can be encoded as JSON number (real) 2 | interface class json_real_encodable extends json_value_encodable; 3 | // Get value encodable as JSON real number 4 | pure virtual function real to_json_encodable(); 5 | endclass : json_real_encodable 6 | -------------------------------------------------------------------------------- /src/encodable/json_int_encodable.sv: -------------------------------------------------------------------------------- 1 | // Interface for a class that can be encoded as JSON number (integer) 2 | interface class json_int_encodable extends json_value_encodable; 3 | // Get value encodable as JSON integer number 4 | pure virtual function longint to_json_encodable(); 5 | endclass : json_int_encodable 6 | -------------------------------------------------------------------------------- /src/encodable/json_array_encodable.sv: -------------------------------------------------------------------------------- 1 | // Interface for a class that can be encoded as JSON array 2 | interface class json_array_encodable extends json_value_encodable; 3 | typedef json_value_encodable values_t[$]; 4 | 5 | // Get value encodable as JSON array 6 | pure virtual function values_t to_json_encodable(); 7 | endclass : json_array_encodable 8 | -------------------------------------------------------------------------------- /src/encodable/json_object_encodable.sv: -------------------------------------------------------------------------------- 1 | // Interface for a class that can be encoded as JSON object 2 | interface class json_object_encodable extends json_value_encodable; 3 | typedef json_value_encodable values_t[string]; 4 | 5 | // Get value encodable as JSON object 6 | pure virtual function values_t to_json_encodable(); 7 | endclass : json_object_encodable 8 | -------------------------------------------------------------------------------- /tests/golden_json/video_indent0.json: -------------------------------------------------------------------------------- 1 | {"description":"An early look at our exciting new feature","disclaimer":"Please ensure your sound is on for the best experience.","features":{"interactive":false,"platforms":[{"name":"Web","supportedBrowsers":["Chrome","Firefox","Edge"]},{"name":"Mobile","supportedOS":["iOS","Android"]}],"subtitles":null,"supportedLanguages":["English","Spanish","French"]},"isAvailable":true,"link":{"text":"Click here to see the preview!","url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},"rating":4.333333,"releaseYear":2021,"title":"Exclusive Preview","viewerCount":null} -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/selftest/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean test test_verilator 2 | 3 | export SVJSON_ROOT := $(realpath ../../../../..) 4 | export EXAMPLES_ROOT := $(realpath ..) 5 | export SVUNIT_INSTALL := $(SVJSON_ROOT)/contrib/svunit 6 | export PATH := $(SVUNIT_INSTALL)/bin:$(PATH) 7 | 8 | all: test 9 | 10 | test: test_verilator 11 | 12 | test_verilator: 13 | runSVUnit \ 14 | -s verilator \ 15 | -l run.log \ 16 | -c "--binary -j $$(nproc) +incdir+$(EXAMPLES_ROOT)" \ 17 | -f $(SVJSON_ROOT)/src/filelist.f \ 18 | -o work_verilator 19 | 20 | clean: 21 | rm -rf work_* 22 | -------------------------------------------------------------------------------- /antora-playbook.yml: -------------------------------------------------------------------------------- 1 | site: 2 | title: SystemVerilog JSON Package Documentation 3 | start_page: json_pkg::welcome.adoc 4 | content: 5 | sources: 6 | - url: https://github.com/esynr3z/svjson.git 7 | start_path: docs 8 | branches: [main] 9 | tags: v* 10 | asciidoc: 11 | extensions: 12 | - asciidoctor-kroki 13 | attributes: 14 | toc: 'true' 15 | toclevels: 3 16 | source-highlighter: highlightjs 17 | highlightjs-theme: github 18 | ui: 19 | bundle: 20 | url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable 21 | snapshot: true 22 | -------------------------------------------------------------------------------- /tests/golden_json/pizza_indent0.json: -------------------------------------------------------------------------------- 1 | {"calories":850.654321,"comments":[{"author":"Foodie44","rating":4,"text":"Delicious recipe, easy to make!"},{"author":"VeggieLover","rating":null,"text":"Could use more veggies."}],"cookingTime":20,"ingredients":[{"name":"Flour","quantity":300,"unit":"grams"},{"isOrganic":true,"name":"Tomato Sauce","quantity":200,"unit":"ml"},{"isOrganic":false,"name":"Mozzarella Cheese","quantity":150,"unit":"grams"},{"name":"Bell Pepper","quantity":1,"unit":null},{"isFresh":true,"name":"Mushrooms","quantity":100,"unit":"grams"}],"isVegan":false,"nutritionInfo":{"carbs":"100g","fat":"30g","fiber":"5g","protein":"35g"},"recipeName":"Vegetarian Pizza","servings":4,"tags":["pizza","vegetarian","main dish"]} -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/decoder0.svh: -------------------------------------------------------------------------------- 1 | string data = 2 | "{\"recipeName\":\"Vegetarian Pizza\",\"servings\":4,\"isVegan\":true}"; 3 | 4 | json_object jobject; 5 | string recipe_name; 6 | 7 | // Try to load string and get `json_result`, which can be either `json_error` 8 | // or `json_value`. First unwrap() is required to get `json_value` from 9 | // load result and avoid error handling. Second unwrap() is implicit and required 10 | // to avoid error handling of possible unsuccessfull cast to `json_object`. 11 | jobject = json_decoder::load_string(data).unwrap().into_object(); 12 | 13 | // Try to get a string for recipe name. 14 | // unwrap() here is implicit to avoid error handling of possible unsuccessfull 15 | // cast to `json_string`. 16 | recipe_name = jobject.get("recipeName").into_string().get(); 17 | 18 | $display("Recipe name is %s", recipe_name); -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/encoder1.svh: -------------------------------------------------------------------------------- 1 | json_object jobject; 2 | json_error jerror; 3 | json_result#(string) dump_res; 4 | string data; 5 | 6 | jobject = json_object::from('{"the_answer": json_int::from(42)}); 7 | 8 | // Try to dump file and get `json_result`, 9 | // which can be either `json_error` or dumped `string`. 10 | dump_res = json_encoder::dump_file(jobject, "answer.json"); 11 | 12 | // Use "pattern matching" to get value and handle errors 13 | case (1) 14 | dump_res.matches_ok(data): begin 15 | //Dumped data is: 16 | //{"the_answer":42} 17 | $display("Dumped data is:\n%s", data); 18 | end 19 | 20 | dump_res.matches_err_eq(json_error::FILE_NOT_OPENED, jerror): begin 21 | $display("Something wrong with a file!"); 22 | end 23 | 24 | dump_res.matches_err(jerror): begin 25 | $fatal(jerror.to_string()); 26 | end 27 | endcase -------------------------------------------------------------------------------- /tests/golden_json/recipes_indent0.json: -------------------------------------------------------------------------------- 1 | [{"calories":350,"dishName":"Caesar Salad","ingredients":[{"amount":2,"name":"Romaine Lettuce","unit":"cups"},{"amount":50,"name":"Parmesan Cheese","unit":"grams"},{"amount":1,"isGlutenFree":false,"name":"Croutons","unit":"cup"},{"amount":3,"name":"Caesar Dressing","unit":"tablespoons"}],"isGlutenFree":true},{"calories":600,"dishName":"Spaghetti Carbonara","ingredients":[{"amount":200,"name":"Spaghetti","unit":"grams"},{"amount":100,"name":"Bacon","unit":"grams"},{"amount":2,"name":"Eggs"},{"amount":50,"name":"Parmesan Cheese","unit":"grams"}],"isGlutenFree":false},{"calories":450,"dishName":"Mushroom Risotto","ingredients":[{"amount":1,"name":"Arborio Rice","unit":"cup"},{"amount":200,"name":"Mushrooms","unit":"grams"},{"amount":500,"name":"Chicken Stock","unit":"ml"},{"amount":100,"isOptional":true,"name":"White Wine","unit":"ml"}],"isGlutenFree":true}] -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/encodable.svh: -------------------------------------------------------------------------------- 1 | class some_config implements json_object_encodable; 2 | int unsigned max_addr; 3 | bit is_active; 4 | string id; 5 | 6 | // Single method has to be implemented for json_object_encodable interface. 7 | // It has to return associative array of JSON values 8 | virtual function json_object_encodable::values_t to_json_encodable(); 9 | json_object_encodable::values_t values; 10 | values["max_addr"] = json_int::from(longint'(this.max_addr)); 11 | values["is_active"] = json_bool::from(this.is_active); 12 | values["id"] = json_string::from(this.id); 13 | return values; 14 | endfunction: to_json_encodable 15 | endclass : some_config 16 | 17 | // Then any `config` instance can be passed to encoder 18 | function void dump_config(some_config cfg); 19 | void'(json_encoder::dump_file(cfg, "config.json")); 20 | endfunction : dump_config -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/encoder0.svh: -------------------------------------------------------------------------------- 1 | json_object jobject; 2 | string data; 3 | 4 | jobject = json_object::from( 5 | '{ 6 | "recipeName": json_string::from("Meatballs"), 7 | "servings": json_int::from(8), 8 | "isVegan": json_bool::from(0) 9 | } 10 | ); 11 | 12 | // Try to dump to string in the most compact way and get `json_result`, 13 | // which can be either `json_error` or `string`. 14 | // Here unwrap() is required to get `string` and avoid error handling. 15 | // Displays: 16 | //{"isVegan":false,"recipeName":"Meatballs","servings":8} 17 | data = json_encoder::dump_string(jobject).unwrap(); 18 | $display(data); 19 | 20 | // Try to dump using 2 spaces for indentation 21 | // Displays: 22 | //{ 23 | // "isVegan": false, 24 | // "recipeName": "Meatballs", 25 | // "servings": 8 26 | //} 27 | data = json_encoder::dump_string(jobject, .indent_spaces(2)).unwrap(); 28 | $display(data); -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/decoder1.svh: -------------------------------------------------------------------------------- 1 | // Content of pizza.json: 2 | // { 3 | // "recipeName": "Vegetarian Pizza", 4 | // "servings": 4, 5 | // "isVegan": true 6 | // } 7 | 8 | json_error jerror; 9 | json_value jvalue; 10 | 11 | // Try to load file and get `json_result`, 12 | // which can be either `json_error` or `json_value`. 13 | json_result#(json_value) load_res = json_decoder::load_file("pizza.json"); 14 | 15 | // Use "pattern matching" to get value 16 | case (1) 17 | load_res.matches_err(jerror): $fatal(jerror.to_string()); 18 | 19 | load_res.matches_ok(jvalue): begin 20 | json_object jobject; 21 | json_result#(json_object) cast_res = jvalue.try_into_object(); 22 | 23 | // Traditional if..else can be used as well 24 | if (cast_res.matches_err(jerror)) begin 25 | $fatal(jerror.to_string()); 26 | end else if (cast_res.matches_ok(jobject)) begin 27 | $display("Keys of an object: %p", jobject.get_keys()); 28 | end 29 | end 30 | endcase -------------------------------------------------------------------------------- /tests/golden_json/video_indent2.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "An early look at our exciting new feature", 3 | "disclaimer": "Please ensure your sound is on for the best experience.", 4 | "features": { 5 | "interactive": false, 6 | "platforms": [ 7 | { 8 | "name": "Web", 9 | "supportedBrowsers": [ 10 | "Chrome", 11 | "Firefox", 12 | "Edge" 13 | ] 14 | }, 15 | { 16 | "name": "Mobile", 17 | "supportedOS": [ 18 | "iOS", 19 | "Android" 20 | ] 21 | } 22 | ], 23 | "subtitles": null, 24 | "supportedLanguages": [ 25 | "English", 26 | "Spanish", 27 | "French" 28 | ] 29 | }, 30 | "isAvailable": true, 31 | "link": { 32 | "text": "Click here to see the preview!", 33 | "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" 34 | }, 35 | "rating": 4.333333, 36 | "releaseYear": 2021, 37 | "title": "Exclusive Preview", 38 | "viewerCount": null 39 | } -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | permissions: 7 | contents: read 8 | pages: write 9 | id-token: write 10 | 11 | jobs: 12 | docs-antora: 13 | runs-on: ubuntu-latest 14 | 15 | environment: 16 | name: github-pages 17 | url: ${{ steps.deployment.outputs.page_url }} 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Configure Pages 24 | uses: actions/configure-pages@v5 25 | 26 | - name: Install Node.js 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: '18' 30 | 31 | - name: Install Antora 32 | run: npm i antora asciidoctor-kroki 33 | 34 | - name: Generate Site 35 | run: npx antora antora-playbook.yml 36 | 37 | - name: Upload Artifacts 38 | uses: actions/upload-pages-artifact@v3 39 | with: 40 | path: build/site 41 | 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/changelog.adoc: -------------------------------------------------------------------------------- 1 | :url-keep-a-changelog: https://keepachangelog.com/en/1.1.0 2 | :url-semantic-versioning: https://semver.org/spec/v2.0.0.html 3 | 4 | = Changelog 5 | 6 | All notable changes to this project will be documented in this file. 7 | 8 | The format is based on {url-keep-a-changelog}[Keep a Changelog], 9 | and this project adheres to {url-semantic-versioning}[Semantic Versioning]. 10 | 11 | == [Unreleased] 12 | === Added 13 | === Fixed 14 | === Changed 15 | === Removed 16 | 17 | == [1.1.0] - 2024-07-07 18 | 19 | === Added 20 | 21 | * Add a table of supported EDA tools in documentation 22 | * Add Verilator as a linter 23 | * Add Modelsim as a linter 24 | * Add scripts to run tests in Modelsim 25 | * Update CI to run linting and tests in Modelsim 26 | * Add VCS as a linter 27 | * Add scripts to run tests in VCS 28 | * Add Xcelium as a linter 29 | * Add scripts to run tests in Xcelium 30 | 31 | === Fixed 32 | 33 | * Fix minor issues within `json_pkg` to support Modelsim 34 | 35 | == [1.0.0] - 2024-06-06 36 | 37 | === Added 38 | 39 | - Implement JSON library 40 | - Implement infrastructure 41 | - Write documentation 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 esynr3z 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/golden_json/video.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Exclusive Preview", 3 | "description": "An early look at our exciting new feature", 4 | "releaseYear": 2021, 5 | "isAvailable": true, 6 | "rating": 4.333333, 7 | "viewerCount": null, 8 | "link": { 9 | "text": "Click here to see the preview!", 10 | "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" 11 | }, 12 | "features": { 13 | "interactive": false, 14 | "subtitles": null, 15 | "supportedLanguages": [ 16 | "English", 17 | "Spanish", 18 | "French" 19 | ], 20 | "platforms": [ 21 | { 22 | "name": "Web", 23 | "supportedBrowsers": [ 24 | "Chrome", 25 | "Firefox", 26 | "Edge" 27 | ] 28 | }, 29 | { 30 | "name": "Mobile", 31 | "supportedOS": [ 32 | "iOS", 33 | "Android" 34 | ] 35 | } 36 | ] 37 | }, 38 | "disclaimer": "Please ensure your sound is on for the best experience." 39 | } -------------------------------------------------------------------------------- /tests/golden_json/video_indent4.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "An early look at our exciting new feature", 3 | "disclaimer": "Please ensure your sound is on for the best experience.", 4 | "features": { 5 | "interactive": false, 6 | "platforms": [ 7 | { 8 | "name": "Web", 9 | "supportedBrowsers": [ 10 | "Chrome", 11 | "Firefox", 12 | "Edge" 13 | ] 14 | }, 15 | { 16 | "name": "Mobile", 17 | "supportedOS": [ 18 | "iOS", 19 | "Android" 20 | ] 21 | } 22 | ], 23 | "subtitles": null, 24 | "supportedLanguages": [ 25 | "English", 26 | "Spanish", 27 | "French" 28 | ] 29 | }, 30 | "isAvailable": true, 31 | "link": { 32 | "text": "Click here to see the preview!", 33 | "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" 34 | }, 35 | "rating": 4.333333, 36 | "releaseYear": 2021, 37 | "title": "Exclusive Preview", 38 | "viewerCount": null 39 | } -------------------------------------------------------------------------------- /tests/golden_json/pizza_indent2.json: -------------------------------------------------------------------------------- 1 | { 2 | "calories": 850.654321, 3 | "comments": [ 4 | { 5 | "author": "Foodie44", 6 | "rating": 4, 7 | "text": "Delicious recipe, easy to make!" 8 | }, 9 | { 10 | "author": "VeggieLover", 11 | "rating": null, 12 | "text": "Could use more veggies." 13 | } 14 | ], 15 | "cookingTime": 20, 16 | "ingredients": [ 17 | { 18 | "name": "Flour", 19 | "quantity": 300, 20 | "unit": "grams" 21 | }, 22 | { 23 | "isOrganic": true, 24 | "name": "Tomato Sauce", 25 | "quantity": 200, 26 | "unit": "ml" 27 | }, 28 | { 29 | "isOrganic": false, 30 | "name": "Mozzarella Cheese", 31 | "quantity": 150, 32 | "unit": "grams" 33 | }, 34 | { 35 | "name": "Bell Pepper", 36 | "quantity": 1, 37 | "unit": null 38 | }, 39 | { 40 | "isFresh": true, 41 | "name": "Mushrooms", 42 | "quantity": 100, 43 | "unit": "grams" 44 | } 45 | ], 46 | "isVegan": false, 47 | "nutritionInfo": { 48 | "carbs": "100g", 49 | "fat": "30g", 50 | "fiber": "5g", 51 | "protein": "35g" 52 | }, 53 | "recipeName": "Vegetarian Pizza", 54 | "servings": 4, 55 | "tags": [ 56 | "pizza", 57 | "vegetarian", 58 | "main dish" 59 | ] 60 | } -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/developer.adoc: -------------------------------------------------------------------------------- 1 | :url-svunit: https://github.com/svunit/svunit 2 | :url-json-test-suite: https://github.com/nst/JSONTestSuite 3 | :url-verilator-github: https://github.com/verilator/verilator 4 | :url-antora: https://antora.org 5 | :url-asciidoc: https://asciidoc.org 6 | :url-github-pages: https://pages.github.com 7 | :url-conventional-commits: https://www.conventionalcommits.org/en/v1.0.0 8 | :url-keep-a-changelog: https://keepachangelog.com/en/1.1.0 9 | :url-semantic-versioning: https://semver.org/spec/v2.0.0.html 10 | 11 | = Developer guide 12 | 13 | == HDL 14 | 15 | * Code tested with {url-svunit}[SVUnit] using {url-json-test-suite}[JSONTestSuite] and custom tests 16 | * Main simulator for development is {url-verilator-github}[Verilator 5.24] 17 | * [TODO] No linting and code formating tools are used yet 18 | 19 | == Documentation 20 | 21 | * All documentation is written in {url-asciidoc}[Asciidoc] 22 | * Documentation site ({url-github-pages}[Github Pages]) is generated via {url-antora}[Antora] 23 | * [TODO] NaturalDocs generated class reference documentation 24 | 25 | == Git 26 | 27 | * Git messages follows {url-conventional-commits}[Conventional Commits] specification 28 | * Changelog format follows {url-keep-a-changelog}[Keep a Changelog] specification 29 | * Versioning is based on {url-semantic-versioning}[Semantic Versioning]. 30 | -------------------------------------------------------------------------------- /tests/golden_json/pizza.json: -------------------------------------------------------------------------------- 1 | { 2 | "recipeName": "Vegetarian Pizza", 3 | "servings": 4, 4 | "isVegan": false, 5 | "ingredients": [ 6 | { 7 | "name": "Flour", 8 | "quantity": 300, 9 | "unit": "grams" 10 | }, 11 | { 12 | "name": "Tomato Sauce", 13 | "quantity": 200, 14 | "unit": "ml", 15 | "isOrganic": true 16 | }, 17 | { 18 | "name": "Mozzarella Cheese", 19 | "quantity": 150, 20 | "unit": "grams", 21 | "isOrganic": false 22 | }, 23 | { 24 | "name": "Bell Pepper", 25 | "quantity": 1, 26 | "unit": null 27 | }, 28 | { 29 | "name": "Mushrooms", 30 | "quantity": 100, 31 | "unit": "grams", 32 | "isFresh": true 33 | } 34 | ], 35 | "cookingTime": 20, 36 | "calories": 850.654321, 37 | "tags": [ 38 | "pizza", 39 | "vegetarian", 40 | "main dish" 41 | ], 42 | "nutritionInfo": { 43 | "carbs": "100g", 44 | "protein": "35g", 45 | "fat": "30g", 46 | "fiber": "5g" 47 | }, 48 | "comments": [ 49 | { 50 | "author": "Foodie44", 51 | "rating": 4, 52 | "text": "Delicious recipe, easy to make!" 53 | }, 54 | { 55 | "author": "VeggieLover", 56 | "rating": null, 57 | "text": "Could use more veggies." 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /tests/golden_json/json_gen.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | with open("pizza.json", "r", encoding="utf-8") as file: 4 | data = json.load(file) 5 | with open("pizza_indent0.json", "w", encoding="utf-8") as file: 6 | json.dump(data, file, indent=None, separators=(",", ":"), sort_keys=True) 7 | with open("pizza_indent2.json", "w", encoding="utf-8") as file: 8 | json.dump(data, file, indent=2, sort_keys=True) 9 | with open("pizza_indent4.json", "w", encoding="utf-8") as file: 10 | json.dump(data, file, indent=4, sort_keys=True) 11 | 12 | with open("recipes.json", "r", encoding="utf-8") as file: 13 | data = json.load(file) 14 | with open("recipes_indent0.json", "w", encoding="utf-8") as file: 15 | json.dump(data, file, indent=None, separators=(",", ":"), sort_keys=True) 16 | with open("recipes_indent2.json", "w", encoding="utf-8") as file: 17 | json.dump(data, file, indent=2, sort_keys=True) 18 | with open("recipes_indent4.json", "w", encoding="utf-8") as file: 19 | json.dump(data, file, indent=4, sort_keys=True) 20 | 21 | with open("video.json", "r", encoding="utf-8") as file: 22 | data = json.load(file) 23 | with open("video_indent0.json", "w", encoding="utf-8") as file: 24 | json.dump(data, file, indent=None, separators=(",", ":"), sort_keys=True) 25 | with open("video_indent2.json", "w", encoding="utf-8") as file: 26 | json.dump(data, file, indent=2, sort_keys=True) 27 | with open("video_indent4.json", "w", encoding="utf-8") as file: 28 | json.dump(data, file, indent=4, sort_keys=True) 29 | -------------------------------------------------------------------------------- /tests/golden_json/pizza_indent4.json: -------------------------------------------------------------------------------- 1 | { 2 | "calories": 850.654321, 3 | "comments": [ 4 | { 5 | "author": "Foodie44", 6 | "rating": 4, 7 | "text": "Delicious recipe, easy to make!" 8 | }, 9 | { 10 | "author": "VeggieLover", 11 | "rating": null, 12 | "text": "Could use more veggies." 13 | } 14 | ], 15 | "cookingTime": 20, 16 | "ingredients": [ 17 | { 18 | "name": "Flour", 19 | "quantity": 300, 20 | "unit": "grams" 21 | }, 22 | { 23 | "isOrganic": true, 24 | "name": "Tomato Sauce", 25 | "quantity": 200, 26 | "unit": "ml" 27 | }, 28 | { 29 | "isOrganic": false, 30 | "name": "Mozzarella Cheese", 31 | "quantity": 150, 32 | "unit": "grams" 33 | }, 34 | { 35 | "name": "Bell Pepper", 36 | "quantity": 1, 37 | "unit": null 38 | }, 39 | { 40 | "isFresh": true, 41 | "name": "Mushrooms", 42 | "quantity": 100, 43 | "unit": "grams" 44 | } 45 | ], 46 | "isVegan": false, 47 | "nutritionInfo": { 48 | "carbs": "100g", 49 | "fat": "30g", 50 | "fiber": "5g", 51 | "protein": "35g" 52 | }, 53 | "recipeName": "Vegetarian Pizza", 54 | "servings": 4, 55 | "tags": [ 56 | "pizza", 57 | "vegetarian", 58 | "main dish" 59 | ] 60 | } -------------------------------------------------------------------------------- /docs/modules/ROOT/examples/selftest/examples_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | 3 | // Tests of all examples used in docs 4 | module examples_unit_test; 5 | import svunit_pkg::svunit_testcase; 6 | import json_pkg::*; 7 | 8 | string name = "examples_ut"; 9 | svunit_testcase svunit_ut; 10 | 11 | function void build(); 12 | svunit_ut = new(name); 13 | endfunction 14 | 15 | task setup(); 16 | svunit_ut.setup(); 17 | endtask 18 | 19 | task teardown(); 20 | svunit_ut.teardown(); 21 | endtask 22 | 23 | function automatic void write_file(string name, string text); 24 | int file_descr = $fopen(name, "w"); 25 | $fwrite(file_descr, text); 26 | $fclose(file_descr); 27 | endfunction : write_file 28 | 29 | `include "encodable.svh" 30 | 31 | `SVUNIT_TESTS_BEGIN 32 | 33 | `SVTEST(decoder0_test) begin 34 | `include "decoder0.svh" 35 | end `SVTEST_END 36 | 37 | 38 | `SVTEST(decoder1_test) begin 39 | write_file( 40 | "pizza.json", 41 | { 42 | "{\n", 43 | " \"recipeName\": \"Vegetarian Pizza\",\n", 44 | " \"servings\": 4,\n", 45 | " \"isVegan\": true\n", 46 | "}" 47 | } 48 | ); 49 | begin 50 | `include "decoder1.svh" 51 | end 52 | end `SVTEST_END 53 | 54 | 55 | `SVTEST(encoder0_test) begin 56 | `include "encoder0.svh" 57 | end `SVTEST_END 58 | 59 | 60 | `SVTEST(encoder1_test) begin 61 | `include "encoder1.svh" 62 | end `SVTEST_END 63 | 64 | 65 | `SVTEST(encodable_test) begin 66 | some_config cfg = new(); 67 | 68 | cfg.max_addr = 32768; 69 | cfg.is_active = 0; 70 | cfg.id = "super_agent"; 71 | 72 | dump_config(cfg); 73 | end `SVTEST_END 74 | 75 | `SVUNIT_TESTS_END 76 | 77 | 78 | endmodule : examples_unit_test 79 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include scripts/common.mk 2 | 3 | .PHONY: all \ 4 | lint lint_verilator lint_modelsim lint_vcs lint_xcelium \ 5 | test test_src test_examples 6 | 7 | export SVJSON_ROOT := $(realpath .) 8 | 9 | all: lint test 10 | 11 | lint: lint_verilator lint_modelsim lint_vcs lint_xcelium 12 | 13 | # VARHIDDEN - too strict, method arguments may overlap with class property names 14 | # UNDRIVEN - has false positives on interface classes and custom constructors 15 | lint_verilator: 16 | @echo "Lint sources with Verilator" 17 | verilator --lint-only -f $(SVJSON_ROOT)/src/filelist.f \ 18 | -Wall -Wno-VARHIDDEN -Wno-UNDRIVEN 19 | 20 | lint_modelsim: 21 | @echo "Lint sources with Modelsim" 22 | $(call run_if_exist,vsim, \ 23 | mkdir -p work_lint_modelsim && \ 24 | cd work_lint_modelsim && \ 25 | vlib work && \ 26 | vlog -l log.txt -sv -warning error -f $(SVJSON_ROOT)/src/filelist.f \ 27 | ) 28 | 29 | lint_vcs: 30 | @echo "Lint sources with VCS" 31 | $(call run_if_exist,vcs, \ 32 | mkdir -p work_lint_vcs && \ 33 | cd work_lint_vcs && \ 34 | vcs -full64 -sverilog -l log.txt +lint=all -error=all \ 35 | -f $(SVJSON_ROOT)/src/filelist.f \ 36 | ) 37 | 38 | lint_xcelium: 39 | @echo "Lint sources with Xcelium" 40 | $(call run_if_exist,xrun, \ 41 | mkdir -p work_lint_xrun && \ 42 | cd work_lint_xrun && \ 43 | xrun -64bit -clean -elaborate -disable_sem2009 -l log.txt -sv -xmallerror \ 44 | -f $(SVJSON_ROOT)/src/filelist.f \ 45 | ) 46 | 47 | test: test_src test_examples 48 | 49 | test_src: 50 | @echo "Run tests for main sources" 51 | make -C tests test 52 | 53 | test_examples: 54 | @echo "Run tests for documentation example snippets" 55 | make -C docs/modules/ROOT/examples/selftest test 56 | 57 | clean: 58 | rm -rf work_* 59 | make -C tests clean 60 | make -C docs/modules/ROOT/examples/selftest clean 61 | -------------------------------------------------------------------------------- /tests/golden_json/recipes_indent2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "calories": 350, 4 | "dishName": "Caesar Salad", 5 | "ingredients": [ 6 | { 7 | "amount": 2, 8 | "name": "Romaine Lettuce", 9 | "unit": "cups" 10 | }, 11 | { 12 | "amount": 50, 13 | "name": "Parmesan Cheese", 14 | "unit": "grams" 15 | }, 16 | { 17 | "amount": 1, 18 | "isGlutenFree": false, 19 | "name": "Croutons", 20 | "unit": "cup" 21 | }, 22 | { 23 | "amount": 3, 24 | "name": "Caesar Dressing", 25 | "unit": "tablespoons" 26 | } 27 | ], 28 | "isGlutenFree": true 29 | }, 30 | { 31 | "calories": 600, 32 | "dishName": "Spaghetti Carbonara", 33 | "ingredients": [ 34 | { 35 | "amount": 200, 36 | "name": "Spaghetti", 37 | "unit": "grams" 38 | }, 39 | { 40 | "amount": 100, 41 | "name": "Bacon", 42 | "unit": "grams" 43 | }, 44 | { 45 | "amount": 2, 46 | "name": "Eggs" 47 | }, 48 | { 49 | "amount": 50, 50 | "name": "Parmesan Cheese", 51 | "unit": "grams" 52 | } 53 | ], 54 | "isGlutenFree": false 55 | }, 56 | { 57 | "calories": 450, 58 | "dishName": "Mushroom Risotto", 59 | "ingredients": [ 60 | { 61 | "amount": 1, 62 | "name": "Arborio Rice", 63 | "unit": "cup" 64 | }, 65 | { 66 | "amount": 200, 67 | "name": "Mushrooms", 68 | "unit": "grams" 69 | }, 70 | { 71 | "amount": 500, 72 | "name": "Chicken Stock", 73 | "unit": "ml" 74 | }, 75 | { 76 | "amount": 100, 77 | "isOptional": true, 78 | "name": "White Wine", 79 | "unit": "ml" 80 | } 81 | ], 82 | "isGlutenFree": true 83 | } 84 | ] -------------------------------------------------------------------------------- /src/values/json_bool.sv: -------------------------------------------------------------------------------- 1 | // JSON bool. 2 | // This wrapper class represens standard JSON bool value type using SV bit. 3 | class json_bool extends json_value implements json_bool_encodable; 4 | // Internal raw value 5 | protected bit value; 6 | 7 | // Normal constructor 8 | extern function new(bit value); 9 | 10 | // Create json_bool from 1-bit value 11 | extern static function json_bool from(bit value); 12 | 13 | // Create a deep copy of an instance 14 | extern virtual function json_value clone(); 15 | 16 | // Compare with another instance. 17 | // Return 1 if instances are equal and 0 otherwise. 18 | extern virtual function bit compare(json_value value); 19 | 20 | // Get internal 1-bit value 21 | extern virtual function bit get(); 22 | 23 | // Set internal 1-bit value 24 | extern virtual function void set(bit value); 25 | 26 | // Get value encodable as JSON bool (for interface json_bool_encodable) 27 | extern virtual function bit to_json_encodable(); 28 | endclass : json_bool 29 | 30 | 31 | function json_bool::new(bit value); 32 | this.value = value; 33 | endfunction : new 34 | 35 | 36 | function json_bool json_bool::from(bit value); 37 | json_bool obj = new(value); 38 | return obj; 39 | endfunction : from 40 | 41 | 42 | function json_value json_bool::clone(); 43 | return json_bool::from(get()); 44 | endfunction : clone 45 | 46 | 47 | function bit json_bool::compare(json_value value); 48 | json_result#(json_bool) casted; 49 | json_error err; 50 | json_bool rhs; 51 | 52 | if (value == null) begin 53 | return 0; 54 | end 55 | 56 | casted = value.try_into_bool(); 57 | case (1) 58 | casted.matches_err(err): return 0; 59 | casted.matches_ok(rhs): return get() == rhs.get(); 60 | endcase 61 | endfunction : compare 62 | 63 | 64 | function bit json_bool::get(); 65 | return this.value; 66 | endfunction : get 67 | 68 | 69 | function void json_bool::set(bit value); 70 | this.value = value; 71 | endfunction : set 72 | 73 | 74 | function bit json_bool::to_json_encodable(); 75 | return get(); 76 | endfunction : to_json_encodable 77 | -------------------------------------------------------------------------------- /src/values/json_string.sv: -------------------------------------------------------------------------------- 1 | // JSON string. 2 | // This wrapper class represens standard JSON string value type using SV string. 3 | class json_string extends json_value implements json_string_encodable; 4 | // Internal raw value 5 | protected string value; 6 | 7 | // Normal constructor 8 | extern function new(string value); 9 | 10 | // Create `json_string` from string 11 | extern static function json_string from(string value); 12 | 13 | // Create a deep copy of an instance 14 | extern virtual function json_value clone(); 15 | 16 | // Compare with another instance. 17 | // Return 1 if instances are equal and 0 otherwise. 18 | extern virtual function bit compare(json_value value); 19 | 20 | // Get internal string value 21 | extern virtual function string get(); 22 | 23 | // Set internal string value 24 | extern virtual function void set(string value); 25 | 26 | // Get value encodable as JSON string (for interface json_string_encodable) 27 | extern virtual function string to_json_encodable(); 28 | endclass : json_string 29 | 30 | 31 | function json_string::new(string value); 32 | this.value = value; 33 | endfunction : new 34 | 35 | 36 | function json_string json_string::from(string value); 37 | json_string obj = new(value); 38 | return obj; 39 | endfunction : from 40 | 41 | 42 | function json_value json_string::clone(); 43 | return json_string::from(get()); 44 | endfunction : clone 45 | 46 | 47 | function bit json_string::compare(json_value value); 48 | json_result#(json_string) casted; 49 | json_error err; 50 | json_string rhs; 51 | 52 | if (value == null) begin 53 | return 0; 54 | end 55 | 56 | casted = value.try_into_string(); 57 | case (1) 58 | casted.matches_err(err): return 0; 59 | casted.matches_ok(rhs): return get() == rhs.get(); 60 | endcase 61 | endfunction : compare 62 | 63 | 64 | function string json_string::get(); 65 | return this.value; 66 | endfunction : get 67 | 68 | 69 | function void json_string::set(string value); 70 | this.value = value; 71 | endfunction : set 72 | 73 | 74 | function string json_string::to_json_encodable(); 75 | return get(); 76 | endfunction : to_json_encodable 77 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | include ../scripts/common.mk 2 | 3 | .PHONY: all clean test test_verilator test_modelsim test_vcs test_xcelium 4 | 5 | extra_args ?= 6 | 7 | export SVJSON_ROOT := $(realpath ..) 8 | export SVUNIT_INSTALL := $(SVJSON_ROOT)/contrib/svunit 9 | export JSON_TEST_SUITE_INSTALL := $(SVJSON_ROOT)/contrib/json_test_suite 10 | export GOLDEN_JSON_DIR := $(SVJSON_ROOT)/tests/golden_json 11 | export PATH := $(SVUNIT_INSTALL)/bin:$(PATH) 12 | 13 | all: test 14 | 15 | test: test_verilator test_modelsim test_vcs test_xcelium 16 | 17 | test_verilator: 18 | @echo Test sources with Verilator 19 | runSVUnit \ 20 | -s verilator \ 21 | -l run.log \ 22 | -c "--binary -j $$(nproc) \ 23 | +define+JSON_TEST_SUITE_INSTALL=$(JSON_TEST_SUITE_INSTALL) \ 24 | +define+GOLDEN_JSON_DIR=$(GOLDEN_JSON_DIR)" \ 25 | -f $(SVJSON_ROOT)/src/filelist.f \ 26 | -f $(SVJSON_ROOT)/tests/filelist.f \ 27 | $(extra_args) \ 28 | -o work_verilator 29 | 30 | test_modelsim: 31 | @echo Test sources with Modelsim 32 | $(call run_if_exist,vsim, \ 33 | runSVUnit \ 34 | -s modelsim \ 35 | -l run.log \ 36 | -c "-sv \ 37 | +define+JSON_TEST_SUITE_INSTALL=$(JSON_TEST_SUITE_INSTALL) \ 38 | +define+GOLDEN_JSON_DIR=$(GOLDEN_JSON_DIR)" \ 39 | -f $(SVJSON_ROOT)/src/filelist.f \ 40 | -f $(SVJSON_ROOT)/tests/filelist.f \ 41 | $(extra_args) \ 42 | -o work_modelsim \ 43 | ) 44 | 45 | test_vcs: 46 | @echo Test sources with VCS 47 | $(call run_if_exist,vcs, \ 48 | runSVUnit \ 49 | -s vcs \ 50 | -l run.log \ 51 | -c "-full64 \ 52 | +define+JSON_TEST_SUITE_INSTALL=$(JSON_TEST_SUITE_INSTALL) \ 53 | +define+GOLDEN_JSON_DIR=$(GOLDEN_JSON_DIR)" \ 54 | -f $(SVJSON_ROOT)/src/filelist.f \ 55 | -f $(SVJSON_ROOT)/tests/filelist.f \ 56 | $(extra_args) \ 57 | -o work_vcs \ 58 | ) 59 | 60 | test_xcelium: 61 | @echo Test sources with Xcelium 62 | $(call run_if_exist,xrun, \ 63 | runSVUnit \ 64 | -s xcelium \ 65 | -l run.log \ 66 | -c "-64bit \ 67 | +define+JSON_TEST_SUITE_INSTALL=$(JSON_TEST_SUITE_INSTALL) \ 68 | +define+GOLDEN_JSON_DIR=$(GOLDEN_JSON_DIR)" \ 69 | -f $(SVJSON_ROOT)/src/filelist.f \ 70 | -f $(SVJSON_ROOT)/tests/filelist.f \ 71 | $(extra_args) \ 72 | -o work_xcelium \ 73 | ) 74 | 75 | clean: 76 | rm -rf work_* 77 | -------------------------------------------------------------------------------- /src/values/json_real.sv: -------------------------------------------------------------------------------- 1 | // JSON real number. 2 | // This wrapper class represens standard JSON number value type using SV real. 3 | // JSON does not specify requirements for number types, but it is more 4 | // convenient to operate with integers and real numbers separately. 5 | // This class covers real numbers. 6 | class json_real extends json_value implements json_real_encodable; 7 | // Internal raw value 8 | protected real value; 9 | 10 | // Normal constructor 11 | extern function new(real value); 12 | 13 | // Create json_real from real 14 | extern static function json_real from(real value); 15 | 16 | // Create a deep copy of an instance 17 | extern virtual function json_value clone(); 18 | 19 | // Compare with another instance. 20 | // Return 1 if instances are equal and 0 otherwise. 21 | extern virtual function bit compare(json_value value); 22 | 23 | // Get internal real value 24 | extern virtual function real get(); 25 | 26 | // Set internal real value 27 | extern virtual function void set(real value); 28 | 29 | // Get value encodable as JSON real number (for interface json_real_encodable) 30 | extern virtual function real to_json_encodable(); 31 | endclass : json_real 32 | 33 | 34 | function json_real::new(real value); 35 | this.value = value; 36 | endfunction : new 37 | 38 | 39 | function json_real json_real::from(real value); 40 | json_real obj = new(value); 41 | return obj; 42 | endfunction : from 43 | 44 | 45 | function json_value json_real::clone(); 46 | return json_real::from(get()); 47 | endfunction : clone 48 | 49 | 50 | function bit json_real::compare(json_value value); 51 | json_result#(json_real) casted; 52 | json_error err; 53 | json_real rhs; 54 | 55 | if (value == null) begin 56 | return 0; 57 | end 58 | 59 | casted = value.try_into_real(); 60 | case (1) 61 | casted.matches_err(err): return 0; 62 | casted.matches_ok(rhs): return get() == rhs.get(); 63 | endcase 64 | endfunction : compare 65 | 66 | 67 | function real json_real::get(); 68 | return this.value; 69 | endfunction : get 70 | 71 | 72 | function void json_real::set(real value); 73 | this.value = value; 74 | endfunction : set 75 | 76 | 77 | function real json_real::to_json_encodable(); 78 | return get(); 79 | endfunction : to_json_encodable 80 | -------------------------------------------------------------------------------- /src/values/json_int.sv: -------------------------------------------------------------------------------- 1 | // JSON integer number. 2 | // This wrapper class represens standard JSON number value using SV longint. 3 | // JSON does not specify requirements for number types, but it is more 4 | // convenient to operate with integers and real numbers separately. 5 | // This class covers integers. 6 | class json_int extends json_value implements json_int_encodable; 7 | // Internal raw value 8 | protected longint value; 9 | 10 | // Normal constructor 11 | extern function new(longint value); 12 | 13 | // Create json_int from longint 14 | extern static function json_int from(longint value); 15 | 16 | // Create a deep copy of an instance 17 | extern virtual function json_value clone(); 18 | 19 | // Compare with another instance. 20 | // Return 1 if instances are equal and 0 otherwise. 21 | extern virtual function bit compare(json_value value); 22 | 23 | // Get internal longint value 24 | extern virtual function longint get(); 25 | 26 | // Set internal longint value 27 | extern virtual function void set(longint value); 28 | 29 | // Get value encodable as JSON integer number (for interface json_int_encodable) 30 | extern virtual function longint to_json_encodable(); 31 | endclass : json_int 32 | 33 | 34 | function json_int::new(longint value); 35 | this.value = value; 36 | endfunction : new 37 | 38 | 39 | function json_int json_int::from(longint value); 40 | json_int obj = new(value); 41 | return obj; 42 | endfunction : from 43 | 44 | 45 | function json_value json_int::clone(); 46 | return json_int::from(get()); 47 | endfunction : clone 48 | 49 | 50 | function bit json_int::compare(json_value value); 51 | json_result#(json_int) casted; 52 | json_error err; 53 | json_int rhs; 54 | 55 | if (value == null) begin 56 | return 0; 57 | end 58 | 59 | casted = value.try_into_int(); 60 | case (1) 61 | casted.matches_err(err): return 0; 62 | casted.matches_ok(rhs): return get() == rhs.get(); 63 | endcase 64 | endfunction : compare 65 | 66 | 67 | function longint json_int::get(); 68 | return this.value; 69 | endfunction : get 70 | 71 | 72 | function void json_int::set(longint value); 73 | this.value = value; 74 | endfunction : set 75 | 76 | 77 | function longint json_int::to_json_encodable(); 78 | return get(); 79 | endfunction : to_json_encodable 80 | -------------------------------------------------------------------------------- /tests/golden_json/recipes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dishName": "Caesar Salad", 4 | "isGlutenFree": true, 5 | "ingredients": [ 6 | { 7 | "name": "Romaine Lettuce", 8 | "amount": 2, 9 | "unit": "cups" 10 | }, 11 | { 12 | "name": "Parmesan Cheese", 13 | "amount": 50, 14 | "unit": "grams" 15 | }, 16 | { 17 | "name": "Croutons", 18 | "amount": 1, 19 | "unit": "cup", 20 | "isGlutenFree": false 21 | }, 22 | { 23 | "name": "Caesar Dressing", 24 | "amount": 3, 25 | "unit": "tablespoons" 26 | } 27 | ], 28 | "calories": 350 29 | }, 30 | { 31 | "dishName": "Spaghetti Carbonara", 32 | "isGlutenFree": false, 33 | "ingredients": [ 34 | { 35 | "name": "Spaghetti", 36 | "amount": 200, 37 | "unit": "grams" 38 | }, 39 | { 40 | "name": "Bacon", 41 | "amount": 100, 42 | "unit": "grams" 43 | }, 44 | { 45 | "name": "Eggs", 46 | "amount": 2 47 | }, 48 | { 49 | "name": "Parmesan Cheese", 50 | "amount": 50, 51 | "unit": "grams" 52 | } 53 | ], 54 | "calories": 600 55 | }, 56 | { 57 | "dishName": "Mushroom Risotto", 58 | "isGlutenFree": true, 59 | "ingredients": [ 60 | { 61 | "name": "Arborio Rice", 62 | "amount": 1, 63 | "unit": "cup" 64 | }, 65 | { 66 | "name": "Mushrooms", 67 | "amount": 200, 68 | "unit": "grams" 69 | }, 70 | { 71 | "name": "Chicken Stock", 72 | "amount": 500, 73 | "unit": "ml" 74 | }, 75 | { 76 | "name": "White Wine", 77 | "amount": 100, 78 | "unit": "ml", 79 | "isOptional": true 80 | } 81 | ], 82 | "calories": 450 83 | } 84 | ] -------------------------------------------------------------------------------- /tests/golden_json/recipes_indent4.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "calories": 350, 4 | "dishName": "Caesar Salad", 5 | "ingredients": [ 6 | { 7 | "amount": 2, 8 | "name": "Romaine Lettuce", 9 | "unit": "cups" 10 | }, 11 | { 12 | "amount": 50, 13 | "name": "Parmesan Cheese", 14 | "unit": "grams" 15 | }, 16 | { 17 | "amount": 1, 18 | "isGlutenFree": false, 19 | "name": "Croutons", 20 | "unit": "cup" 21 | }, 22 | { 23 | "amount": 3, 24 | "name": "Caesar Dressing", 25 | "unit": "tablespoons" 26 | } 27 | ], 28 | "isGlutenFree": true 29 | }, 30 | { 31 | "calories": 600, 32 | "dishName": "Spaghetti Carbonara", 33 | "ingredients": [ 34 | { 35 | "amount": 200, 36 | "name": "Spaghetti", 37 | "unit": "grams" 38 | }, 39 | { 40 | "amount": 100, 41 | "name": "Bacon", 42 | "unit": "grams" 43 | }, 44 | { 45 | "amount": 2, 46 | "name": "Eggs" 47 | }, 48 | { 49 | "amount": 50, 50 | "name": "Parmesan Cheese", 51 | "unit": "grams" 52 | } 53 | ], 54 | "isGlutenFree": false 55 | }, 56 | { 57 | "calories": 450, 58 | "dishName": "Mushroom Risotto", 59 | "ingredients": [ 60 | { 61 | "amount": 1, 62 | "name": "Arborio Rice", 63 | "unit": "cup" 64 | }, 65 | { 66 | "amount": 200, 67 | "name": "Mushrooms", 68 | "unit": "grams" 69 | }, 70 | { 71 | "amount": 500, 72 | "name": "Chicken Stock", 73 | "unit": "ml" 74 | }, 75 | { 76 | "amount": 100, 77 | "isOptional": true, 78 | "name": "White Wine", 79 | "unit": "ml" 80 | } 81 | ], 82 | "isGlutenFree": true 83 | } 84 | ] -------------------------------------------------------------------------------- /src/json_pkg.sv: -------------------------------------------------------------------------------- 1 | package json_pkg; 2 | 3 | // Forward declarations 4 | typedef json_value; 5 | typedef json_object; 6 | typedef json_array; 7 | typedef json_string; 8 | typedef json_int; 9 | typedef json_real; 10 | typedef json_bool; 11 | 12 | // Alias to raise syntax errors in a more compact way 13 | `define JSON_SYNTAX_ERR(KIND, STR, IDX, DESCR="")\ 14 | parser_result::err( \ 15 | json_error::create( \ 16 | .kind(KIND), \ 17 | .description(DESCR), \ 18 | .json_str(STR), \ 19 | .json_pos(IDX), \ 20 | .source_file(`__FILE__), \ 21 | .source_line(`__LINE__) \ 22 | ) \ 23 | ) 24 | 25 | // Alias to raise internal error in a more compact way 26 | `define JSON_INTERNAL_ERR(DESCR="", RES_T=parser_result)\ 27 | RES_T::err( \ 28 | json_error::create( \ 29 | .kind(json_error::INTERNAL), \ 30 | .description(DESCR), \ 31 | .source_file(`__FILE__), \ 32 | .source_line(`__LINE__) \ 33 | ) \ 34 | ) 35 | 36 | // Alias to raise common error 37 | `define JSON_ERR(KIND, DESCR="", VAL_T=json_value)\ 38 | json_result#(VAL_T)::err( \ 39 | json_error::create( \ 40 | .kind(KIND), \ 41 | .description(DESCR), \ 42 | .source_file(`__FILE__), \ 43 | .source_line(`__LINE__) \ 44 | ) \ 45 | ) 46 | 47 | // Utility classes 48 | `include "json_error.sv" 49 | `include "json_result.sv" 50 | 51 | // Interface classes for encoding standard JSON value types 52 | `include "json_value_encodable.sv" 53 | `include "json_object_encodable.sv" 54 | `include "json_array_encodable.sv" 55 | `include "json_string_encodable.sv" 56 | `include "json_int_encodable.sv" 57 | `include "json_real_encodable.sv" 58 | `include "json_bool_encodable.sv" 59 | 60 | // Wrapper classes to represent standard JSON value types 61 | `include "json_value.sv" 62 | `include "json_object.sv" 63 | `include "json_array.sv" 64 | `include "json_string.sv" 65 | `include "json_int.sv" 66 | `include "json_real.sv" 67 | `include "json_bool.sv" 68 | 69 | // Extension classes to handle SV specific types 70 | `include "json_enum.sv" 71 | `include "json_bits.sv" 72 | 73 | // JSON processors 74 | `include "json_decoder.sv" 75 | `include "json_encoder.sv" 76 | 77 | `undef JSON_SYNTAX_ERR 78 | `undef JSON_INTERNAL_ERR 79 | `undef JSON_ERR 80 | endpackage : json_pkg 81 | -------------------------------------------------------------------------------- /tests/test_utils_macros.svh: -------------------------------------------------------------------------------- 1 | `ifndef TEST_UTILS_MACROS_SVH 2 | 3 | // Macro to create string from a preprocessor text 4 | `define STRINGIFY(x) `"x`" 5 | 6 | // Automation macro to hide boilerplate of checking JSON string to be successfuly parsed. 7 | // If `GOLDEN_VAL` is provided, then parsed value is compared with the golden one. 8 | // Otherwise, only parsing status (err/ok) is evaluated. 9 | `define EXPECT_OK_LOAD_STR(RAW_STR, GOLDEN_VAL=null)\ 10 | begin \ 11 | string err_msg = test_utils_pkg::expect_ok_load_str(RAW_STR, GOLDEN_VAL);\ 12 | `FAIL_IF_LOG(err_msg.len() > 0, {"\n", err_msg, "\n"})\ 13 | end 14 | 15 | // Automation macro to hide boilerplate of checking JSON file to be successfuly parsed. 16 | // If `GOLDEN_VAL` is provided, then parsed value is compared with the golden one. 17 | // Otherwise, only parsing status (err/ok) is evaluated. 18 | `define EXPECT_OK_LOAD_FILE(PATH, GOLDEN_VAL=null)\ 19 | begin \ 20 | string err_msg = test_utils_pkg::expect_ok_load_file(PATH, GOLDEN_VAL);\ 21 | `FAIL_IF_LOG(err_msg.len() > 0, {"\n", err_msg, "\n"})\ 22 | end 23 | 24 | // Automation macro to hide boilerplate of checking JSON string to be parsed with error. 25 | // If `GOLDEN_ERR` is provided, then parsed error is compared with the golden error. 26 | // Otherwise, only parsing status (err/ok) is evaluated. 27 | `define EXPECT_ERR_LOAD_STR(RAW_STR, GOLDEN_ERR=null)\ 28 | begin \ 29 | string err_msg = test_utils_pkg::expect_err_load_str(RAW_STR, GOLDEN_ERR);\ 30 | `FAIL_IF_LOG(err_msg.len() > 0, {"\n", err_msg, "\n"})\ 31 | end 32 | 33 | // Automation macro to hide boilerplate of checking JSON file to be parsed with error. 34 | // If `GOLDEN_ERR` is provided, then parsed error is compared with the golden error. 35 | // Otherwise, only parsing status (err/ok) is evaluated. 36 | `define EXPECT_ERR_LOAD_FILE(PATH, GOLDEN_ERR=null)\ 37 | begin \ 38 | string err_msg = test_utils_pkg::expect_err_load_file(PATH, GOLDEN_ERR);\ 39 | `FAIL_IF_LOG(err_msg.len() > 0, {"\n", err_msg, "\n"})\ 40 | end 41 | 42 | // Automation macro to hide boilerplate of checking JSON string to be successfuly dumped. 43 | // Dumped value is compared with the golden one. 44 | `define EXPECT_OK_DUMP_STR(OBJ, GOLDEN_VAL, INDENT=0)\ 45 | begin\ 46 | json_result#(string) res = json_encoder::dump_string(OBJ, .indent_spaces(INDENT));\ 47 | `FAIL_IF(res.is_err())\ 48 | `FAIL_UNLESS_STR_EQUAL(res.unwrap(), GOLDEN_VAL)\ 49 | end 50 | 51 | `define TEST_UTILS_MACROS_SVH 52 | `endif // !TEST_UTILS_MACROS_SVH -------------------------------------------------------------------------------- /tests/json_enum_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_enum` 5 | module json_enum_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_enum_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | typedef json_enum#(dummy_e) json_dummy_enum; 14 | 15 | function void build(); 16 | svunit_ut = new(name); 17 | endfunction 18 | 19 | task setup(); 20 | svunit_ut.setup(); 21 | endtask 22 | 23 | task teardown(); 24 | svunit_ut.teardown(); 25 | endtask 26 | 27 | `SVUNIT_TESTS_BEGIN 28 | 29 | `SVTEST(create_new_test) begin 30 | json_dummy_enum jenum; 31 | jenum = new(DUMMY_BAR); 32 | `FAIL_UNLESS(jenum.get_enum() == DUMMY_BAR) 33 | // it also behaves like json_string 34 | `FAIL_UNLESS_STR_EQUAL(jenum.get(), "DUMMY_BAR") 35 | end `SVTEST_END 36 | 37 | 38 | `SVTEST(create_from_test) begin 39 | json_dummy_enum jenum = json_dummy_enum::from(DUMMY_BAR); 40 | `FAIL_UNLESS(jenum.get_enum() == DUMMY_BAR) 41 | // it also behaves like json_string 42 | `FAIL_UNLESS_STR_EQUAL(jenum.get(), "DUMMY_BAR") 43 | end `SVTEST_END 44 | 45 | 46 | `SVTEST(create_try_from_test) begin 47 | json_dummy_enum jenum; 48 | json_result#(json_dummy_enum) jres; 49 | 50 | jres = json_dummy_enum::try_from("DUMMY_BAR"); 51 | `FAIL_IF(jres.is_err) 52 | `FAIL_UNLESS(jres.unwrap().get_enum() == DUMMY_BAR) 53 | 54 | jres = json_dummy_enum::try_from("DUMMY_FOO"); 55 | `FAIL_IF(jres.is_err) 56 | `FAIL_UNLESS(jres.unwrap().get_enum() == DUMMY_FOO) 57 | 58 | jres = json_dummy_enum::try_from("DUMMY_BAZ"); 59 | `FAIL_IF(jres.is_err) 60 | `FAIL_UNLESS(jres.unwrap().get_enum() == DUMMY_BAZ) 61 | end `SVTEST_END 62 | 63 | 64 | `SVTEST(create_try_from_err_test) begin 65 | json_dummy_enum jenum; 66 | json_result#(json_dummy_enum) jres; 67 | json_error error; 68 | 69 | jres = json_dummy_enum::try_from("DUMMY_EGGS"); 70 | `FAIL_IF(jres.is_ok) 71 | `FAIL_UNLESS(jres.matches_err_eq(json_error::TYPE_CONVERSION, error)) 72 | end `SVTEST_END 73 | 74 | 75 | `SVTEST(clone_enum_test) begin 76 | json_dummy_enum orig = json_dummy_enum::from(DUMMY_FOO); 77 | json_dummy_enum clone; 78 | $cast(clone, orig.clone()); 79 | `FAIL_IF(orig == clone) 80 | `FAIL_UNLESS(orig.get() == clone.get()) 81 | `FAIL_UNLESS(orig.get_enum() == clone.get_enum()) 82 | end `SVTEST_END 83 | 84 | `SVUNIT_TESTS_END 85 | 86 | endmodule : json_enum_unit_test 87 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | :url-json: https://www.json.org 2 | :url-ecma-404: https://ecma-international.org/publications-and-standards/standards/ecma-404 3 | :url-svjson-pages: https://esynr3z.github.io/svjson 4 | :url-svunit: https://github.com/svunit/svunit 5 | :url-json-test-suite: https://github.com/nst/JSONTestSuite 6 | :url-verilator-github: https://github.com/verilator/verilator 7 | :url-antora: https://antora.org 8 | :url-asciidoc: https://asciidoc.org 9 | :url-tests-badge: https://github.com/esynr3z/svjson/actions/workflows/tests.yaml/badge.svg?branch=main 10 | :url-documentation-badge: https://github.com/esynr3z/svjson/actions/workflows/docs.yaml/badge.svg?branch=main 11 | :url-json-pkg: src/json_pkg.sv 12 | 13 | = JSON encoder and decoder in SystemVerilog 14 | 15 | image:{url-tests-badge}[Tests] image:{url-documentation-badge}[Documentation] 16 | 17 | The project introduces {url-json}[JSON] decoder and encoder implementation in pure SystemVerilog without any external dependencies. 18 | It provides SystemVerilog package xref:{url-json-pkg}[`json_pkg`] for convenient integration into any HDL project. 19 | 20 | The package allows decoding and encoding JSON values from or to string (file). 21 | It also provides rich capabilities for values manipulation, traversing ans inspection. 22 | Special interface classes can be used for creation of encoding-friendly user classes. 23 | Additionally, there is an error propagation and reporting system inspired by some ideas of Rust. 24 | 25 | Implementation follows {url-ecma-404}[ECMA standard] almost completely - only several cases of escape values are not supported. 26 | 27 | Code tested intensively in {url-verilator-github}[Verilator 5.24] with {url-svunit}[SVUnit] using {url-json-test-suite}[JSONTestSuite] and custom tests. **Support of other simulators is planned, but not guaranteed at the moment**. 28 | 29 | == Documentation 30 | 31 | Documentation is hosted at {url-svjson-pages}[Github Pages]. 32 | 33 | All documentation is written in {url-asciidoc}[Asciidoc] and transformed into static site via {url-antora}[Antora]. 34 | 35 | == Integration 36 | 37 | * Setup environment variable `SVJSON_ROOT` with a path to `svjson` root on your filesystem 38 | * Add filelist to your simulator using according arguments, e.g. `-f ${SVJSON_ROOT}/src/filelist.f` 39 | * Use classes within package to decode or encode JSON files: `json_pkg::json_decoder::load_file()` or `json_pkg::json_encoder::dump_file()` 40 | 41 | For more details please refer to {url-svjson-pages}[documentation]. 42 | 43 | == Development 44 | 45 | Please refer to developer guide in {url-svjson-pages}[documentation]. 46 | -------------------------------------------------------------------------------- /src/json_result.sv: -------------------------------------------------------------------------------- 1 | // Result class to facilitate error handling. 2 | // Inspired by Result enumeration and a common way to propagate errors in Rust. 3 | // However, error type is hardcoded for `json_error`. 4 | class json_result#(type VAL_T=json_value); 5 | protected VAL_T value; 6 | protected json_error error; 7 | 8 | // Private constructor to force using `ok()` or `err()` 9 | extern local function new(); 10 | 11 | // Is result OK? 12 | extern virtual function bit is_ok(); 13 | 14 | // Is result error? 15 | extern virtual function bit is_err(); 16 | 17 | // Match result with any OK value and return value item and 1 on success 18 | extern virtual function bit matches_ok(output VAL_T value); 19 | 20 | // Match result with any error and return error item and 1 on success 21 | extern virtual function bit matches_err(output json_error error); 22 | 23 | // Match result with specific error and return error item and 1 on success 24 | extern virtual function bit matches_err_eq(input json_error::kind_e kind, output json_error error); 25 | 26 | // Create OK result 27 | static function json_result#(VAL_T) ok(VAL_T value); 28 | // FIXME: extern is not used here, because verialtor does not work well with parametrized return type 29 | json_result#(VAL_T) result = new(); 30 | result.value = value; 31 | return result; 32 | endfunction : ok 33 | 34 | // Create error result 35 | static function json_result#(VAL_T) err(json_error error); 36 | // FIXME: extern is not used here, because verialtor does not work well with parametrized return type 37 | json_result#(VAL_T) result = new(); 38 | result.error = error; 39 | return result; 40 | endfunction : err 41 | 42 | // Create error result 43 | virtual function VAL_T unwrap(); 44 | // FIXME: extern is not used here, because verialtor does not work well with parametrized return type 45 | if (this.is_err()) begin 46 | this.error.throw_fatal(); 47 | end 48 | return this.value; 49 | endfunction : unwrap 50 | endclass : json_result 51 | 52 | 53 | function json_result::new(); 54 | endfunction : new 55 | 56 | 57 | function bit json_result::is_ok(); 58 | return this.error == null; 59 | endfunction : is_ok 60 | 61 | 62 | function bit json_result::is_err(); 63 | return !this.is_ok(); 64 | endfunction : is_err 65 | 66 | 67 | function bit json_result::matches_ok(output VAL_T value); 68 | if (this.is_ok()) begin 69 | value = this.value; 70 | return 1; 71 | end else begin 72 | return 0; 73 | end 74 | endfunction : matches_ok 75 | 76 | 77 | function bit json_result::matches_err(output json_error error); 78 | if (this.is_err()) begin 79 | error = this.error; 80 | return 1; 81 | end else begin 82 | return 0; 83 | end 84 | endfunction : matches_err 85 | 86 | 87 | function bit json_result::matches_err_eq(input json_error::kind_e kind, output json_error error); 88 | if (this.is_err() && (this.error.kind == kind)) begin 89 | error = this.error; 90 | return 1; 91 | end else begin 92 | return 0; 93 | end 94 | endfunction : matches_err_eq 95 | -------------------------------------------------------------------------------- /src/values/json_enum.sv: -------------------------------------------------------------------------------- 1 | // JSON enum. 2 | // This wrapper class represens SV enum value as standard JSON string. 3 | // Purpose of this class is to facilitate using SV enum with JSON decoder/encoder. 4 | class json_enum #(type ENUM_T) extends json_string; 5 | // Internal raw value of enum 6 | protected ENUM_T enum_value; 7 | 8 | // Normal constructor 9 | extern function new(ENUM_T value); 10 | 11 | // Create `json_enum` from enum 12 | static function json_enum#(ENUM_T) from(ENUM_T value); 13 | // FIXME: extern is not used here, because verialtor does not work well with parametrized return type 14 | json_enum#(ENUM_T) obj = new(value); 15 | return obj; 16 | endfunction : from 17 | 18 | // Try to create `json_enum` from string 19 | static function json_result#(json_enum#(ENUM_T)) try_from(string value); 20 | // FIXME: extern is not used here, because verialtor does not work well with parametrized return type 21 | for (ENUM_T e = e.first();; e = e.next()) begin 22 | if (e.name() == value) begin 23 | return json_result#(json_enum#(ENUM_T))::ok(json_enum#(ENUM_T)::from(e)); 24 | end 25 | if (e == e.last()) begin 26 | break; 27 | end 28 | end 29 | 30 | return json_result#(json_enum#(ENUM_T))::err(json_error::create(json_error::TYPE_CONVERSION)); 31 | endfunction : try_from 32 | 33 | // Create a deep copy of an instance 34 | extern virtual function json_value clone(); 35 | 36 | // Compare with another instance 37 | extern virtual function bit compare(json_value value); 38 | 39 | // Get internal string value 40 | extern virtual function string get(); 41 | 42 | // Set internal string value. 43 | // Important: error propagation is not expected here, so if string cannot be converted to valid enum, 44 | // fatal error is thrown. 45 | extern virtual function void set(string value); 46 | 47 | // Get internal enum value 48 | virtual function ENUM_T get_enum(); 49 | // FIXME: extern is not used here, because verialtor does not work well with parametrized return type 50 | return this.enum_value; 51 | endfunction : get_enum 52 | 53 | // Set internal enum value 54 | extern virtual function void set_enum(ENUM_T value); 55 | endclass : json_enum 56 | 57 | 58 | function json_enum::new(ENUM_T value); 59 | super.new(""); 60 | this.enum_value = value; 61 | endfunction : new 62 | 63 | 64 | function json_value json_enum::clone(); 65 | return json_enum#(ENUM_T)::from(get_enum()); 66 | endfunction : clone 67 | 68 | 69 | function bit json_enum::compare(json_value value); 70 | json_enum#(ENUM_T) rhs; 71 | 72 | if (value == null) begin 73 | return 0; 74 | end else if ($cast(rhs, value)) begin 75 | return get_enum() == rhs.get_enum(); 76 | end else begin 77 | return 0; 78 | end 79 | endfunction : compare 80 | 81 | 82 | function string json_enum::get(); 83 | return get_enum().name(); 84 | endfunction : get 85 | 86 | 87 | function void json_enum::set(string value); 88 | set_enum(json_enum#(ENUM_T)::try_from(value).unwrap().get_enum()); 89 | endfunction : set 90 | 91 | 92 | function void json_enum::set_enum(ENUM_T value); 93 | this.enum_value = value; 94 | endfunction : set_enum 95 | -------------------------------------------------------------------------------- /src/values/json_bits.sv: -------------------------------------------------------------------------------- 1 | // JSON bit vector. 2 | // This wrapper class represens SV bit vector value as standard JSON string. 3 | // Purpose of this class is to facilitate using SV bit vectors of arbitrary size 4 | // with JSON decoder/encoder. As a result, any number, that cannot be represented 5 | // as JSON number using longint or real, can be represented as a string. 6 | class json_bits #(type BITS_T=bit) extends json_string; 7 | typedef enum { 8 | RADIX_DEC, // Base 10, no prefix 9 | RADIX_BIN, // Base 2, 0b prefix 10 | RADIX_HEX // Base 16, 0x prefix 11 | } radix_e; 12 | 13 | // Internal raw value of a bit vector 14 | protected BITS_T bits_value; 15 | 16 | // Preferred radix for conversion into string 17 | radix_e preferred_radix; 18 | 19 | // Normal constructor 20 | extern function new(BITS_T value, radix_e preferred_radix=RADIX_DEC); 21 | 22 | // Create `json_bits` from enum 23 | static function json_bits#(BITS_T) from(BITS_T value, radix_e preferred_radix=RADIX_DEC); 24 | // FIXME: extern is not used here, because verilator does not work well with parametrized return type 25 | json_bits#(BITS_T) obj = new(value, preferred_radix); 26 | return obj; 27 | endfunction : from 28 | 29 | // Try to create `json_bits` from string 30 | static function json_result#(json_bits#(BITS_T)) try_from(string value); 31 | // FIXME: extern is not used here, because verilator does not work well with parametrized return type 32 | BITS_T bits_value; 33 | radix_e preferred_radix; 34 | 35 | if ($sscanf(value, "0x%x", bits_value) == 1) begin 36 | preferred_radix = RADIX_HEX; 37 | end else if ($sscanf(value, "0b%b", bits_value) == 1) begin 38 | preferred_radix = RADIX_BIN; 39 | end else if ($sscanf(value, "%d", bits_value) == 1) begin 40 | preferred_radix = RADIX_DEC; 41 | end else begin 42 | return json_result#(json_bits#(BITS_T))::err(json_error::create(json_error::TYPE_CONVERSION)); 43 | end 44 | 45 | return json_result#(json_bits#(BITS_T))::ok(json_bits#(BITS_T)::from(bits_value, preferred_radix)); 46 | endfunction : try_from 47 | 48 | // Create a deep copy of an instance 49 | extern virtual function json_value clone(); 50 | 51 | // Compare with another instance 52 | extern virtual function bit compare(json_value value); 53 | 54 | // Get internal string value 55 | extern virtual function string get(); 56 | 57 | // Set internal string value. 58 | // Important: error propagation is not expected here, so if string cannot be converted to valid bit vector, 59 | // fatal error is thrown. 60 | extern virtual function void set(string value); 61 | 62 | // Get internal bit vector value 63 | virtual function BITS_T get_bits(); 64 | // FIXME: extern is not used here, because verialtor does not work well with parametrized return type 65 | return this.bits_value; 66 | endfunction : get_bits 67 | 68 | // Set internal bit vector value 69 | extern virtual function void set_bits(BITS_T value); 70 | endclass : json_bits 71 | 72 | 73 | function json_bits::new(BITS_T value, radix_e preferred_radix=RADIX_DEC); 74 | super.new(""); 75 | this.bits_value = value; 76 | endfunction : new 77 | 78 | 79 | function json_value json_bits::clone(); 80 | return json_bits#(BITS_T)::from(get_bits(), this.preferred_radix); 81 | endfunction : clone 82 | 83 | 84 | function bit json_bits::compare(json_value value); 85 | json_bits#(BITS_T) rhs; 86 | 87 | if (value == null) begin 88 | return 0; 89 | end else if ($cast(rhs, value)) begin 90 | return get_bits() == rhs.get_bits(); 91 | end else begin 92 | return 0; 93 | end 94 | endfunction : compare 95 | 96 | 97 | function string json_bits::get(); 98 | case (this.preferred_radix) 99 | RADIX_DEC : return $sformatf("%0d", get_bits()); 100 | RADIX_BIN : return $sformatf("0b%0b", get_bits()); 101 | RADIX_HEX : return $sformatf("0x%0x", get_bits()); 102 | endcase 103 | endfunction : get 104 | 105 | 106 | function void json_bits::set(string value); 107 | set_bits(json_bits#(BITS_T)::try_from(value).unwrap().get_bits()); 108 | endfunction : set 109 | 110 | 111 | function void json_bits::set_bits(BITS_T value); 112 | this.bits_value = value; 113 | endfunction : set_bits 114 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize] 9 | 10 | jobs: 11 | test-verilator: 12 | runs-on: ubuntu-latest 13 | 14 | env: 15 | VERILATOR_VERSION: v5.024 16 | VERILATOR_INSTALL: /opt/verilator 17 | 18 | steps: 19 | - name: Cache Verilator 20 | id: cache-verilator 21 | uses: actions/cache@v4 22 | with: 23 | path: ${{ env.VERILATOR_INSTALL }} 24 | key: verilator-${{ env.VERILATOR_VERSION }}-${{ runner.os }} 25 | save-always: true 26 | 27 | - name: Build Verilator 28 | if: steps.cache-verilator.outputs.cache-hit != 'true' 29 | run: | 30 | # Install dependecies 31 | sudo apt-get update 32 | sudo apt-get install -y git help2man perl python3 make autoconf g++ flex bison ccache libfl2 libfl-dev 33 | 34 | # Clone Verilator and checkout version 35 | git clone https://github.com/verilator/verilator.git 36 | cd verilator 37 | git checkout ${{ env.VERILATOR_VERSION }} 38 | 39 | # Build Verilator 40 | autoconf 41 | ./configure --prefix ${{ env.VERILATOR_INSTALL }} 42 | make -j$(nproc) 43 | sudo make install 44 | 45 | - name: Checkout source code 46 | uses: actions/checkout@v4 47 | with: 48 | submodules: "recursive" 49 | 50 | - name: Prepare evinronment 51 | run: | 52 | sudo apt-get install -y g++ flex bison ccache 53 | echo "${{ env.VERILATOR_INSTALL }}/bin" >> $GITHUB_PATH 54 | echo "${{ github.workspace }}/contrib/svunit/bin" >> $GITHUB_PATH 55 | 56 | - name: Lint the package 57 | run: make lint_verilator 58 | 59 | - name: Test the package 60 | run: make -C tests test_verilator 61 | 62 | - name: Test documentation examples 63 | run: make -C docs/modules/ROOT/examples/selftest test_verilator 64 | 65 | - name: Parse test results 66 | uses: mikepenz/action-junit-report@v4 67 | if: always() 68 | with: 69 | report_paths: '**/work_verilator/tests.xml' 70 | require_tests: true 71 | 72 | test-modelsim: 73 | runs-on: ubuntu-latest 74 | 75 | env: 76 | MODELSIM_INSTALLER: ModelSimSetup-20.1.0.711-linux.run 77 | MODELSIM_INSTALLER_URL: https://download.altera.com/akdlm/software/acdsinst/20.1std/711/ib_installers/ModelSimSetup-20.1.0.711-linux.run 78 | MODELSIM_INSTALL: /opt/modelsim 79 | 80 | steps: 81 | - name: Cache Modelsim 82 | id: cache-modelsim 83 | uses: actions/cache@v4 84 | with: 85 | path: ${{ env.MODELSIM_INSTALL }} 86 | key: ${{ env.MODELSIM_INSTALLER }}-${{ runner.os }} 87 | save-always: true 88 | 89 | - name: Install Modelsim 90 | if: steps.cache-modelsim.outputs.cache-hit != 'true' 91 | run: | 92 | sudo dpkg --add-architecture i386 93 | sudo apt update 94 | sudo apt install -y libc6:i386 libxtst6:i386 libncurses5:i386 libxft2:i386 libstdc++6:i386 libc6-dev-i386 lib32z1 libqt5xml5 liblzma-dev 95 | wget ${{ env.MODELSIM_INSTALLER_URL }} 96 | chmod +x ${{ env.MODELSIM_INSTALLER }} 97 | ./${{ env.MODELSIM_INSTALLER }} --mode unattended --accept_eula 1 --installdir ${{ env.MODELSIM_INSTALL }} --unattendedmodeui none 98 | 99 | - name: Checkout source code 100 | uses: actions/checkout@v4 101 | with: 102 | submodules: "recursive" 103 | 104 | - name: Prepare evinronment 105 | run: | 106 | sudo dpkg --add-architecture i386 107 | sudo apt update 108 | sudo apt install -y libc6:i386 libxtst6:i386 libncurses5:i386 libxft2:i386 libstdc++6:i386 libc6-dev-i386 lib32z1 libqt5xml5 liblzma-dev 109 | echo "${{ env.MODELSIM_INSTALL }}/modelsim_ase/bin" >> $GITHUB_PATH 110 | echo "${{ github.workspace }}/contrib/svunit/bin" >> $GITHUB_PATH 111 | 112 | - name: Lint the package 113 | run: make lint_modelsim 114 | 115 | - name: Test the package 116 | run: make -C tests test_modelsim 117 | 118 | - name: Parse test results 119 | uses: mikepenz/action-junit-report@v4 120 | if: always() 121 | with: 122 | report_paths: '**/work_modelsim/tests.xml' 123 | require_tests: true -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/welcome.adoc: -------------------------------------------------------------------------------- 1 | :url-ecma-404: https://ecma-international.org/publications-and-standards/standards/ecma-404 2 | :url-svjson-github: https://github.com/esynr3z/svjson/tree/{page-origin-refname} 3 | :url-verilator-github: https://github.com/verilator/verilator/releases/tag/v5.024 4 | 5 | = Welcome 6 | 7 | Welcome to the homepage of {url-svjson-github}[`svjson`] project! 8 | 9 | The project introduces JSON decoder and encoder in pure SystemVerilog without any external dependencies. 10 | 11 | * JSON decoder can parse values from string or file 12 | * JSON encoder can dump values to string or file 13 | * JSON values (object, array, string, number, bool) are represented with collection of wrapper classes 14 | * JSON encoding capability can be added to user classes with collection of interface classes 15 | * Additional classes for encoding SystemVerilog enums and bit vectors of any width 16 | * Error reporting and propagation system inspired by Rust 17 | 18 | Implementation follows {url-ecma-404}[ECMA standard] with some limitations: 19 | 20 | * `\b` and `\u` escape sequences are not supported for decoder/encoder 21 | * Nesting depth is limited for decoder (1024 by default, limit is controllable) 22 | * No recursion detection for encoder 23 | 24 | == Supported EDA 25 | 26 | The project's code is provided as a SystemVerilog package. Various EDA tools have different level of SV support, so below is a summary table. 27 | 28 | .Supported EDA tools 29 | [cols="3,1,1,1"] 30 | |=== 31 | | Tool | Lint | Test | CI 32 | | {url-verilator-github}[Verilator 5.24] | ✅ | ✅ | ✅ 33 | | Synopsys VCS | ✅ | ✅ | ❌ 34 | | Cadence Xcelium | ✅ | ✅ | ❌ 35 | | Siemens Modelsim | ✅ | ✅ | ✅ 36 | | Siemens Questa | ❌ | ❌ | ❌ 37 | |=== 38 | 39 | * Lint - the package is compilable without warnings using this tool 40 | * Test - the package is tested using this tool via SVUnit 41 | * CI - this tool is used in CI to perform automatic checks 42 | 43 | IMPORTANT: Extending support of other simulators is planned, but Verilator is the main simulator for project as it is the only viable option to organize CI and opensource flow. 44 | 45 | == How to setup and compile the code? 46 | 47 | Please refer to xref:user.adoc#_integration[User Guide: Integration] section. 48 | 49 | == How to parse JSON file or string? 50 | 51 | Please refer to xref:user.adoc#_json_decoder[User Guide: JSON Decoder] section. 52 | 53 | == How to dump JSON file or string? 54 | 55 | Please refer to xref:user.adoc#_json_encoder[User Guide: JSON Encoder] section. 56 | 57 | == Where complete API is described? 58 | 59 | Please refer to xref:classref.adoc[Class Reference Manual]. 60 | 61 | == How to contribute, run tests, etc.? 62 | 63 | Please refer to xref:developer.adoc[Developer Guide]. 64 | 65 | == What has changed for version x.y.z? 66 | 67 | Please refer to xref:changelog.adoc[Changelog]. 68 | 69 | == Are there any alternatives? 70 | 71 | :url-ieee-compatible-uvm-2018: https://www.accellera.org/images/resources/videos/IEEE_Compatible_UVM_2018.pdf 72 | :url-custom-uvm-report-servers: http://www.fivecomputers.com/static/images/papers/snug_custom_uvm_report_servers.pdf 73 | :url-verilab-uvm-structured-logs: https://bitbucket.org/verilab/uvm_structured_logs 74 | :url-uvm-tutorial-for-candy-lovers-defining-do_print: https://cluelogic.com/2016/05/uvm-tutorial-for-candy-lovers-defining-do_print 75 | :url-milestone12-json: https://github.com/milestone12/JSON.sv 76 | :url-quinnwerks-jsonpacketparser: https://github.com/quinnwerks/JSONPacketParser 77 | :url-jsmn: https://zserge.com/jsmn/ 78 | :url-zhouchuanrui-jsoninsv: https://github.com/zhouchuanrui/JSONinSV 79 | :url-json-for-vhdl: https://github.com/Paebbels/JSON-for-VHDL 80 | :url-json-test-suite: https://github.com/nst/JSONTestSuite 81 | :url-fusesoc: https://github.com/olofk/fusesoc 82 | 83 | [cols="3s,1,10"] 84 | |=== 85 | | DVCon US 2018, IEEE-Compatible UVM Reference Implementation and Verification Components: Objects and Policies, Mark Peryer. 86 | | {url-ieee-compatible-uvm-2018}[Slides] 87 | | Slides contain references to a JSON `uvm_printer` with some code. 88 | 89 | | SNUG 2013, Applications of Custom UVM Report Servers, Gordon Mc Gregor 90 | | {url-custom-uvm-report-servers}[Artice], {url-verilab-uvm-structured-logs}[sources] 91 | | Article mentions JSON implementation of UVM server, however, link to the code doesn't work. 92 | 93 | | UVM Tutorial for Candy Lovers – 33. Defining do_print 94 | | {url-uvm-tutorial-for-candy-lovers-defining-do_print}[Link] 95 | | Famous UVM tutorial shows how to implement simple JSON `uvm_printer` with code. 96 | 97 | | JSON.sv 98 | | {url-milestone12-json}[Github] 99 | | Implementation of JSON encoder/decoder in SystemVerilog. Integrated into {url-fusesoc}[FuseSoc] ecosystem. 100 | It has only few tests and almost no documentation. 101 | 102 | | JSONPacketParser 103 | | {url-quinnwerks-jsonpacketparser}[Github] 104 | | Project uses {url-jsmn}[the JSMN library] to parse a JSON file into a C data structure. This data structure is then passed to a SystemVerilog testbench via DPI. 105 | 106 | | JSONinSV 107 | | {url-zhouchuanrui-jsoninsv}[Github] 108 | | JSON encoder and decoder in SystemVerilog. It is well tested (own framework is introduced). Project comes with a nicely structured README (on Chinese, though). 109 | 110 | | JSON-for-VHDL 111 | | {url-json-for-vhdl}[Github] 112 | | JSON decoder in VHDL. Tested against well-known {url-json-test-suite}[JSON Test Suite]. 113 | |=== 114 | -------------------------------------------------------------------------------- /tests/json_int_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_int` 5 | module json_int_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_int_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | `SVUNIT_TESTS_BEGIN 26 | 27 | // Create json_int using constructor 28 | `SVTEST(create_new_test) begin 29 | json_int jint; 30 | jint = new(42); 31 | `FAIL_UNLESS(jint.get() == 42) 32 | end `SVTEST_END 33 | 34 | 35 | // Create json_int using static method 36 | `SVTEST(create_static_test) begin 37 | json_int jint; 38 | jint = json_int::from(42); 39 | `FAIL_UNLESS(jint.get() == 42) 40 | end `SVTEST_END 41 | 42 | 43 | // Clone json_int instance 44 | `SVTEST(clone_test) begin 45 | json_int orig = json_int::from(42); 46 | json_int clone = orig.clone().into_int(); 47 | `FAIL_IF(orig == clone) 48 | `FAIL_UNLESS(orig.get() == clone.get()) 49 | end `SVTEST_END 50 | 51 | 52 | // Compare json_int instances 53 | `SVTEST(compare_test) begin 54 | json_int jint_a = json_int::from(42); 55 | json_int jint_b = json_int::from(42); 56 | // OK 57 | `FAIL_UNLESS(jint_a.compare(jint_a)) 58 | `FAIL_UNLESS(jint_a.compare(jint_b)) 59 | jint_a.set(-123213213); 60 | jint_b.set(-123213213); 61 | `FAIL_UNLESS(jint_a.compare(jint_b)) 62 | // Fail 63 | jint_a.set(1232); 64 | jint_b.set(12333); 65 | `FAIL_IF(jint_a.compare(jint_b)) 66 | jint_a.set(-8232); 67 | jint_b.set(123); 68 | `FAIL_IF(jint_a.compare(jint_b)) 69 | end `SVTEST_END 70 | 71 | 72 | // Compare jsom_int with other JSON values 73 | `SVTEST(compare_with_others_test) begin 74 | json_string jstring = json_string::from("1"); 75 | json_int jint = json_int::from(1); 76 | json_real jreal = json_real::from(1.0); 77 | json_bool jbool = json_bool::from(1); 78 | json_array jarray = json_array::from('{json_int::from(1)}); 79 | json_object jobject = json_object::from('{"int": json_int::from(1)}); 80 | 81 | `FAIL_IF(jint.compare(jstring)) 82 | `FAIL_IF(jint.compare(jreal)) 83 | `FAIL_IF(jint.compare(jbool)) 84 | `FAIL_IF(jint.compare(jarray)) 85 | `FAIL_IF(jint.compare(jobject)) 86 | `FAIL_IF(jint.compare(null)) 87 | end `SVTEST_END 88 | 89 | 90 | // Access internal value of json_int 91 | `SVTEST(getter_setter_test) begin 92 | json_int jint = json_int::from(42); 93 | `FAIL_UNLESS(jint.get() == 42) 94 | jint.set(-12345); 95 | `FAIL_UNLESS(jint.get() == -12345) 96 | end `SVTEST_END 97 | 98 | 99 | // Test json_int_encodable interface 100 | `SVTEST(encodable_test) begin 101 | json_int jint = json_int::from(42); 102 | // Should be the same as get - returns internal value 103 | `FAIL_UNLESS(jint.to_json_encodable() == jint.get()) 104 | end `SVTEST_END 105 | 106 | 107 | // Test is_* methods 108 | `SVTEST(is_something_test) begin 109 | json_int jint = json_int::from(42); 110 | json_value jvalue = jint; 111 | 112 | `FAIL_IF(jvalue.is_object()) 113 | `FAIL_IF(jvalue.is_array()) 114 | `FAIL_IF(jvalue.is_string()) 115 | `FAIL_UNLESS(jvalue.is_int()) 116 | `FAIL_IF(jvalue.is_real()) 117 | `FAIL_IF(jvalue.is_bool()) 118 | end `SVTEST_END 119 | 120 | 121 | // Test match_* methods 122 | `SVTEST(matches_something_test) begin 123 | json_object jobject; 124 | json_array jarray; 125 | json_string jstring; 126 | json_int jint; 127 | json_real jreal; 128 | json_bool jbool; 129 | 130 | json_int orig_jint = json_int::from(42); 131 | json_value jvalue = orig_jint; 132 | 133 | `FAIL_IF(jvalue.matches_object(jobject)) 134 | `FAIL_IF(jvalue.matches_array(jarray)) 135 | `FAIL_IF(jvalue.matches_string(jstring)) 136 | `FAIL_UNLESS(jvalue.matches_int(jint)) 137 | `FAIL_IF(jvalue.matches_real(jreal)) 138 | `FAIL_IF(jvalue.matches_bool(jbool)) 139 | 140 | `FAIL_UNLESS(jint == orig_jint) 141 | end `SVTEST_END 142 | 143 | 144 | // Test try_into_* methods 145 | `SVTEST(try_into_something_test) begin 146 | json_result#(json_object) res_jobject; 147 | json_result#(json_array) res_jarray; 148 | json_result#(json_string) res_jstring; 149 | json_result#(json_int) res_jint; 150 | json_result#(json_real) res_jreal; 151 | json_result#(json_bool) res_jbool; 152 | 153 | json_int orig_jint = json_int::from(42); 154 | json_value jvalue = orig_jint; 155 | 156 | res_jobject = jvalue.try_into_object(); 157 | res_jarray = jvalue.try_into_array(); 158 | res_jstring = jvalue.try_into_string(); 159 | res_jint = jvalue.try_into_int(); 160 | res_jreal = jvalue.try_into_real(); 161 | res_jbool = jvalue.try_into_bool(); 162 | 163 | `FAIL_IF(res_jobject.is_ok()) 164 | `FAIL_IF(res_jarray.is_ok()) 165 | `FAIL_IF(res_jstring.is_ok()) 166 | `FAIL_UNLESS(res_jint.is_ok()) 167 | `FAIL_IF(res_jreal.is_ok()) 168 | `FAIL_IF(res_jbool.is_ok()) 169 | 170 | `FAIL_UNLESS(res_jint.unwrap() == orig_jint) 171 | end `SVTEST_END 172 | 173 | 174 | // Test into_* method 175 | `SVTEST(into_something_test) begin 176 | json_int res_jint; 177 | json_int orig_jint = json_int::from(1); 178 | json_value jvalue = orig_jint; 179 | 180 | res_jint = jvalue.into_int(); 181 | end `SVTEST_END 182 | 183 | `SVUNIT_TESTS_END 184 | 185 | endmodule : json_int_unit_test 186 | -------------------------------------------------------------------------------- /tests/json_bool_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_bool` 5 | module json_bool_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_bool_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | `SVUNIT_TESTS_BEGIN 26 | 27 | // Create json_bool using constructor 28 | `SVTEST(create_new_test) begin 29 | json_bool jbool; 30 | jbool = new(1); 31 | `FAIL_UNLESS(jbool.get() == 1) 32 | end `SVTEST_END 33 | 34 | 35 | // Create json_bool using static method 36 | `SVTEST(create_static_test) begin 37 | json_bool jbool; 38 | jbool = json_bool::from(0); 39 | `FAIL_UNLESS(jbool.get() == 0) 40 | end `SVTEST_END 41 | 42 | 43 | // Clone json_bool instance 44 | `SVTEST(clone_test) begin 45 | json_bool orig = json_bool::from(1); 46 | json_bool clone = orig.clone().into_bool(); 47 | `FAIL_IF(orig == clone) 48 | `FAIL_UNLESS(orig.get() == clone.get()) 49 | end `SVTEST_END 50 | 51 | 52 | // Compare json_bool instances 53 | `SVTEST(compare_test) begin 54 | json_bool jbool_a = json_bool::from(1); 55 | json_bool jbool_b = json_bool::from(1); 56 | // OK 57 | `FAIL_UNLESS(jbool_a.compare(jbool_a)) 58 | `FAIL_UNLESS(jbool_a.compare(jbool_b)) 59 | jbool_a.set(0); 60 | jbool_b.set(0); 61 | `FAIL_UNLESS(jbool_a.compare(jbool_b)) 62 | // Fail 63 | jbool_a.set(1); 64 | jbool_b.set(0); 65 | `FAIL_IF(jbool_a.compare(jbool_b)) 66 | jbool_a.set(0); 67 | jbool_b.set(1); 68 | `FAIL_IF(jbool_a.compare(jbool_b)) 69 | end `SVTEST_END 70 | 71 | 72 | // Compare jsom_int with other JSON values 73 | `SVTEST(compare_with_others_test) begin 74 | json_string jstring = json_string::from("1"); 75 | json_int jint = json_int::from(1); 76 | json_real jreal = json_real::from(1.0); 77 | json_bool jbool = json_bool::from(1); 78 | json_array jarray = json_array::from('{json_int::from(1)}); 79 | json_object jobject = json_object::from('{"int": json_int::from(1)}); 80 | 81 | `FAIL_IF(jbool.compare(jstring)) 82 | `FAIL_IF(jbool.compare(jreal)) 83 | `FAIL_IF(jbool.compare(jint)) 84 | `FAIL_IF(jbool.compare(jarray)) 85 | `FAIL_IF(jbool.compare(jobject)) 86 | `FAIL_IF(jbool.compare(null)) 87 | end `SVTEST_END 88 | 89 | 90 | // Access internal value of json_bool 91 | `SVTEST(getter_setter_test) begin 92 | json_bool jbool = json_bool::from(1); 93 | `FAIL_UNLESS(jbool.get() == 1) 94 | jbool.set(0); 95 | `FAIL_UNLESS(jbool.get() == 0) 96 | end `SVTEST_END 97 | 98 | 99 | // Test json_bool_encodable interface 100 | `SVTEST(encodable_test) begin 101 | json_bool jbool = json_bool::from(1); 102 | // Should be the same as get - returns internal value 103 | `FAIL_UNLESS(jbool.to_json_encodable() == jbool.get()) 104 | end `SVTEST_END 105 | 106 | 107 | // Test is_* methods 108 | `SVTEST(is_something_test) begin 109 | json_bool jbool = json_bool::from(1); 110 | json_value jvalue = jbool; 111 | 112 | `FAIL_IF(jvalue.is_object()) 113 | `FAIL_IF(jvalue.is_array()) 114 | `FAIL_IF(jvalue.is_string()) 115 | `FAIL_IF(jvalue.is_int()) 116 | `FAIL_IF(jvalue.is_real()) 117 | `FAIL_UNLESS(jvalue.is_bool()) 118 | end `SVTEST_END 119 | 120 | 121 | // Test match_* methods 122 | `SVTEST(matches_something_test) begin 123 | json_object jobject; 124 | json_array jarray; 125 | json_string jstring; 126 | json_int jint; 127 | json_real jreal; 128 | json_bool jbool; 129 | 130 | json_bool orig_jbool = json_bool::from(0); 131 | json_value jvalue = orig_jbool; 132 | 133 | `FAIL_IF(jvalue.matches_object(jobject)) 134 | `FAIL_IF(jvalue.matches_array(jarray)) 135 | `FAIL_IF(jvalue.matches_string(jstring)) 136 | `FAIL_IF(jvalue.matches_int(jint)) 137 | `FAIL_IF(jvalue.matches_real(jreal)) 138 | `FAIL_UNLESS(jvalue.matches_bool(jbool)) 139 | 140 | `FAIL_UNLESS(jbool == orig_jbool) 141 | end `SVTEST_END 142 | 143 | 144 | // Test try_into_* methods 145 | `SVTEST(try_into_something_test) begin 146 | json_result#(json_object) res_jobject; 147 | json_result#(json_array) res_jarray; 148 | json_result#(json_string) res_jstring; 149 | json_result#(json_int) res_jint; 150 | json_result#(json_real) res_jreal; 151 | json_result#(json_bool) res_jbool; 152 | 153 | json_bool orig_jbool = json_bool::from(1); 154 | json_value jvalue = orig_jbool; 155 | 156 | res_jobject = jvalue.try_into_object(); 157 | res_jarray = jvalue.try_into_array(); 158 | res_jstring = jvalue.try_into_string(); 159 | res_jint = jvalue.try_into_int(); 160 | res_jreal = jvalue.try_into_real(); 161 | res_jbool = jvalue.try_into_bool(); 162 | 163 | `FAIL_IF(res_jobject.is_ok()) 164 | `FAIL_IF(res_jarray.is_ok()) 165 | `FAIL_IF(res_jstring.is_ok()) 166 | `FAIL_IF(res_jint.is_ok()) 167 | `FAIL_IF(res_jreal.is_ok()) 168 | `FAIL_UNLESS(res_jbool.is_ok()) 169 | 170 | `FAIL_UNLESS(res_jbool.unwrap() == orig_jbool) 171 | end `SVTEST_END 172 | 173 | 174 | // Test into_* method 175 | `SVTEST(into_something_test) begin 176 | json_bool res_jbool; 177 | json_bool orig_jbool = json_bool::from(1); 178 | json_value jvalue = orig_jbool; 179 | 180 | res_jbool = jvalue.into_bool(); 181 | end `SVTEST_END 182 | 183 | `SVUNIT_TESTS_END 184 | 185 | endmodule : json_bool_unit_test 186 | -------------------------------------------------------------------------------- /src/values/json_array.sv: -------------------------------------------------------------------------------- 1 | // JSON array. 2 | // This wrapper class represens standard JSON array value using SV queue. 3 | // The class basically wraps standard SV queue methods with some additional methods required to operate as JSON value. 4 | // No additional checks are implemented for "out-of-range" accesses and similar, 5 | // so you can expect that this class will operate according to behavior of an original underlying SV queue. 6 | class json_array extends json_value implements json_array_encodable; 7 | typedef json_value values_t[$]; 8 | 9 | protected values_t values; 10 | 11 | // Normal constructor 12 | extern function new(values_t values); 13 | 14 | // Create `json_array` from queue 15 | extern static function json_array from(values_t values); 16 | 17 | // Create a deep copy of an instance 18 | extern virtual function json_value clone(); 19 | 20 | // Compare with another instance 21 | extern virtual function bit compare(json_value value); 22 | 23 | // Get a value at the provided index 24 | extern virtual function json_value get(int index); 25 | 26 | // Set the given value for the provided index 27 | extern virtual function void set(int index, json_value value); 28 | 29 | // Get a size of the array (number of items stored) 30 | extern virtual function int size(); 31 | 32 | // Insert the given value at the provided index 33 | extern virtual function void insert(int index, json_value value); 34 | 35 | // Remove a value from the provided index 36 | extern virtual function void delete(int index); 37 | 38 | // Remove all values 39 | extern virtual function void flush(); 40 | 41 | // Insert the given value at the back of the array 42 | extern virtual function void push_back(json_value value); 43 | 44 | // Insert the given value at the front of the array 45 | extern virtual function void push_front(json_value value); 46 | 47 | // Remove and return the last element in the array 48 | extern virtual function json_value pop_back(); 49 | 50 | // Remove and return the first element in the array 51 | extern virtual function json_value pop_front(); 52 | 53 | // Get all internal values as SV queue 54 | extern virtual function values_t get_values(); 55 | 56 | // Get value encodable as JSON array (for interface json_array_encodable) 57 | extern virtual function json_array_encodable::values_t to_json_encodable(); 58 | endclass : json_array 59 | 60 | 61 | function json_array::new(values_t values); 62 | foreach (values[i]) begin 63 | this.values.push_back(values[i]); 64 | end 65 | endfunction : new 66 | 67 | 68 | function json_array json_array::from(values_t values); 69 | json_array obj = new(values); 70 | return obj; 71 | endfunction : from 72 | 73 | 74 | function json_value json_array::clone(); 75 | json_value new_values[$]; 76 | 77 | for(int i = 0; i < this.size(); i++) begin 78 | if (this.get(i) == null) begin 79 | new_values.push_back(null); 80 | end else begin 81 | new_values.push_back(this.get(i).clone()); 82 | end 83 | end 84 | 85 | return json_array::from(new_values); 86 | endfunction : clone 87 | 88 | 89 | function bit json_array::compare(json_value value); 90 | json_result#(json_array) casted; 91 | json_error err; 92 | json_array rhs; 93 | 94 | if (value == null) begin 95 | return 0; 96 | end 97 | 98 | casted = value.try_into_array(); 99 | case (1) 100 | casted.matches_err(err): return 0; 101 | casted.matches_ok(rhs): begin 102 | if (rhs.size() != this.size()) begin 103 | return 0; 104 | end 105 | 106 | for(int i = 0; i < this.size(); i++) begin 107 | if ((this.get(i) != null) && (rhs.get(i) != null)) begin 108 | if (!this.get(i).compare(rhs.get(i))) begin 109 | return 0; 110 | end 111 | end else if ((this.get(i) == null) && (rhs.get(i) == null)) begin 112 | continue; 113 | end else begin 114 | return 0; 115 | end 116 | end 117 | return 1; 118 | end 119 | endcase 120 | endfunction : compare 121 | 122 | 123 | function json_value json_array::get(int index); 124 | return this.values[index]; 125 | endfunction : get 126 | 127 | 128 | function void json_array::set(int index, json_value value); 129 | this.values[index] = value; 130 | endfunction : set 131 | 132 | 133 | function int json_array::size(); 134 | return this.values.size(); 135 | endfunction : size 136 | 137 | 138 | function void json_array::insert(int index, json_value value); 139 | this.values.insert(index, value); 140 | endfunction : insert 141 | 142 | 143 | function void json_array::delete(int index); 144 | this.values.delete(index); 145 | endfunction : delete 146 | 147 | 148 | function void json_array::flush(); 149 | this.values.delete(); 150 | endfunction : flush 151 | 152 | 153 | function void json_array::push_back(json_value value); 154 | this.values.push_back(value); 155 | endfunction : push_back 156 | 157 | 158 | function void json_array::push_front(json_value value); 159 | this.values.push_front(value); 160 | endfunction : push_front 161 | 162 | 163 | function json_value json_array::pop_back(); 164 | return this.values.pop_back(); 165 | endfunction : pop_back 166 | 167 | 168 | function json_value json_array::pop_front(); 169 | return this.values.pop_front(); 170 | endfunction : pop_front 171 | 172 | 173 | function json_array::values_t json_array::get_values(); 174 | return this.values; 175 | endfunction : get_values 176 | 177 | 178 | function json_array_encodable::values_t json_array::to_json_encodable(); 179 | json_array_encodable::values_t values; 180 | 181 | for(int i = 0; i < this.size(); i++) begin 182 | values.push_back(this.get(i)); 183 | end 184 | 185 | return values; 186 | endfunction : to_json_encodable 187 | -------------------------------------------------------------------------------- /tests/json_real_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_real` 5 | module json_real_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_real_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | `SVUNIT_TESTS_BEGIN 26 | 27 | // Create json_real using constructor 28 | `SVTEST(create_new_test) begin 29 | json_real jreal; 30 | jreal = new(1.0); 31 | `FAIL_UNLESS(jreal.get() == 1.0) 32 | end `SVTEST_END 33 | 34 | 35 | // Create json_real using static method 36 | `SVTEST(create_static_test) begin 37 | json_real jreal; 38 | jreal = json_real::from(-3.14); 39 | `FAIL_UNLESS(jreal.get() == -3.14) 40 | end `SVTEST_END 41 | 42 | 43 | // Clone json_real instance 44 | `SVTEST(clone_test) begin 45 | json_real orig = json_real::from(0.234); 46 | json_real clone = orig.clone().into_real(); 47 | `FAIL_IF(orig == clone) 48 | `FAIL_UNLESS(orig.get() == clone.get()) 49 | end `SVTEST_END 50 | 51 | 52 | // Compare json_real instances 53 | `SVTEST(compare_test) begin 54 | json_real jreal_a = json_real::from(1.0); 55 | json_real jreal_b = json_real::from(1.0); 56 | // OK 57 | `FAIL_UNLESS(jreal_a.compare(jreal_a)) 58 | `FAIL_UNLESS(jreal_a.compare(jreal_b)) 59 | jreal_a.set(-123213213.987654); 60 | jreal_b.set(-123213213.987654); 61 | `FAIL_UNLESS(jreal_a.compare(jreal_b)) 62 | // Fail 63 | jreal_a.set(1.2); 64 | jreal_b.set(1.3); 65 | `FAIL_IF(jreal_a.compare(jreal_b)) 66 | jreal_a.set(-10.6); 67 | jreal_b.set(10.6); 68 | `FAIL_IF(jreal_a.compare(jreal_b)) 69 | end `SVTEST_END 70 | 71 | 72 | // Compare jsom_real with other JSON values 73 | `SVTEST(compare_with_others_test) begin 74 | json_string jstring = json_string::from("1"); 75 | json_int jint = json_int::from(1); 76 | json_real jreal = json_real::from(1.0); 77 | json_bool jbool = json_bool::from(1); 78 | json_array jarray = json_array::from('{json_int::from(1)}); 79 | json_object jobject = json_object::from('{"int": json_int::from(1)}); 80 | 81 | `FAIL_IF(jreal.compare(jstring)) 82 | `FAIL_IF(jreal.compare(jint)) 83 | `FAIL_IF(jreal.compare(jbool)) 84 | `FAIL_IF(jreal.compare(jarray)) 85 | `FAIL_IF(jreal.compare(jobject)) 86 | `FAIL_IF(jreal.compare(null)) 87 | end `SVTEST_END 88 | 89 | 90 | // Access internal value of json_real 91 | `SVTEST(getter_setter_test) begin 92 | json_real jreal = json_real::from(42.2); 93 | `FAIL_UNLESS(jreal.get() == 42.2) 94 | jreal.set(-36.6); 95 | `FAIL_UNLESS(jreal.get() == -36.6) 96 | end `SVTEST_END 97 | 98 | 99 | // Test json_real_encodable interface 100 | `SVTEST(encodable_test) begin 101 | json_real jreal = json_real::from(0.0002); 102 | // Should be the same as get - returns internal value 103 | `FAIL_UNLESS(jreal.to_json_encodable() == jreal.get()) 104 | end `SVTEST_END 105 | 106 | 107 | // Test is_* methods 108 | `SVTEST(is_something_test) begin 109 | json_real jreal = json_real::from(42.0); 110 | json_value jvalue = jreal; 111 | 112 | `FAIL_IF(jvalue.is_object()) 113 | `FAIL_IF(jvalue.is_array()) 114 | `FAIL_IF(jvalue.is_string()) 115 | `FAIL_IF(jvalue.is_int()) 116 | `FAIL_UNLESS(jvalue.is_real()) 117 | `FAIL_IF(jvalue.is_bool()) 118 | end `SVTEST_END 119 | 120 | 121 | // Test match_* methods 122 | `SVTEST(matches_something_test) begin 123 | json_object jobject; 124 | json_array jarray; 125 | json_string jstring; 126 | json_int jint; 127 | json_real jreal; 128 | json_bool jbool; 129 | 130 | json_real orig_jreal = json_real::from(2.71); 131 | json_value jvalue = orig_jreal; 132 | 133 | `FAIL_IF(jvalue.matches_object(jobject)) 134 | `FAIL_IF(jvalue.matches_array(jarray)) 135 | `FAIL_IF(jvalue.matches_string(jstring)) 136 | `FAIL_IF(jvalue.matches_int(jint)) 137 | `FAIL_UNLESS(jvalue.matches_real(jreal)) 138 | `FAIL_IF(jvalue.matches_bool(jbool)) 139 | 140 | `FAIL_UNLESS(jreal == orig_jreal) 141 | end `SVTEST_END 142 | 143 | 144 | // Test try_into_* methods 145 | `SVTEST(try_into_something_test) begin 146 | json_result#(json_object) res_jobject; 147 | json_result#(json_array) res_jarray; 148 | json_result#(json_string) res_jstring; 149 | json_result#(json_int) res_jint; 150 | json_result#(json_real) res_jreal; 151 | json_result#(json_bool) res_jbool; 152 | 153 | json_real orig_jreal = json_real::from(-3e5); 154 | json_value jvalue = orig_jreal; 155 | 156 | res_jobject = jvalue.try_into_object(); 157 | res_jarray = jvalue.try_into_array(); 158 | res_jstring = jvalue.try_into_string(); 159 | res_jint = jvalue.try_into_int(); 160 | res_jreal = jvalue.try_into_real(); 161 | res_jbool = jvalue.try_into_bool(); 162 | 163 | `FAIL_IF(res_jobject.is_ok()) 164 | `FAIL_IF(res_jarray.is_ok()) 165 | `FAIL_IF(res_jstring.is_ok()) 166 | `FAIL_IF(res_jint.is_ok()) 167 | `FAIL_UNLESS(res_jreal.is_ok()) 168 | `FAIL_IF(res_jbool.is_ok()) 169 | 170 | `FAIL_UNLESS(res_jreal.unwrap() == orig_jreal) 171 | end `SVTEST_END 172 | 173 | 174 | // Test into_* method 175 | `SVTEST(into_something_test) begin 176 | json_real res_jreal; 177 | json_real orig_jreal = json_real::from(1.0); 178 | json_value jvalue = orig_jreal; 179 | 180 | res_jreal = jvalue.into_real(); 181 | end `SVTEST_END 182 | 183 | `SVUNIT_TESTS_END 184 | 185 | endmodule : json_real_unit_test 186 | -------------------------------------------------------------------------------- /tests/test_utils_pkg.sv: -------------------------------------------------------------------------------- 1 | package test_utils_pkg; 2 | import json_pkg::*; 3 | 4 | // Check that JSON string parsing finishes with OK status. 5 | // It also compares parsed value with the golden one, if the latest is provided. 6 | // Returns error message if something is wrong. 7 | function automatic string expect_ok_load_str(string raw, json_value golden); 8 | json_error err; 9 | json_value val; 10 | json_result parsed = json_decoder::load_string(raw); 11 | 12 | case (1) 13 | parsed.matches_err(err): begin 14 | return err.to_string(); 15 | end 16 | parsed.matches_ok(val): begin 17 | if ((val == null) && (golden == null)) begin 18 | return ""; 19 | end else if (val.compare(golden) || (golden == null)) begin 20 | return ""; 21 | end else begin 22 | return "Loaded JSON value do not match the golden one!"; 23 | end 24 | end 25 | endcase 26 | endfunction : expect_ok_load_str 27 | 28 | 29 | // Check that JSON file parsing finishes with OK status. 30 | // It also compares parsed value with the golden one, if the latest is provided. 31 | // Returns error message if something is wrong. 32 | function automatic string expect_ok_load_file(string path, json_value golden); 33 | json_error err; 34 | json_value val; 35 | json_result parsed = json_decoder::load_file(path); 36 | 37 | case (1) 38 | parsed.matches_err(err): begin 39 | return err.to_string(); 40 | end 41 | parsed.matches_ok(val): begin 42 | if ((val == null) && (golden == null)) begin 43 | return ""; 44 | end else if (val.compare(golden) || (golden == null)) begin 45 | return ""; 46 | end else begin 47 | return "Loaded JSON value do not match the golden one!"; 48 | end 49 | end 50 | endcase 51 | endfunction : expect_ok_load_file 52 | 53 | 54 | // Check that JSON string parsing finishes with error status. 55 | // It also compares error with the golden one, if the latest is provided. 56 | // Returns error message if something is not expected. 57 | function automatic string expect_err_load_str(string raw, json_error golden); 58 | json_error err; 59 | json_value val; 60 | json_result parsed = json_decoder::load_string(raw); 61 | 62 | case (1) 63 | parsed.matches_err(err): begin 64 | if (golden != null) begin 65 | if (err.kind != golden.kind) begin 66 | return $sformatf( 67 | "Expect %s error but got %s intead! Captured error:\n%s", 68 | golden.kind.name(), err.kind.name(), err.to_string() 69 | ); 70 | end 71 | 72 | if ((golden.json_pos >= 0) && (err.json_pos != golden.json_pos)) begin 73 | return $sformatf( 74 | "Expect to get error for %0d symbol but got for %0d intead! Captured error:\n%s", 75 | golden.json_pos, err.json_pos, err.to_string() 76 | ); 77 | end 78 | end 79 | return ""; 80 | end 81 | 82 | parsed.matches_ok(val): begin 83 | return "JSON parsed succesfully, but error was expected!"; 84 | end 85 | endcase 86 | endfunction : expect_err_load_str 87 | 88 | 89 | // Check that JSON file parsing finishes with error status. 90 | // It also compares error with the golden one, if the latest is provided. 91 | // Returns error message if something is not expected. 92 | function automatic string expect_err_load_file(string path, json_error golden); 93 | json_error err; 94 | json_value val; 95 | json_result parsed = json_decoder::load_file(path); 96 | 97 | case (1) 98 | parsed.matches_err(err): begin 99 | if (golden != null) begin 100 | if (err.kind != golden.kind) begin 101 | return $sformatf( 102 | "Expect %s error but got %s intead! Captured error:\n%s", 103 | golden.kind.name(), err.kind.name(), err.to_string() 104 | ); 105 | end 106 | 107 | if ((golden.json_pos >= 0) && (err.json_pos != golden.json_pos)) begin 108 | return $sformatf( 109 | "Expect to get error for %0d symbol but got for %0d intead! Captured error:\n%s", 110 | golden.json_pos, err.json_pos, err.to_string() 111 | ); 112 | end 113 | end 114 | return ""; 115 | end 116 | 117 | parsed.matches_ok(val): begin 118 | return "JSON parsed succesfully, but error was expected!"; 119 | end 120 | endcase 121 | endfunction : expect_err_load_file 122 | 123 | 124 | // SystemVerilog does not support \r escape code, however, for JSON testing it has to be used. 125 | // So that function replace every two consecutive chars "\" and "r" with a single ASCII char 'd13 (CR). 126 | // Note: it looks like Verilator actually supports \r, so replacing for it is not required. 127 | function automatic string replace_cr(input string str); 128 | string res = ""; 129 | byte prev_char = ""; 130 | 131 | for (int i = 0; i < str.len(); i++) begin 132 | byte curr_char = str[i]; 133 | if ((prev_char == "\\") && (curr_char == "r")) begin 134 | res[res.len() - 1] = 8'd13; 135 | end else begin 136 | res = {res, curr_char}; 137 | end 138 | prev_char = curr_char; 139 | end 140 | 141 | return res; 142 | endfunction : replace_cr 143 | 144 | 145 | // Dummy enum for testing purposes 146 | typedef enum { 147 | DUMMY_FOO, 148 | DUMMY_BAR, 149 | DUMMY_BAZ 150 | } dummy_e; 151 | 152 | typedef json_enum#(dummy_e) json_dummy_enum; 153 | 154 | // Some simulators do not support '{} for empty associative arrays, so this a workaround 155 | json_value empty_jvalue_map [string]; 156 | 157 | endpackage : test_utils_pkg 158 | -------------------------------------------------------------------------------- /tests/json_string_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_string` 5 | module json_string_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_string_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | `SVUNIT_TESTS_BEGIN 26 | 27 | // Create json_string using constructor 28 | `SVTEST(create_new_test) begin 29 | json_string jstring; 30 | jstring = new("abc"); 31 | `FAIL_UNLESS_STR_EQUAL(jstring.get(), "abc") 32 | end `SVTEST_END 33 | 34 | 35 | // Create json_string using static method 36 | `SVTEST(create_static_test) begin 37 | json_string jstring; 38 | jstring = json_string::from("xyz"); 39 | `FAIL_UNLESS_STR_EQUAL(jstring.get(), "xyz") 40 | end `SVTEST_END 41 | 42 | 43 | // Clone json_string instance 44 | `SVTEST(clone_test) begin 45 | json_string orig = json_string::from("!@#"); 46 | json_string clone = orig.clone().into_string(); 47 | `FAIL_IF(orig == clone) 48 | `FAIL_UNLESS(orig.get() == clone.get()) 49 | end `SVTEST_END 50 | 51 | 52 | // Compare json_string instances 53 | `SVTEST(compare_test) begin 54 | json_string jstring_a = json_string::from(""); 55 | json_string jstring_b = json_string::from(""); 56 | // OK 57 | `FAIL_UNLESS(jstring_a.compare(jstring_a)) 58 | `FAIL_UNLESS(jstring_a.compare(jstring_b)) 59 | jstring_a.set("foobar"); 60 | jstring_b.set("foobar"); 61 | `FAIL_UNLESS(jstring_a.compare(jstring_b)) 62 | // Fail 63 | jstring_a.set("foo"); 64 | jstring_b.set("bar"); 65 | `FAIL_IF(jstring_a.compare(jstring_b)) 66 | jstring_a.set("hello"); 67 | jstring_b.set("bye"); 68 | `FAIL_IF(jstring_a.compare(jstring_b)) 69 | end `SVTEST_END 70 | 71 | 72 | // Compare jsom_string with other JSON values 73 | `SVTEST(compare_with_others_test) begin 74 | json_string jstring = json_string::from("1"); 75 | json_int jint = json_int::from(1); 76 | json_real jreal = json_real::from(1.0); 77 | json_bool jbool = json_bool::from(1); 78 | json_array jarray = json_array::from('{json_int::from(1)}); 79 | json_object jobject = json_object::from('{"int": json_int::from(1)}); 80 | 81 | `FAIL_IF(jstring.compare(jint)) 82 | `FAIL_IF(jstring.compare(jreal)) 83 | `FAIL_IF(jstring.compare(jbool)) 84 | `FAIL_IF(jstring.compare(jarray)) 85 | `FAIL_IF(jstring.compare(jobject)) 86 | `FAIL_IF(jstring.compare(null)) 87 | end `SVTEST_END 88 | 89 | 90 | // Access internal value of json_string 91 | `SVTEST(getter_setter_test) begin 92 | json_string jstring = json_string::from("42"); 93 | `FAIL_UNLESS_STR_EQUAL(jstring.get(), "42") 94 | jstring.set("-12345"); 95 | `FAIL_UNLESS_STR_EQUAL(jstring.get(), "-12345") 96 | end `SVTEST_END 97 | 98 | 99 | // Test json_string_encodable interface 100 | `SVTEST(encodable_test) begin 101 | json_string jstring = json_string::from("foo"); 102 | // Should be the same as get - returns internal value 103 | `FAIL_UNLESS(jstring.to_json_encodable() == jstring.get()) 104 | end `SVTEST_END 105 | 106 | 107 | // Test is_* methods 108 | `SVTEST(is_something_test) begin 109 | json_string jstring = json_string::from("42"); 110 | json_value jvalue = jstring; 111 | 112 | `FAIL_IF(jvalue.is_object()) 113 | `FAIL_IF(jvalue.is_array()) 114 | `FAIL_UNLESS(jvalue.is_string()) 115 | `FAIL_IF(jvalue.is_int()) 116 | `FAIL_IF(jvalue.is_real()) 117 | `FAIL_IF(jvalue.is_bool()) 118 | end `SVTEST_END 119 | 120 | 121 | // Test match_* methods 122 | `SVTEST(matches_something_test) begin 123 | json_object jobject; 124 | json_array jarray; 125 | json_string jstring; 126 | json_int jint; 127 | json_real jreal; 128 | json_bool jbool; 129 | 130 | json_string orig_jstring = json_string::from("42"); 131 | json_value jvalue = orig_jstring; 132 | 133 | `FAIL_IF(jvalue.matches_object(jobject)) 134 | `FAIL_IF(jvalue.matches_array(jarray)) 135 | `FAIL_UNLESS(jvalue.matches_string(jstring)) 136 | `FAIL_IF(jvalue.matches_int(jint)) 137 | `FAIL_IF(jvalue.matches_real(jreal)) 138 | `FAIL_IF(jvalue.matches_bool(jbool)) 139 | 140 | `FAIL_UNLESS(jstring == orig_jstring) 141 | end `SVTEST_END 142 | 143 | 144 | // Test try_into_* methods 145 | `SVTEST(try_into_something_test) begin 146 | json_result#(json_object) res_jobject; 147 | json_result#(json_array) res_jarray; 148 | json_result#(json_string) res_jstring; 149 | json_result#(json_int) res_jint; 150 | json_result#(json_real) res_jreal; 151 | json_result#(json_bool) res_jbool; 152 | 153 | json_string orig_jstring = json_string::from("baz"); 154 | json_value jvalue = orig_jstring; 155 | 156 | res_jobject = jvalue.try_into_object(); 157 | res_jarray = jvalue.try_into_array(); 158 | res_jstring = jvalue.try_into_string(); 159 | res_jint = jvalue.try_into_int(); 160 | res_jreal = jvalue.try_into_real(); 161 | res_jbool = jvalue.try_into_bool(); 162 | 163 | `FAIL_IF(res_jobject.is_ok()) 164 | `FAIL_IF(res_jarray.is_ok()) 165 | `FAIL_UNLESS(res_jstring.is_ok()) 166 | `FAIL_IF(res_jint.is_ok()) 167 | `FAIL_IF(res_jreal.is_ok()) 168 | `FAIL_IF(res_jbool.is_ok()) 169 | 170 | `FAIL_UNLESS(res_jstring.unwrap() == orig_jstring) 171 | end `SVTEST_END 172 | 173 | 174 | // Test into_* method 175 | `SVTEST(into_something_test) begin 176 | json_string res_jstring; 177 | json_string orig_jstring = json_string::from(""); 178 | json_value jvalue = orig_jstring; 179 | 180 | res_jstring = jvalue.into_string(); 181 | end `SVTEST_END 182 | 183 | `SVUNIT_TESTS_END 184 | 185 | endmodule : json_string_unit_test 186 | -------------------------------------------------------------------------------- /tests/json_load_err_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Basic tests of `json_decoder` is used to parse JSON with error 5 | module json_load_err_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import json_pkg::*; 8 | 9 | string name = "json_load_err_ut"; 10 | svunit_testcase svunit_ut; 11 | 12 | function void build(); 13 | svunit_ut = new(name); 14 | endfunction 15 | 16 | task setup(); 17 | svunit_ut.setup(); 18 | endtask 19 | 20 | task teardown(); 21 | svunit_ut.teardown(); 22 | endtask 23 | 24 | `SVUNIT_TESTS_BEGIN 25 | 26 | 27 | `SVTEST(value_err_test) begin 28 | `EXPECT_ERR_LOAD_STR("bar", json_error::create(json_error::EXPECTED_VALUE, .json_pos(0))) 29 | `EXPECT_ERR_LOAD_STR(" bar", json_error::create(json_error::EXPECTED_VALUE, .json_pos(2))) 30 | `EXPECT_ERR_LOAD_STR("False", json_error::create(json_error::EXPECTED_VALUE, .json_pos(0))) 31 | `EXPECT_ERR_LOAD_STR("True", json_error::create(json_error::EXPECTED_VALUE, .json_pos(0))) 32 | `EXPECT_ERR_LOAD_STR("Null", json_error::create(json_error::EXPECTED_VALUE, .json_pos(0))) 33 | `EXPECT_ERR_LOAD_STR(" ", json_error::create(json_error::EOF_VALUE, .json_pos(0))) 34 | `EXPECT_ERR_LOAD_STR("\n \n ", json_error::create(json_error::EOF_VALUE, .json_pos(3))) 35 | end `SVTEST_END 36 | 37 | 38 | `SVTEST(literal_err_test) begin 39 | `EXPECT_ERR_LOAD_STR("t", json_error::create(json_error::EOF_LITERAL, .json_pos(0))) 40 | `EXPECT_ERR_LOAD_STR(" tru", json_error::create(json_error::EOF_LITERAL, .json_pos(1))) 41 | `EXPECT_ERR_LOAD_STR("f ", json_error::create(json_error::EOF_LITERAL, .json_pos(0))) 42 | `EXPECT_ERR_LOAD_STR("fals", json_error::create(json_error::EOF_LITERAL, .json_pos(0))) 43 | `EXPECT_ERR_LOAD_STR("n\n", json_error::create(json_error::EOF_LITERAL, .json_pos(0))) 44 | `EXPECT_ERR_LOAD_STR("nul", json_error::create(json_error::EOF_LITERAL, .json_pos(0))) 45 | `EXPECT_ERR_LOAD_STR("folse", json_error::create(json_error::INVALID_LITERAL, .json_pos(0))) 46 | `EXPECT_ERR_LOAD_STR("falsed", json_error::create(json_error::TRAILING_CHARS, .json_pos(5))) 47 | `EXPECT_ERR_LOAD_STR("truee", json_error::create(json_error::TRAILING_CHARS, .json_pos(4))) 48 | `EXPECT_ERR_LOAD_STR("ttrue", json_error::create(json_error::INVALID_LITERAL, .json_pos(0))) 49 | `EXPECT_ERR_LOAD_STR("nill", json_error::create(json_error::INVALID_LITERAL, .json_pos(0))) 50 | end `SVTEST_END 51 | 52 | 53 | `SVTEST(number_err_test) begin 54 | `EXPECT_ERR_LOAD_STR("8'h23", json_error::create(json_error::INVALID_NUMBER, .json_pos(1))) 55 | `EXPECT_ERR_LOAD_STR("0x1", json_error::create(json_error::INVALID_NUMBER, .json_pos(1))) 56 | `EXPECT_ERR_LOAD_STR("0b11", json_error::create(json_error::INVALID_NUMBER, .json_pos(1))) 57 | `EXPECT_ERR_LOAD_STR("0,1", json_error::create(json_error::TRAILING_CHARS, .json_pos(1))) 58 | `EXPECT_ERR_LOAD_STR("212 122", json_error::create(json_error::TRAILING_CHARS, .json_pos(5))) 59 | `EXPECT_ERR_LOAD_STR("2f5e", json_error::create(json_error::INVALID_NUMBER, .json_pos(1))) 60 | end `SVTEST_END 61 | 62 | 63 | `SVTEST(string_err_test) begin 64 | `EXPECT_ERR_LOAD_STR("\"", json_error::create(json_error::EOF_STRING, .json_pos(0))) 65 | `EXPECT_ERR_LOAD_STR("\" 1221 ", json_error::create(json_error::EOF_STRING, .json_pos(7))) 66 | `EXPECT_ERR_LOAD_STR("\" [] ", json_error::create(json_error::EOF_STRING, .json_pos(5))) 67 | `EXPECT_ERR_LOAD_STR("\"}", json_error::create(json_error::EOF_STRING, .json_pos(1))) 68 | `EXPECT_ERR_LOAD_STR("\" \\z \"", json_error::create(json_error::INVALID_ESCAPE, .json_pos(3))) 69 | `EXPECT_ERR_LOAD_STR("\" \n \"", json_error::create(json_error::INVALID_CHAR, .json_pos(2))) 70 | end `SVTEST_END 71 | 72 | 73 | `SVTEST(array_err_test) begin 74 | `EXPECT_ERR_LOAD_STR(" [", json_error::create(json_error::EOF_VALUE, .json_pos(1))) 75 | `EXPECT_ERR_LOAD_STR("[ a", json_error::create(json_error::EXPECTED_VALUE, .json_pos(2))) 76 | `EXPECT_ERR_LOAD_STR("[null,", json_error::create(json_error::EOF_VALUE, .json_pos(5))) 77 | `EXPECT_ERR_LOAD_STR("[ true, 1.2, ] ", json_error::create(json_error::TRAILING_COMMA, .json_pos(13))) 78 | `EXPECT_ERR_LOAD_STR("[ 42, , false] ", json_error::create(json_error::EXPECTED_VALUE, .json_pos(6))) 79 | `EXPECT_ERR_LOAD_STR("[ , ] ", json_error::create(json_error::EXPECTED_VALUE, .json_pos(2))) 80 | `EXPECT_ERR_LOAD_STR("[ \"123\"; 456 ] ", json_error::create(json_error::EXPECTED_ARRAY_COMMA_OR_END, .json_pos(7))) 81 | end `SVTEST_END 82 | 83 | 84 | `SVTEST(object_err_test) begin 85 | `EXPECT_ERR_LOAD_STR(" {", json_error::create(json_error::EOF_STRING, .json_pos(1))) 86 | `EXPECT_ERR_LOAD_STR("{ a", json_error::create(json_error::EXPECTED_DOUBLE_QUOTE, .json_pos(2))) 87 | `EXPECT_ERR_LOAD_STR("{ \"foo\"", json_error::create(json_error::EOF_OBJECT, .json_pos(6))) 88 | `EXPECT_ERR_LOAD_STR("{ \"foo\", \"bar\"", json_error::create(json_error::EXPECTED_COLON, .json_pos(7))) 89 | `EXPECT_ERR_LOAD_STR("{ \"foo\"}", json_error::create(json_error::EXPECTED_COLON, .json_pos(7))) 90 | `EXPECT_ERR_LOAD_STR("{ \"foo\": ", json_error::create(json_error::EOF_VALUE, .json_pos(9))) 91 | `EXPECT_ERR_LOAD_STR("{ \"foo\":,", json_error::create(json_error::EXPECTED_VALUE, .json_pos(8))) 92 | `EXPECT_ERR_LOAD_STR("{ \"foo\": } ", json_error::create(json_error::EXPECTED_VALUE, .json_pos(9))) 93 | `EXPECT_ERR_LOAD_STR("{ \"foo\": true, ", json_error::create(json_error::EOF_STRING, .json_pos(14))) 94 | `EXPECT_ERR_LOAD_STR("{ \"foo\": true,}", json_error::create(json_error::TRAILING_COMMA, .json_pos(14))) 95 | `EXPECT_ERR_LOAD_STR("{ : true}", json_error::create(json_error::EXPECTED_DOUBLE_QUOTE, .json_pos(2))) 96 | `EXPECT_ERR_LOAD_STR("{ 2345: 32}", json_error::create(json_error::EXPECTED_DOUBLE_QUOTE, .json_pos(2))) 97 | `EXPECT_ERR_LOAD_STR("{ true: true}", json_error::create(json_error::EXPECTED_DOUBLE_QUOTE, .json_pos(2))) 98 | end `SVTEST_END 99 | 100 | 101 | `SVUNIT_TESTS_END 102 | 103 | endmodule : json_load_err_unit_test 104 | -------------------------------------------------------------------------------- /tests/json_dump_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Basic tests of `json_encoder` is used to dump JSON succesfuly 5 | module json_dump_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_dump_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | // Construct and return full path to a JSON file from `tests/golden_json` 26 | function automatic string resolve_path(string path); 27 | return {`STRINGIFY(`GOLDEN_JSON_DIR), "/", path}; 28 | endfunction : resolve_path 29 | 30 | 31 | function automatic string read_file(string path); 32 | int file_descr; 33 | string file_text; 34 | 35 | file_descr = $fopen(path, "r"); 36 | if (file_descr == 0) begin 37 | return "File not opened"; 38 | end 39 | 40 | while (!$feof(file_descr)) begin 41 | string line; 42 | void'($fgets(line, file_descr)); 43 | file_text = {file_text, line}; 44 | end 45 | $fclose(file_descr); 46 | 47 | return file_text; 48 | endfunction : read_file 49 | 50 | `SVUNIT_TESTS_BEGIN 51 | 52 | 53 | `SVTEST(dump_literal_test) 54 | begin 55 | `EXPECT_OK_DUMP_STR(null, "null") 56 | `EXPECT_OK_DUMP_STR(json_bool::from(1), "true") 57 | `EXPECT_OK_DUMP_STR(json_bool::from(0), "false") 58 | end 59 | `SVTEST_END 60 | 61 | 62 | `SVTEST(dump_int_test) 63 | begin 64 | `EXPECT_OK_DUMP_STR(json_int::from(0), "0") 65 | `EXPECT_OK_DUMP_STR(json_int::from(42), "42") 66 | `EXPECT_OK_DUMP_STR(json_int::from(-123213), "-123213") 67 | end 68 | `SVTEST_END 69 | 70 | 71 | `SVTEST(dump_real_test) 72 | begin 73 | `EXPECT_OK_DUMP_STR(json_real::from(0.000002), "0.000002") 74 | `EXPECT_OK_DUMP_STR(json_real::from(-123213292.0), "-123213292.000000") 75 | `EXPECT_OK_DUMP_STR(json_real::from(123302.123876), "123302.123876") 76 | end 77 | `SVTEST_END 78 | 79 | 80 | `SVTEST(dump_string_test) 81 | begin 82 | `EXPECT_OK_DUMP_STR(json_string::from(""), "\"\"") 83 | `EXPECT_OK_DUMP_STR(json_string::from(" "), "\" \"") 84 | `EXPECT_OK_DUMP_STR(json_string::from("hello world"), "\"hello world\"") 85 | `EXPECT_OK_DUMP_STR(json_string::from("hello\nworld"), "\"hello\\nworld\"") 86 | end 87 | `SVTEST_END 88 | 89 | 90 | `SVTEST(dump_array_test) 91 | begin 92 | `EXPECT_OK_DUMP_STR(json_array::from('{}), "[]") 93 | `EXPECT_OK_DUMP_STR(json_array::from('{json_int::from(42)}), "[42]") 94 | `EXPECT_OK_DUMP_STR(json_array::from('{json_bool::from(1), json_bool::from(0)}), "[true,false]") 95 | `EXPECT_OK_DUMP_STR(json_array::from('{json_array::from('{}), json_string::from("foo")}), "[[],\"foo\"]") 96 | `EXPECT_OK_DUMP_STR(json_array::from('{json_object::from(empty_jvalue_map)}), "[{}]") 97 | end 98 | `SVTEST_END 99 | 100 | 101 | `SVTEST(dump_object_test) 102 | begin 103 | `EXPECT_OK_DUMP_STR(json_object::from(empty_jvalue_map), "{}") 104 | `EXPECT_OK_DUMP_STR(json_object::from('{"bar" : json_int::from(42)}), "{\"bar\":42}") 105 | `EXPECT_OK_DUMP_STR( 106 | json_object::from('{"bar": json_int::from(42), "foo" : json_int::from(-1)}), 107 | "{\"bar\":42,\"foo\":-1}" 108 | ) 109 | `EXPECT_OK_DUMP_STR(json_object::from('{"arr": json_array::from('{})}), "{\"arr\":[]}") 110 | end 111 | `SVTEST_END 112 | 113 | 114 | `SVTEST(dump_indent0_test) 115 | begin 116 | `EXPECT_OK_DUMP_STR( 117 | json_decoder::load_file(resolve_path("pizza_indent0.json")).unwrap(), 118 | read_file(resolve_path("pizza_indent0.json")) 119 | ) 120 | `EXPECT_OK_DUMP_STR( 121 | json_decoder::load_file(resolve_path("recipes_indent0.json")).unwrap(), 122 | read_file(resolve_path("recipes_indent0.json")) 123 | ) 124 | `EXPECT_OK_DUMP_STR( 125 | json_decoder::load_file(resolve_path("video_indent0.json")).unwrap(), 126 | read_file(resolve_path("video_indent0.json")) 127 | ) 128 | end 129 | `SVTEST_END 130 | 131 | 132 | `SVTEST(dump_indent2_test) 133 | begin 134 | `EXPECT_OK_DUMP_STR( 135 | json_decoder::load_file(resolve_path("pizza_indent2.json")).unwrap(), 136 | read_file(resolve_path("pizza_indent2.json")), 137 | 2 138 | ) 139 | `EXPECT_OK_DUMP_STR( 140 | json_decoder::load_file(resolve_path("recipes_indent2.json")).unwrap(), 141 | read_file(resolve_path("recipes_indent2.json")), 142 | 2 143 | ) 144 | `EXPECT_OK_DUMP_STR( 145 | json_decoder::load_file(resolve_path("video_indent2.json")).unwrap(), 146 | read_file(resolve_path("video_indent2.json")), 147 | 2 148 | ) 149 | end 150 | `SVTEST_END 151 | 152 | 153 | `SVTEST(dump_indent4_test) 154 | begin 155 | `EXPECT_OK_DUMP_STR( 156 | json_decoder::load_file(resolve_path("pizza_indent4.json")).unwrap(), 157 | read_file(resolve_path("pizza_indent4.json")), 158 | 4 159 | ) 160 | `EXPECT_OK_DUMP_STR( 161 | json_decoder::load_file(resolve_path("recipes_indent4.json")).unwrap(), 162 | read_file(resolve_path("recipes_indent4.json")), 163 | 4 164 | ) 165 | `EXPECT_OK_DUMP_STR( 166 | json_decoder::load_file(resolve_path("video_indent4.json")).unwrap(), 167 | read_file(resolve_path("video_indent4.json")), 168 | 4 169 | ) 170 | end 171 | `SVTEST_END 172 | 173 | 174 | `SVTEST(loopback_test) 175 | begin 176 | json_value dec_obj_before = json_decoder::load_file(resolve_path("pizza.json")).unwrap(); 177 | string enc_str = json_encoder::dump_file(dec_obj_before, "dump_pizza.json").unwrap(); 178 | json_value dec_obj_ret = json_decoder::load_string(enc_str).unwrap(); 179 | json_value dec_obj_after = json_decoder::load_file("dump_pizza.json").unwrap(); 180 | 181 | `FAIL_UNLESS(dec_obj_before.compare(dec_obj_ret)) 182 | `FAIL_UNLESS(dec_obj_before.compare(dec_obj_after)) 183 | end 184 | `SVTEST_END 185 | 186 | 187 | `SVUNIT_TESTS_END 188 | 189 | endmodule : json_dump_unit_test 190 | -------------------------------------------------------------------------------- /src/values/json_object.sv: -------------------------------------------------------------------------------- 1 | // JSON object. 2 | // This wrapper class represens standard JSON object value using SV associative array. 3 | // The class basically wraps standard SV associative array methods with some additional methods 4 | // required to operate as JSON value. 5 | // No additional checks are implemented for "out-of-range" accesses and similar, 6 | // so you can expect that this class will operate according to behavior of an original underlying SV associative array. 7 | class json_object extends json_value implements json_object_encodable; 8 | typedef json_value values_t[string]; 9 | typedef string keys_t[$]; 10 | 11 | protected values_t values; 12 | 13 | // Normal constructor 14 | extern function new(values_t values); 15 | 16 | // Create `json_object` from associative array 17 | extern static function json_object from(values_t values); 18 | 19 | // Create a deep copy of an instance 20 | extern virtual function json_value clone(); 21 | 22 | // Compare with another instance 23 | extern virtual function bit compare(json_value value); 24 | 25 | // Get a value at the provided key 26 | extern virtual function json_value get(string key); 27 | 28 | // Set the given value for the provided key 29 | extern virtual function void set(string key, json_value value); 30 | 31 | // Get a size of the object (number of items stored) 32 | extern virtual function int size(); 33 | 34 | // Check if a value with the given key already exists in the object. 35 | // Return 1 if so, and 0 otherwise. 36 | extern virtual function int exists(string key); 37 | 38 | // Remove a value with the provided key 39 | extern virtual function void delete(string key); 40 | 41 | // Remove all stored values 42 | extern virtual function void flush(); 43 | 44 | // Assign to the given key variable the value of the first (smallest) key. 45 | // Return 0 if not entires, otherwise, return 1. 46 | extern virtual function int first(ref string key); 47 | 48 | // Assign to the given key variable the value of the last (largest) key. 49 | // Return 0 if not entires, otherwise, return 1. 50 | extern virtual function int last(ref string key); 51 | 52 | // Find the smallest key whose value is greater than the given key. 53 | // Return 1 if such key exists, 0 otherwise. 54 | extern virtual function int next(ref string key); 55 | 56 | // Find the smallest key whose value is smaller than the given key. 57 | // Return 1 if such key exists, 0 otherwise. 58 | extern virtual function int prev(ref string key); 59 | 60 | // Get all internal values as associative array 61 | extern virtual function values_t get_values(); 62 | 63 | // Get all keys for internal values as queue 64 | extern virtual function keys_t get_keys(); 65 | 66 | // Get value encodable as JSON object (for interface json_object_encodable) 67 | extern virtual function json_object_encodable::values_t to_json_encodable(); 68 | endclass : json_object 69 | 70 | 71 | function json_object::new(values_t values); 72 | foreach (values[key]) begin 73 | this.values[key] = values[key]; 74 | end 75 | endfunction : new 76 | 77 | 78 | function json_object json_object::from(values_t values); 79 | json_object obj = new(values); 80 | return obj; 81 | endfunction : from 82 | 83 | 84 | function json_value json_object::clone(); 85 | json_value new_values [string]; 86 | string key; 87 | 88 | if (first(key) == 1) begin 89 | do begin 90 | if (this.get(key) == null) begin 91 | new_values[key] = null; 92 | end else begin 93 | new_values[key] = this.get(key).clone(); 94 | end 95 | end while (next(key) == 1); 96 | end 97 | 98 | return json_object::from(new_values); 99 | endfunction : clone 100 | 101 | 102 | function bit json_object::compare(json_value value); 103 | json_result#(json_object) casted; 104 | json_error err; 105 | json_object rhs; 106 | 107 | if (value == null) begin 108 | return 0; 109 | end 110 | 111 | casted = value.try_into_object(); 112 | case (1) 113 | casted.matches_err(err): return 0; 114 | casted.matches_ok(rhs): begin 115 | string key; 116 | if (rhs.size() != this.size()) begin 117 | return 0; 118 | end 119 | 120 | if (first(key) == 1) begin 121 | do begin 122 | if ((rhs.exists(key) == 0)) begin 123 | return 0; 124 | end else if ((this.get(key) != null) && (rhs.get(key) != null)) begin 125 | if (!this.get(key).compare(rhs.get(key))) begin 126 | return 0; 127 | end 128 | end else if ((this.get(key) == null) && (rhs.get(key) == null)) begin 129 | continue; 130 | end else begin 131 | return 0; 132 | end 133 | end while (next(key) == 1); 134 | end 135 | 136 | return 1; 137 | end 138 | endcase 139 | endfunction : compare 140 | 141 | 142 | function json_value json_object::get(string key); 143 | return this.values[key]; 144 | endfunction : get 145 | 146 | 147 | function void json_object::set(string key, json_value value); 148 | this.values[key] = value; 149 | endfunction : set 150 | 151 | 152 | function int json_object::size(); 153 | return this.values.size(); 154 | endfunction : size 155 | 156 | 157 | function int json_object::exists(string key); 158 | return this.values.exists(key); 159 | endfunction : exists 160 | 161 | 162 | function void json_object::delete(string key); 163 | this.values.delete(key); 164 | endfunction : delete 165 | 166 | 167 | function void json_object::flush(); 168 | this.values.delete(); 169 | endfunction : flush 170 | 171 | 172 | function int json_object::first(ref string key); 173 | return this.values.first(key); 174 | endfunction : first 175 | 176 | 177 | function int json_object::last(ref string key); 178 | return this.values.last(key); 179 | endfunction : last 180 | 181 | 182 | function int json_object::next(ref string key); 183 | return this.values.next(key); 184 | endfunction : next 185 | 186 | 187 | function int json_object::prev(ref string key); 188 | return this.values.prev(key); 189 | endfunction : prev 190 | 191 | 192 | function json_object::values_t json_object::get_values(); 193 | return this.values; 194 | endfunction : get_values 195 | 196 | 197 | function json_object::keys_t json_object::get_keys(); 198 | keys_t keys; 199 | 200 | foreach (this.values[key]) begin 201 | keys.push_back(key); 202 | end 203 | 204 | return keys; 205 | endfunction : get_keys 206 | 207 | 208 | function json_object_encodable::values_t json_object::to_json_encodable(); 209 | json_object_encodable::values_t values; 210 | string key; 211 | 212 | if (first(key) == 1) begin 213 | do begin 214 | values[key] = this.get(key); 215 | end while (next(key) == 1); 216 | end 217 | 218 | return values; 219 | endfunction : to_json_encodable 220 | -------------------------------------------------------------------------------- /tests/json_error_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_error` class 5 | module json_error_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import json_pkg::*; 8 | 9 | string name = "json_error_ut"; 10 | svunit_testcase svunit_ut; 11 | 12 | function void build(); 13 | svunit_ut = new(name); 14 | endfunction 15 | 16 | task setup(); 17 | svunit_ut.setup(); 18 | endtask 19 | 20 | task teardown(); 21 | svunit_ut.teardown(); 22 | endtask 23 | 24 | `SVUNIT_TESTS_BEGIN 25 | 26 | 27 | `SVTEST(plain_msg_test) 28 | begin 29 | json_error err = json_error::create(json_error::INTERNAL); 30 | string err_s = err.to_string(); 31 | `FAIL_UNLESS_STR_EQUAL(err_s, "JSON error:\nINTERNAL: Unspecified internal error") 32 | end 33 | `SVTEST_END 34 | 35 | 36 | `SVTEST(file_msg_test) 37 | begin 38 | json_error err = json_error::create( 39 | .kind(json_error::INTERNAL), 40 | .source_file("/path/to/some/file.sv") 41 | ); 42 | string err_s = err.to_string(); 43 | `FAIL_UNLESS_STR_EQUAL(err_s, { 44 | "JSON error raised from /path/to/some/file.sv:\n", 45 | "INTERNAL: Unspecified internal error" 46 | }) 47 | end 48 | `SVTEST_END 49 | 50 | 51 | `SVTEST(file_line_msg_test) 52 | begin 53 | json_error err = json_error::create( 54 | .kind(json_error::INTERNAL), 55 | .source_file("/path/to/some/file.sv"), 56 | .source_line(20) 57 | ); 58 | string err_s = err.to_string(); 59 | `FAIL_UNLESS_STR_EQUAL(err_s, { 60 | "JSON error raised from /path/to/some/file.sv:20:\n", 61 | "INTERNAL: Unspecified internal error" 62 | }) 63 | end 64 | `SVTEST_END 65 | 66 | 67 | `SVTEST(description_test) 68 | begin 69 | json_error err = json_error::create( 70 | .kind(json_error::INTERNAL), 71 | .description("Additional description message") 72 | ); 73 | string err_s = err.to_string(); 74 | `FAIL_UNLESS_STR_EQUAL(err_s, { 75 | "JSON error:\n", 76 | "INTERNAL: Unspecified internal error\n", 77 | "Additional description message" 78 | }) 79 | end 80 | `SVTEST_END 81 | 82 | 83 | `SVTEST(file_line_descr_msg_test) 84 | begin 85 | json_error err = json_error::create( 86 | .kind(json_error::INTERNAL), 87 | .description("Additional description message"), 88 | .source_file("/path/to/some/file.sv"), 89 | .source_line(20) 90 | ); 91 | string err_s = err.to_string(); 92 | `FAIL_UNLESS_STR_EQUAL(err_s, { 93 | "JSON error raised from /path/to/some/file.sv:20:\n", 94 | "INTERNAL: Unspecified internal error\n", 95 | "Additional description message" 96 | }) 97 | end 98 | `SVTEST_END 99 | 100 | 101 | `SVTEST(single_line_short1_context_msg_test) 102 | begin 103 | json_error err = json_error::create( 104 | .kind(json_error::TRAILING_COMMA), 105 | .json_str("[[],]"), 106 | .json_pos(3) 107 | ); 108 | string err_s = err.to_string(); 109 | `FAIL_UNLESS_STR_EQUAL(err_s, { 110 | "JSON error:\n", 111 | "TRAILING_COMMA: Unexpected comma after the last value\n", 112 | "JSON string line 1 symbol 4:\n", 113 | "[[],]\n", 114 | " ^\n", 115 | " |" 116 | }) 117 | end 118 | `SVTEST_END 119 | 120 | 121 | `SVTEST(single_line_short2_context_msg_test) 122 | begin 123 | json_error err = json_error::create( 124 | .kind(json_error::EXPECTED_VALUE), 125 | .json_str("a"), 126 | .json_pos(0) 127 | ); 128 | string err_s = err.to_string(); 129 | `FAIL_UNLESS_STR_EQUAL(err_s, { 130 | "JSON error:\n", 131 | "EXPECTED_VALUE: Current character should start some JSON value\n", 132 | "JSON string line 1 symbol 1:\n", 133 | "a\n", 134 | "^\n", 135 | "|" 136 | }) 137 | end 138 | `SVTEST_END 139 | 140 | 141 | `SVTEST(single_line_short3_context_msg_test) 142 | begin 143 | json_error err = json_error::create( 144 | .kind(json_error::EXPECTED_VALUE), 145 | .json_str("[[],a"), 146 | .json_pos(4) 147 | ); 148 | string err_s = err.to_string(); 149 | `FAIL_UNLESS_STR_EQUAL(err_s, { 150 | "JSON error:\n", 151 | "EXPECTED_VALUE: Current character should start some JSON value\n", 152 | "JSON string line 1 symbol 5:\n", 153 | "[[],a\n", 154 | " ^\n", 155 | " |" 156 | }) 157 | end 158 | `SVTEST_END 159 | 160 | 161 | `SVTEST(single_line_long1_context_msg_test) 162 | begin 163 | json_error err = json_error::create( 164 | .kind(json_error::EXPECTED_VALUE), 165 | .json_str("[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,abc,24,25,26,27,28,29,30]"), 166 | .json_pos(60) 167 | ); 168 | string err_s = err.to_string(); 169 | `FAIL_UNLESS_STR_EQUAL(err_s, { 170 | "JSON error:\n", 171 | "EXPECTED_VALUE: Current character should start some JSON value\n", 172 | "JSON string line 1 symbol 61:\n", 173 | "...11,12,13,14,15,16,17,18,19,20,21,22,abc,24,25,26,27,28,29,30]\n", 174 | " ^\n", 175 | " |" 176 | }) 177 | end 178 | `SVTEST_END 179 | 180 | 181 | `SVTEST(single_line_long2_context_msg_test) 182 | begin 183 | json_error err = json_error::create( 184 | .kind(json_error::EXPECTED_VALUE), 185 | .json_str("[0,1,abc,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33]"), 186 | .json_pos(5) 187 | ); 188 | string err_s = err.to_string(); 189 | `FAIL_UNLESS_STR_EQUAL(err_s, { 190 | "JSON error:\n", 191 | "EXPECTED_VALUE: Current character should start some JSON value\n", 192 | "JSON string line 1 symbol 6:\n", 193 | "[0,1,abc,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27...\n", 194 | " ^\n", 195 | " |" 196 | }) 197 | end 198 | `SVTEST_END 199 | 200 | 201 | `SVTEST(single_line_long3_context_msg_test) 202 | begin 203 | json_error err = json_error::create( 204 | .kind(json_error::EXPECTED_VALUE), 205 | .json_str("[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,abc,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48]"), 206 | .json_pos(60) 207 | ); 208 | string err_s = err.to_string(); 209 | `FAIL_UNLESS_STR_EQUAL(err_s, { 210 | "JSON error:\n", 211 | "EXPECTED_VALUE: Current character should start some JSON value\n", 212 | "JSON string line 1 symbol 61:\n", 213 | "...11,12,13,14,15,16,17,18,19,20,21,22,abc,24,25,26,27,28,29,30,31,32,33,34,...\n", 214 | " ^\n", 215 | " |" 216 | }) 217 | end 218 | `SVTEST_END 219 | 220 | 221 | `SVTEST(multi_line_short_context_msg_test) 222 | begin 223 | json_error err = json_error::create( 224 | .kind(json_error::EXPECTED_VALUE), 225 | .json_str({ 226 | "[\n", 227 | " 42,\n", 228 | " abc\n", 229 | "]" 230 | }), 231 | .json_pos(14) 232 | ); 233 | string err_s = err.to_string(); 234 | `FAIL_UNLESS_STR_EQUAL(err_s, { 235 | "JSON error:\n", 236 | "EXPECTED_VALUE: Current character should start some JSON value\n", 237 | "JSON string line 3 symbol 5:\n", 238 | " abc\n", 239 | " ^\n", 240 | " |" 241 | }) 242 | end 243 | `SVTEST_END 244 | 245 | 246 | `SVUNIT_TESTS_END 247 | 248 | endmodule : json_error_unit_test -------------------------------------------------------------------------------- /src/values/json_value.sv: -------------------------------------------------------------------------------- 1 | // Generic JSON value 2 | virtual class json_value implements json_value_encodable; 3 | // Create deep copy of a value 4 | pure virtual function json_value clone(); 5 | 6 | // Compare with value 7 | pure virtual function bit compare(json_value value); 8 | 9 | // Try to cast current value to `json_object` 10 | extern virtual function bit matches_object(output json_object value); 11 | 12 | // Try to cast current value to `json_array` 13 | extern virtual function bit matches_array(output json_array value); 14 | 15 | // Try to cast current value to `json_string` 16 | extern virtual function bit matches_string(output json_string value); 17 | 18 | // Try to cast current value to `json_int` 19 | extern virtual function bit matches_int(output json_int value); 20 | 21 | // Try to cast current value to `json_real` 22 | extern virtual function bit matches_real(output json_real value); 23 | 24 | // Try to cast current value to `json_bool` 25 | extern virtual function bit matches_bool(output json_bool value); 26 | 27 | // Try to represent current value as `json_object` 28 | extern virtual function json_result#(json_object) try_into_object(); 29 | 30 | // Try to represent current value as `json_array` 31 | extern virtual function json_result#(json_array) try_into_array(); 32 | 33 | // Try to represent current value as `json_string` 34 | extern virtual function json_result#(json_string) try_into_string(); 35 | 36 | // Try to represent current value as `json_int` 37 | extern virtual function json_result#(json_int) try_into_int(); 38 | 39 | // Try to represent current value as `json_real` 40 | extern virtual function json_result#(json_real) try_into_real(); 41 | 42 | // Try to represent current value as `json_bool` 43 | extern virtual function json_result#(json_bool) try_into_bool(); 44 | 45 | // Represent current value as `json_object`. Throw $fatal on failure. 46 | extern virtual function json_object into_object(); 47 | 48 | // Represent current value as `json_array`. Throw $fatal on failure. 49 | extern virtual function json_array into_array(); 50 | 51 | // Represent current value as `json_string`. Throw $fatal on failure. 52 | extern virtual function json_string into_string(); 53 | 54 | // Represent current value as `json_int`. Throw $fatal on failure. 55 | extern virtual function json_int into_int(); 56 | 57 | // Represent current value as `json_real`. Throw $fatal on failure. 58 | extern virtual function json_real into_real(); 59 | 60 | // Represent current value as `json_bool`. Throw $fatal on failure. 61 | extern virtual function json_bool into_bool(); 62 | 63 | // Check if current value is `json_object` 64 | extern virtual function bit is_object(); 65 | 66 | // Check if current value is `json_array` 67 | extern virtual function bit is_array(); 68 | 69 | // Check if current value is `json_string` 70 | extern virtual function bit is_string(); 71 | 72 | // Check if current value is `json_int` 73 | extern virtual function bit is_int(); 74 | 75 | // Check if current value is `json_real` 76 | extern virtual function bit is_real(); 77 | 78 | // Check if current value is `json_bool` 79 | extern virtual function bit is_bool(); 80 | endclass : json_value 81 | 82 | 83 | function bit json_value::matches_object(output json_object value); 84 | return $cast(value, this); 85 | endfunction : matches_object 86 | 87 | 88 | function bit json_value::matches_array(output json_array value); 89 | return $cast(value, this); 90 | endfunction : matches_array 91 | 92 | 93 | function bit json_value::matches_string(output json_string value); 94 | return $cast(value, this); 95 | endfunction : matches_string 96 | 97 | 98 | function bit json_value::matches_int(output json_int value); 99 | return $cast(value, this); 100 | endfunction : matches_int 101 | 102 | 103 | function bit json_value::matches_real(output json_real value); 104 | return $cast(value, this); 105 | endfunction : matches_real 106 | 107 | 108 | function bit json_value::matches_bool(output json_bool value); 109 | return $cast(value, this); 110 | endfunction : matches_bool 111 | 112 | 113 | function json_result#(json_object) json_value::try_into_object(); 114 | json_object value; 115 | if (this.matches_object(value)) begin 116 | return json_result#(json_object)::ok(value); 117 | end else begin 118 | return json_result#(json_object)::err(json_error::create(json_error::TYPE_CONVERSION)); 119 | end 120 | endfunction : try_into_object 121 | 122 | 123 | function json_result#(json_array) json_value::try_into_array(); 124 | json_array value; 125 | if (this.matches_array(value)) begin 126 | return json_result#(json_array)::ok(value); 127 | end else begin 128 | return json_result#(json_array)::err(json_error::create(json_error::TYPE_CONVERSION)); 129 | end 130 | endfunction : try_into_array 131 | 132 | 133 | function json_result#(json_string) json_value::try_into_string(); 134 | json_string value; 135 | if (this.matches_string(value)) begin 136 | return json_result#(json_string)::ok(value); 137 | end else begin 138 | return json_result#(json_string)::err(json_error::create(json_error::TYPE_CONVERSION)); 139 | end 140 | endfunction : try_into_string 141 | 142 | 143 | function json_result#(json_int) json_value::try_into_int(); 144 | json_int value; 145 | if (this.matches_int(value)) begin 146 | return json_result#(json_int)::ok(value); 147 | end else begin 148 | return json_result#(json_int)::err(json_error::create(json_error::TYPE_CONVERSION)); 149 | end 150 | endfunction : try_into_int 151 | 152 | 153 | function json_result#(json_real) json_value::try_into_real(); 154 | json_real value; 155 | if (this.matches_real(value)) begin 156 | return json_result#(json_real)::ok(value); 157 | end else begin 158 | return json_result#(json_real)::err(json_error::create(json_error::TYPE_CONVERSION)); 159 | end 160 | endfunction : try_into_real 161 | 162 | 163 | function json_result#(json_bool) json_value::try_into_bool(); 164 | json_bool value; 165 | if (this.matches_bool(value)) begin 166 | return json_result#(json_bool)::ok(value); 167 | end else begin 168 | return json_result#(json_bool)::err(json_error::create(json_error::TYPE_CONVERSION)); 169 | end 170 | endfunction : try_into_bool 171 | 172 | 173 | function json_object json_value::into_object(); 174 | return this.try_into_object().unwrap(); 175 | endfunction : into_object 176 | 177 | 178 | function json_array json_value::into_array(); 179 | return this.try_into_array().unwrap(); 180 | endfunction : into_array 181 | 182 | 183 | function json_string json_value::into_string(); 184 | return this.try_into_string().unwrap(); 185 | endfunction : into_string 186 | 187 | 188 | function json_int json_value::into_int(); 189 | return this.try_into_int().unwrap(); 190 | endfunction : into_int 191 | 192 | 193 | function json_real json_value::into_real(); 194 | return this.try_into_real().unwrap(); 195 | endfunction : into_real 196 | 197 | 198 | function json_bool json_value::into_bool(); 199 | return this.try_into_bool().unwrap(); 200 | endfunction : into_bool 201 | 202 | 203 | function bit json_value::is_object(); 204 | json_object value; 205 | return this.matches_object(value); 206 | endfunction : is_object 207 | 208 | 209 | function bit json_value::is_array(); 210 | json_array value; 211 | return this.matches_array(value); 212 | endfunction : is_array 213 | 214 | 215 | function bit json_value::is_string(); 216 | json_string value; 217 | return this.matches_string(value); 218 | endfunction : is_string 219 | 220 | 221 | function bit json_value::is_int(); 222 | json_int value; 223 | return this.matches_int(value); 224 | endfunction : is_int 225 | 226 | 227 | function bit json_value::is_real(); 228 | json_real value; 229 | return this.matches_real(value); 230 | endfunction : is_real 231 | 232 | 233 | function bit json_value::is_bool(); 234 | json_bool value; 235 | return this.matches_bool(value); 236 | endfunction : is_bool 237 | -------------------------------------------------------------------------------- /src/json_error.sv: -------------------------------------------------------------------------------- 1 | // Generic JSON error 2 | class json_error; 3 | // Width of context for printing JSON errors. 4 | // It controls how long would be part of a JSON string to show the context. 5 | // Why 80? Just a reasonable number for a message in CLI. 6 | static int unsigned ctx_width = 80; 7 | 8 | // All types of JSON related errors 9 | typedef enum { 10 | // JSON syntax errors 11 | EOF_VALUE, 12 | EOF_OBJECT, 13 | EOF_ARRAY, 14 | EOF_STRING, 15 | EOF_LITERAL, 16 | EXPECTED_TOKEN, 17 | EXPECTED_COLON, 18 | EXPECTED_OBJECT_COMMA_OR_END, 19 | EXPECTED_ARRAY_COMMA_OR_END, 20 | EXPECTED_DOUBLE_QUOTE, 21 | EXPECTED_VALUE, 22 | INVALID_ESCAPE, 23 | INVALID_CHAR, 24 | INVALID_LITERAL, 25 | INVALID_NUMBER, 26 | INVALID_OBJECT_KEY, 27 | TRAILING_COMMA, 28 | TRAILING_CHARS, 29 | DEEP_NESTING, 30 | // IO and generic errors 31 | TYPE_CONVERSION, 32 | FILE_NOT_OPENED, 33 | NOT_IMPLEMENTED, 34 | INTERNAL 35 | } kind_e; 36 | 37 | // Error properties 38 | protected string info [kind_e]; 39 | kind_e kind; 40 | string description; 41 | string file; 42 | int line; 43 | string json_str; 44 | int json_pos; 45 | 46 | // Create empty error 47 | extern function new(kind_e kind); 48 | 49 | // Create error 50 | extern static function json_error create( 51 | kind_e kind, 52 | string description="", 53 | string json_str="", 54 | int json_pos=-1, 55 | string source_file="", 56 | int source_line=-1 57 | ); 58 | 59 | // Report error 60 | extern virtual function void throw_error(); 61 | 62 | // Report fatal 63 | extern virtual function void throw_fatal(); 64 | 65 | // Convert error to printable string 66 | extern virtual function string to_string(); 67 | 68 | // Try to extract context for error from provided JSON string 69 | extern protected virtual function string extract_err_context(); 70 | 71 | // Make error context human readable 72 | extern protected virtual function string prettify_err_context(string err_ctx, int err_pos, int err_line_idx); 73 | endclass : json_error 74 | 75 | 76 | function json_error::new(kind_e kind); 77 | this.kind = kind; 78 | // '{ assignment cannot be used due to Verilator 79 | this.info[EOF_OBJECT] = "EOF while parsing an object"; 80 | this.info[EOF_ARRAY] = "EOF while parsing an array"; 81 | this.info[EOF_STRING] = "EOF while parsing a string"; 82 | this.info[EOF_LITERAL] = "EOF while parsing a literal"; 83 | this.info[EXPECTED_TOKEN] = "Current character should be some expected token"; 84 | this.info[EXPECTED_COLON] = "Current character should be ':'"; 85 | this.info[EXPECTED_OBJECT_COMMA_OR_END] = "Current character should be either ',' or '}'"; 86 | this.info[EXPECTED_ARRAY_COMMA_OR_END] = "Current character should be either ',' or ']'"; 87 | this.info[EXPECTED_DOUBLE_QUOTE] = "Current character should be '\"'"; 88 | this.info[EXPECTED_VALUE] = "Current character should start some JSON value"; 89 | this.info[INVALID_ESCAPE] = "Invaid escape code"; 90 | this.info[INVALID_CHAR] = "Unexpected control character"; 91 | this.info[INVALID_LITERAL] = "Invaid literal that should be 'true', 'false', or 'null'"; 92 | this.info[INVALID_NUMBER] = "Invaid number"; 93 | this.info[INVALID_OBJECT_KEY] = "String must be used as a key"; 94 | this.info[TRAILING_COMMA] = "Unexpected comma after the last value"; 95 | this.info[TRAILING_CHARS] = "Unexpected characters after the JSON value"; 96 | this.info[DEEP_NESTING] = "This JSON value exceeds nesing limit for a decoder"; 97 | this.info[TYPE_CONVERSION] = "Type conversion failed"; 98 | this.info[FILE_NOT_OPENED] = "File opening failed"; 99 | this.info[NOT_IMPLEMENTED] = "Feature is not implemented"; 100 | this.info[INTERNAL] = "Unspecified internal error"; 101 | endfunction : new 102 | 103 | 104 | function json_error json_error::create( 105 | kind_e kind, 106 | string description="", 107 | string json_str="", 108 | int json_pos=-1, 109 | string source_file="", 110 | int source_line=-1 111 | ); 112 | json_error err = new(kind); 113 | err.description = description; 114 | err.json_str = json_str; 115 | err.json_pos = json_pos; 116 | err.file = source_file; 117 | err.line = source_line; 118 | return err; 119 | endfunction : create 120 | 121 | 122 | function void json_error::throw_error(); 123 | $error(this.to_string()); 124 | endfunction : throw_error 125 | 126 | 127 | function void json_error::throw_fatal(); 128 | $fatal(0, this.to_string()); 129 | endfunction : throw_fatal 130 | 131 | 132 | function string json_error::to_string(); 133 | string str = "JSON error"; 134 | 135 | // Add information about file and line where error was raised if this was provided 136 | if (this.file != "") begin 137 | str = {str, $sformatf(" raised from %s", this.file)}; 138 | if (this.line >= 0) begin 139 | str = {str, $sformatf(":%0d", this.line)}; 140 | end 141 | end 142 | 143 | // Add error kind and base information 144 | str = {str, $sformatf(":\n%s: %s", this.kind.name(), this.info[this.kind])}; 145 | 146 | // Add custom description if exists 147 | if (this.description != "") begin 148 | str = {str, $sformatf("\n%s", this.description)}; 149 | end 150 | 151 | // Provide context from JSON error if context was provided 152 | if ((this.json_pos >= 0) && (this.json_str.len() > 0) && (this.json_pos < this.json_str.len())) begin 153 | string err_ctx = this.extract_err_context(); 154 | str = {str, {"\n", err_ctx}}; 155 | end 156 | 157 | return str; 158 | endfunction : to_string 159 | 160 | 161 | function string json_error::extract_err_context(); 162 | int ctx_start_idx = 0; 163 | int ctx_end_idx = this.json_str.len() - 1; 164 | 165 | int err_pos; 166 | int err_line_idx; 167 | string err_ctx; 168 | 169 | // Locate line with error 170 | foreach (json_str[i]) begin 171 | if (json_str[i] == "\n") begin 172 | ctx_end_idx = i; 173 | if (ctx_end_idx >= this.json_pos) begin 174 | break; 175 | end 176 | ctx_start_idx = i + 1; 177 | err_line_idx++; 178 | end 179 | end 180 | 181 | // Extract this line 182 | err_ctx = this.json_str.substr(ctx_start_idx, ctx_end_idx); 183 | err_pos = this.json_pos - ctx_start_idx; 184 | 185 | // Make the error context more human readable 186 | return prettify_err_context(err_ctx, err_pos, err_line_idx); 187 | endfunction : extract_err_context 188 | 189 | 190 | function string json_error::prettify_err_context(string err_ctx, int err_pos, int err_line_idx); 191 | int ctx_width_half = ctx_width / 2 - 1; 192 | string pretty_ctx; 193 | int pretty_start_idx; 194 | int pretty_end_idx; 195 | string pointer_offset; 196 | 197 | // Cut off line to fit context width window 198 | pretty_start_idx = (err_pos - ctx_width_half) < 0 ? 0 : err_pos - ctx_width_half; 199 | pretty_end_idx = (err_ctx.len() - pretty_start_idx - 1) < 78 ? err_ctx.len() - 1 : 200 | pretty_start_idx + ctx_width_half * 2; 201 | if (err_ctx[pretty_end_idx] == "\n") begin 202 | pretty_end_idx--; // there might be a single newline character that has to be handled 203 | end 204 | 205 | // Prepare final context line 206 | pretty_ctx = err_ctx.substr(pretty_start_idx, pretty_end_idx); 207 | if (pretty_start_idx > 0) begin 208 | for (int i = 0; i < 3; i++) begin 209 | pretty_ctx[i] = "."; // just to show that error is far away from line start 210 | end 211 | end 212 | if (pretty_end_idx < (err_ctx.len() - 2)) begin 213 | for (int i = pretty_ctx.len() - 3; i < pretty_ctx.len(); i++) begin 214 | pretty_ctx[i] = "."; // just to show that error is far away from line end 215 | end 216 | end 217 | 218 | // Prepare offset for error pointer 219 | repeat(err_pos - pretty_start_idx) begin 220 | pointer_offset = {pointer_offset, " "}; 221 | end 222 | 223 | return $sformatf( 224 | "JSON string line %0d symbol %0d:\n%s\n%s\n%s", 225 | err_line_idx + 1, // lines and symbols in text are usually counted from 1 by a normal human 226 | err_pos + 1, 227 | pretty_ctx, 228 | {pointer_offset, "^"}, 229 | {pointer_offset, "|"} 230 | ); 231 | endfunction : prettify_err_context 232 | -------------------------------------------------------------------------------- /src/json_encoder.sv: -------------------------------------------------------------------------------- 1 | // JSON encoder 2 | class json_encoder; 3 | localparam byte CR = 8'd13; // 13=\r=CR - not in SV standard 4 | 5 | //---------------------------------------------------------------------------- 6 | // Public methods 7 | //---------------------------------------------------------------------------- 8 | // Encode provided JSON value to string 9 | extern static function json_result#(string) dump_string( 10 | json_value_encodable obj, 11 | int unsigned indent_spaces = 0 12 | ); 13 | 14 | // Encode provided JSON value to string and try to dump to file. 15 | // Encoded string is returned. 16 | extern static function json_result#(string) dump_file( 17 | json_value_encodable obj, 18 | string path, 19 | int unsigned indent_spaces = 0 20 | ); 21 | 22 | //---------------------------------------------------------------------------- 23 | // Private properties 24 | //---------------------------------------------------------------------------- 25 | protected int unsigned indent_spaces; 26 | 27 | //---------------------------------------------------------------------------- 28 | // Private methods 29 | //---------------------------------------------------------------------------- 30 | // Private constructor 31 | extern local function new(); 32 | 33 | // Encode generic JSON value 34 | extern protected function json_result#(string) convert_value(json_value_encodable obj, int unsigned nesting_lvl); 35 | 36 | // Encode JSON object 37 | extern protected function json_result#(string) convert_object(json_object_encodable obj, int unsigned nesting_lvl); 38 | 39 | // Encode JSON array 40 | extern protected function json_result#(string) convert_array(json_array_encodable obj, int unsigned nesting_lvl); 41 | 42 | // Encode JSON string 43 | extern protected function json_result#(string) convert_string(json_string_encodable obj); 44 | 45 | // Encode JSON number (int) 46 | extern protected function json_result#(string) convert_int(json_int_encodable obj); 47 | 48 | // Encode JSON number (real) 49 | extern protected function json_result#(string) convert_real(json_real_encodable obj); 50 | 51 | // Encode JSON nool 52 | extern protected function json_result#(string) convert_bool(json_bool_encodable obj); 53 | 54 | // Convert indentation level to string of spaces 55 | extern protected function string level_to_spaces(int unsigned lvl); 56 | 57 | // Check if compact mode is enabled 58 | extern protected function bit is_compact(); 59 | endclass : json_encoder 60 | 61 | 62 | function json_encoder::new(); 63 | endfunction : new 64 | 65 | 66 | function json_result#(string) json_encoder::dump_string( 67 | json_value_encodable obj, 68 | int unsigned indent_spaces = 0 69 | ); 70 | json_encoder encoder = new(); 71 | 72 | encoder.indent_spaces = indent_spaces; 73 | 74 | return encoder.convert_value(obj, 0); 75 | endfunction : dump_string 76 | 77 | 78 | function json_result#(string) json_encoder::dump_file( 79 | json_value_encodable obj, 80 | string path, 81 | int unsigned indent_spaces = 0 82 | ); 83 | json_result#(string) dump; 84 | json_error err; 85 | string encoded; 86 | 87 | dump = dump_string(obj, .indent_spaces(indent_spaces)); 88 | case(1) 89 | dump.matches_err(err): return dump; 90 | 91 | dump.matches_ok(encoded): begin 92 | int file_descr = $fopen(path, "w"); 93 | 94 | if (file_descr == 0) begin 95 | return `JSON_ERR(json_error::FILE_NOT_OPENED, $sformatf("Failed to open the file '%s'!", path), string); 96 | end 97 | 98 | $fwrite(file_descr, encoded); 99 | $fclose(file_descr); 100 | 101 | return dump; 102 | end 103 | endcase 104 | endfunction : dump_file 105 | 106 | 107 | function json_result#(string) json_encoder::convert_value(json_value_encodable obj, int unsigned nesting_lvl); 108 | json_object_encodable jobject; 109 | json_array_encodable jarray; 110 | json_string_encodable jstring; 111 | json_int_encodable jint; 112 | json_real_encodable jreal; 113 | json_bool_encodable jbool; 114 | 115 | case(1) 116 | obj == null: return json_result#(string)::ok("null"); 117 | $cast(jobject, obj): return convert_object(jobject, nesting_lvl); 118 | $cast(jarray, obj): return convert_array(jarray, nesting_lvl); 119 | $cast(jstring, obj): return convert_string(jstring); 120 | $cast(jint, obj): return convert_int(jint); 121 | $cast(jreal, obj): return convert_real(jreal); 122 | $cast(jbool, obj): return convert_bool(jbool); 123 | default: return `JSON_ERR( 124 | json_error::TYPE_CONVERSION, 125 | $sformatf("Provided object has unsupported JSON encodable interface implemented!"), 126 | string 127 | ); 128 | endcase 129 | endfunction : convert_value 130 | 131 | 132 | function json_result#(string) json_encoder::convert_object(json_object_encodable obj, int unsigned nesting_lvl); 133 | string converted = {"{", this.is_compact() ? "" : "\n"}; 134 | json_object_encodable::values_t values = obj.to_json_encodable(); 135 | string last_key; 136 | 137 | void'(values.last(last_key)); 138 | foreach(values[key]) begin 139 | json_error err; 140 | string ok; 141 | json_result#(string) nested_conv = convert_value(values[key], nesting_lvl + 1); 142 | 143 | case(1) 144 | nested_conv.matches_err(err): return nested_conv; 145 | 146 | nested_conv.matches_ok(ok): begin 147 | string encoded_key = convert_string(json_string::from(key)).unwrap(); 148 | converted = {converted, level_to_spaces(nesting_lvl + 1), encoded_key, ":", this.is_compact() ? "" : " ", ok}; 149 | if (key != last_key) begin 150 | converted = {converted, ","}; 151 | end 152 | converted = {converted, this.is_compact() ? "" : "\n"}; 153 | end 154 | endcase 155 | 156 | end 157 | converted = {converted, level_to_spaces(nesting_lvl), "}"}; 158 | 159 | return json_result#(string)::ok(converted); 160 | endfunction : convert_object 161 | 162 | 163 | function json_result#(string) json_encoder::convert_array(json_array_encodable obj, int unsigned nesting_lvl); 164 | string converted = {"[", this.is_compact() ? "" : "\n"}; 165 | json_array_encodable::values_t values = obj.to_json_encodable(); 166 | int unsigned values_num = values.size(); 167 | 168 | foreach(values[i]) begin 169 | json_error err; 170 | string ok; 171 | json_result#(string) nested_conv = convert_value(values[i], nesting_lvl + 1); 172 | 173 | case(1) 174 | nested_conv.matches_err(err): return nested_conv; 175 | 176 | nested_conv.matches_ok(ok): begin 177 | converted = {converted, level_to_spaces(nesting_lvl + 1), ok}; 178 | if (i < (values_num - 1)) begin 179 | converted = {converted, ","}; 180 | end 181 | converted = {converted, this.is_compact() ? "" : "\n"}; 182 | end 183 | endcase 184 | 185 | end 186 | converted = {converted, level_to_spaces(nesting_lvl), "]"}; 187 | 188 | return json_result#(string)::ok(converted); 189 | endfunction : convert_array 190 | 191 | 192 | function json_result#(string) json_encoder::convert_string(json_string_encodable obj); 193 | string orig = obj.to_json_encodable(); 194 | string converted = "\""; 195 | 196 | foreach (orig[i]) begin 197 | string sym; 198 | case (orig[i]) 199 | "\"" : sym = "\\\""; 200 | "\\" : sym = "\\\\"; 201 | "\f" : sym = "\\f"; 202 | "\n" : sym = "\\n"; 203 | CR : sym = "\\r"; 204 | "\t" : sym = "\\t"; 205 | default: sym = string'(orig[i]); 206 | endcase 207 | converted = {converted, sym}; 208 | end 209 | converted = {converted, "\""}; 210 | 211 | return json_result#(string)::ok(converted); 212 | endfunction : convert_string 213 | 214 | 215 | function json_result#(string) json_encoder::convert_int(json_int_encodable obj); 216 | return json_result#(string)::ok($sformatf("%0d", obj.to_json_encodable())); 217 | endfunction : convert_int 218 | 219 | 220 | function json_result#(string) json_encoder::convert_real(json_real_encodable obj); 221 | return json_result#(string)::ok($sformatf("%0f", obj.to_json_encodable())); 222 | endfunction : convert_real 223 | 224 | 225 | function json_result#(string) json_encoder::convert_bool(json_bool_encodable obj); 226 | return json_result#(string)::ok(obj.to_json_encodable() ? "true" : "false"); 227 | endfunction : convert_bool 228 | 229 | 230 | function string json_encoder::level_to_spaces(int unsigned lvl); 231 | string spaces = ""; 232 | repeat(lvl * this.indent_spaces) begin 233 | spaces = {spaces, " "}; 234 | end 235 | return spaces; 236 | endfunction : level_to_spaces 237 | 238 | 239 | function bit json_encoder::is_compact(); 240 | return this.indent_spaces == 0; 241 | endfunction : is_compact 242 | -------------------------------------------------------------------------------- /tests/json_load_ok_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Basic tests of `json_decoder` is used to parse JSON succesfuly 5 | module json_load_ok_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_load_ok_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | `SVUNIT_TESTS_BEGIN 26 | 27 | 28 | `SVTEST(null_test) begin 29 | `EXPECT_OK_LOAD_STR("null", null) 30 | `EXPECT_OK_LOAD_STR(" null ", null) 31 | `EXPECT_OK_LOAD_STR(" \nnull\n \t ", null) 32 | end `SVTEST_END 33 | 34 | 35 | `SVTEST(bool_test) begin 36 | `EXPECT_OK_LOAD_STR("true", json_bool::from(1)) 37 | `EXPECT_OK_LOAD_STR("false", json_bool::from(0)) 38 | `EXPECT_OK_LOAD_STR(" true ", json_bool::from(1)) 39 | `EXPECT_OK_LOAD_STR(" false ", json_bool::from(0)) 40 | `EXPECT_OK_LOAD_STR(" \n\n\t true\n \n ", json_bool::from(1)) 41 | `EXPECT_OK_LOAD_STR(" \n false\n \t", json_bool::from(0)) 42 | end `SVTEST_END 43 | 44 | 45 | `SVTEST(int_test) begin 46 | `EXPECT_OK_LOAD_STR("42", json_int::from(42)) 47 | `EXPECT_OK_LOAD_STR("0", json_int::from(0)) 48 | `EXPECT_OK_LOAD_STR("-1", json_int::from(-1)) 49 | `EXPECT_OK_LOAD_STR("137438953472", json_int::from(64'd137438953472)) 50 | `EXPECT_OK_LOAD_STR("-213787953472", json_int::from(-64'd213787953472)) 51 | `EXPECT_OK_LOAD_STR(" 123213 ", json_int::from(123213)) 52 | `EXPECT_OK_LOAD_STR(" \n-123213\n\n\t ", json_int::from(-123213)) 53 | end `SVTEST_END 54 | 55 | 56 | `SVTEST(real_test) begin 57 | `EXPECT_OK_LOAD_STR("3.14", json_real::from(3.14)) 58 | `EXPECT_OK_LOAD_STR("0.02132", json_real::from(0.02132)) 59 | `EXPECT_OK_LOAD_STR("-0.6", json_real::from(-0.6)) 60 | `EXPECT_OK_LOAD_STR("-10002.32", json_real::from(-10002.32)) 61 | `EXPECT_OK_LOAD_STR("28.8299", json_real::from(28.8299)) 62 | `EXPECT_OK_LOAD_STR("42e3", json_real::from(42e3)) 63 | `EXPECT_OK_LOAD_STR("15e-6", json_real::from(15e-6)) 64 | `EXPECT_OK_LOAD_STR("0.3E3", json_real::from(0.3E3)) 65 | `EXPECT_OK_LOAD_STR("-0.001E-4", json_real::from(-0.001E-4)) 66 | `EXPECT_OK_LOAD_STR(" 0.1234 ", json_real::from(0.1234)) 67 | `EXPECT_OK_LOAD_STR(replace_cr(" \n\t\\r-10.875\n \n "), json_real::from(-10.875)) 68 | end `SVTEST_END 69 | 70 | 71 | `SVTEST(string_test) begin 72 | `EXPECT_OK_LOAD_STR("\"\"", json_string::from("")) 73 | `EXPECT_OK_LOAD_STR("\" \"", json_string::from(" ")) 74 | `EXPECT_OK_LOAD_STR("\"\\n\"", json_string::from("\n")) 75 | `EXPECT_OK_LOAD_STR("\"a\"", json_string::from("a")) 76 | `EXPECT_OK_LOAD_STR("\"abc [klm] {xyz}\"", json_string::from("abc [klm] {xyz}")) 77 | `EXPECT_OK_LOAD_STR("\"true, false, null\"", json_string::from("true, false, null")) 78 | `EXPECT_OK_LOAD_STR("\"1234abcABC!@#$^&*\"", json_string::from("1234abcABC!@#$^&*")) 79 | `EXPECT_OK_LOAD_STR(" \n\n\t \"hello\"\n \n ", json_string::from("hello")) 80 | end `SVTEST_END 81 | 82 | 83 | `SVTEST(string_escapes_test) begin 84 | `EXPECT_OK_LOAD_STR("\" \\n \"", json_string::from(" \n ")) 85 | `EXPECT_OK_LOAD_STR("\" \\t \"", json_string::from(" \t ")) 86 | `EXPECT_OK_LOAD_STR("\" \\r \"", json_string::from(replace_cr(" \\r "))) 87 | `EXPECT_OK_LOAD_STR("\" \\f \"", json_string::from(" \f ")) 88 | `EXPECT_OK_LOAD_STR("\" \\\\ \"", json_string::from(" \\ ")) 89 | `EXPECT_OK_LOAD_STR("\" \\\" \"", json_string::from(" \" ")) 90 | `EXPECT_OK_LOAD_STR("\" \\/ \"", json_string::from(" / ")) 91 | `EXPECT_OK_LOAD_STR("\"\\n \\t \\r \\f \\\\ \\/ \\\"\"", json_string::from(replace_cr("\n \t \\r \f \\ / \""))) 92 | `EXPECT_OK_LOAD_STR("\" \\u1234\\uabcd \\b \"", json_string::from(" \\u1234\\uabcd \\b ")) 93 | end `SVTEST_END 94 | 95 | 96 | `SVTEST(empty_object_test) begin 97 | json_object golden = json_object::from(empty_jvalue_map); 98 | `EXPECT_OK_LOAD_STR("{}", golden) 99 | `EXPECT_OK_LOAD_STR("{ }", golden) 100 | `EXPECT_OK_LOAD_STR("{\n}", golden) 101 | `EXPECT_OK_LOAD_STR("{\t}", golden) 102 | `EXPECT_OK_LOAD_STR("{ \n \n \n }", golden) 103 | `EXPECT_OK_LOAD_STR(" {} \n\n", golden) 104 | `EXPECT_OK_LOAD_STR("\n\n{\n}\n\n", golden) 105 | `EXPECT_OK_LOAD_STR("\n { } \n", golden) 106 | end `SVTEST_END 107 | 108 | 109 | `SVTEST(simple_object_test) begin 110 | json_object golden = json_object::from('{"the_answer": json_int::from(42)}); 111 | `EXPECT_OK_LOAD_STR("{\"the_answer\":42}", golden) 112 | `EXPECT_OK_LOAD_STR(" {\n \"the_answer\": 42\n}\n\n ", golden) 113 | `EXPECT_OK_LOAD_STR(" {\n \"the_answer\": 42 \n} \n\n ", golden) 114 | `EXPECT_OK_LOAD_STR(" {\n \"the_answer\" : \t\n42\n}\n\n ", golden) 115 | `EXPECT_OK_LOAD_STR("\n{\n\"the_answer\"\n:\n42\n}\n", golden) 116 | end `SVTEST_END 117 | 118 | 119 | `SVTEST(normal_object_test) begin 120 | json_object golden = json_object::from('{ 121 | "jint": json_int::from(42), 122 | "jreal": json_real::from(3.14), 123 | "jstring": json_string::from("XyZ"), 124 | "jbool": json_bool::from(1), 125 | "jnull": null 126 | }); 127 | `EXPECT_OK_LOAD_STR("{\"jint\":42,\"jreal\":3.14,\"jstring\":\"XyZ\",\"jbool\":true,\"jnull\":null}", golden) 128 | `EXPECT_OK_LOAD_STR( 129 | "{\"jint\":42\n, \"jreal\" : 3.14\t, \"jstring\" : \"XyZ\"\n,\n \"jbool\" :true, \"jnull\": null}", 130 | golden 131 | ) 132 | end `SVTEST_END 133 | 134 | 135 | `SVTEST(nested_object_test) begin 136 | json_object golden = json_object::from('{ 137 | "jbool": json_bool::from(1), 138 | "jobj1": json_object::from('{ 139 | "jobj2": json_object::from(empty_jvalue_map), 140 | "jnull": null 141 | }) 142 | }); 143 | `EXPECT_OK_LOAD_STR("{\"jbool\":true,\"jobj1\":{\"jobj2\":{},\"jnull\":null}}", golden) 144 | `EXPECT_OK_LOAD_STR("{\"jbool\":true , \"jobj1\" :\n{\n \"jobj2\" : { }\n , \"jnull\" : null}}", golden) 145 | end `SVTEST_END 146 | 147 | 148 | `SVTEST(empty_array_test) begin 149 | json_array golden = json_array::from('{}); 150 | `EXPECT_OK_LOAD_STR("[]", golden) 151 | `EXPECT_OK_LOAD_STR("[ ]", golden) 152 | `EXPECT_OK_LOAD_STR("[\n]", golden) 153 | `EXPECT_OK_LOAD_STR("[\t]", golden) 154 | `EXPECT_OK_LOAD_STR("[ \n \n \n ]", golden) 155 | `EXPECT_OK_LOAD_STR(" [] \n\n", golden) 156 | `EXPECT_OK_LOAD_STR("\n\n[\n]\n\n", golden) 157 | `EXPECT_OK_LOAD_STR(replace_cr("\n\\r[ ] \n"), golden) 158 | end `SVTEST_END 159 | 160 | 161 | `SVTEST(simple_array_test) begin 162 | json_array golden = json_array::from('{json_int::from(777)}); 163 | `EXPECT_OK_LOAD_STR("[777]", golden) 164 | `EXPECT_OK_LOAD_STR(" [ 777 ] ", golden) 165 | `EXPECT_OK_LOAD_STR(" \n[ 777 ]\n ", golden) 166 | `EXPECT_OK_LOAD_STR("\n[\n777\n]\n", golden) 167 | end `SVTEST_END 168 | 169 | 170 | `SVTEST(normal_array_test) begin 171 | json_array golden = json_array::from('{ 172 | json_int::from(42), 173 | json_real::from(3.14), 174 | json_string::from("XyZ"), 175 | json_bool::from(1), 176 | null 177 | }); 178 | `EXPECT_OK_LOAD_STR("[42,3.14,\"XyZ\",true,null]", golden) 179 | `EXPECT_OK_LOAD_STR(" [ 42 , 3.14 , \"XyZ\" , true , null ] ", golden) 180 | `EXPECT_OK_LOAD_STR("\n[\n42,\n3.14,\n\"XyZ\",\ntrue,\nnull\n]\n", golden) 181 | end `SVTEST_END 182 | 183 | 184 | `SVTEST(nested_array_test) begin 185 | json_array golden = json_array::from('{ 186 | json_bool::from(0), 187 | json_array::from('{ 188 | null, 189 | json_array::from('{}), 190 | json_int::from(5) 191 | }) 192 | }); 193 | `EXPECT_OK_LOAD_STR("[false,[null,[],5]]", golden) 194 | `EXPECT_OK_LOAD_STR(" [ false , [ null , [ ] , 5 ] ]", golden) 195 | `EXPECT_OK_LOAD_STR("\t [ false\n, [\t null,\n [\n\n] , \n5\t ] \n]\n\n", golden) 196 | end `SVTEST_END 197 | 198 | 199 | `SVTEST(mixed_test) begin 200 | `EXPECT_OK_LOAD_STR( 201 | "[[1, 2, 3], { \"key\": \"value\" }]", 202 | json_array::from('{ 203 | json_array::from('{ 204 | json_int::from(1), 205 | json_int::from(2), 206 | json_int::from(3) 207 | }), 208 | json_object::from('{ 209 | "key": json_string::from("value") 210 | }) 211 | }) 212 | ) 213 | 214 | `EXPECT_OK_LOAD_STR( 215 | "{\"arr\":[null, true], \"obj\": { \"key\": \"value\" }}", 216 | json_object::from('{ 217 | "arr": json_array::from('{ 218 | null, 219 | json_bool::from(1) 220 | }), 221 | "obj": json_object::from('{ 222 | "key": json_string::from("value") 223 | }) 224 | }) 225 | ) 226 | end `SVTEST_END 227 | 228 | 229 | `SVUNIT_TESTS_END 230 | 231 | endmodule : json_load_ok_unit_test 232 | -------------------------------------------------------------------------------- /tests/json_array_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_array` 5 | module json_array_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_array_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | `SVUNIT_TESTS_BEGIN 26 | 27 | // Create json_array using constructor 28 | `SVTEST(create_new_test) begin 29 | json_array jarray; 30 | jarray = new('{json_bool::from(0)}); 31 | `FAIL_UNLESS(jarray.get(0).into_bool().get() == 0) 32 | end `SVTEST_END 33 | 34 | 35 | // Create json_array using static method 36 | `SVTEST(create_static_test) begin 37 | json_array jarray; 38 | jarray = json_array::from('{json_bool::from(0)}); 39 | `FAIL_UNLESS(jarray.get(0).into_bool().get() == 0) 40 | end `SVTEST_END 41 | 42 | 43 | // Clone json_array instance 44 | `SVTEST(clone_array_test) begin 45 | json_array orig = json_array::from('{json_bool::from(0), json_int::from(-13)}); 46 | json_array clone = orig.clone().into_array(); 47 | `FAIL_IF(orig == clone) 48 | `FAIL_UNLESS(clone.size() == 2) 49 | `FAIL_UNLESS(clone.get(0).into_bool().get() == 0) 50 | `FAIL_UNLESS(clone.get(1).into_int().get() == -13) 51 | end `SVTEST_END 52 | 53 | 54 | // Clone empty array 55 | `SVTEST(clone_empty_array_test) begin 56 | json_array orig = json_array::from('{}); 57 | json_array clone = orig.clone().into_array(); 58 | `FAIL_IF(orig == clone) 59 | `FAIL_UNLESS(clone.size() == 0) 60 | end `SVTEST_END 61 | 62 | 63 | // Clone array, where some items are nulls 64 | `SVTEST(clone_array_with_nulls_test) begin 65 | string str = "foo"; 66 | json_array orig = json_array::from('{null, json_string::from(str), null}); 67 | json_array clone = orig.clone().into_array(); 68 | `FAIL_IF(orig == clone) 69 | `FAIL_UNLESS(clone.size() == 3) 70 | `FAIL_UNLESS(clone.get(0) == null) 71 | `FAIL_UNLESS(clone.get(1).into_string().get() == str) 72 | `FAIL_UNLESS(clone.get(2) == null) 73 | end `SVTEST_END 74 | 75 | 76 | // Clone complex structure array 77 | `SVTEST(clone_array_complex_test) begin 78 | string str = "bar"; 79 | json_array orig = json_array::from('{ 80 | json_real::from(0.008), 81 | json_array::from('{json_int::from(0), json_array::from('{})}), 82 | json_object::from('{"bar": json_bool::from(1)}) 83 | }); 84 | json_array clone = orig.clone().into_array(); 85 | `FAIL_IF(orig == clone) 86 | `FAIL_UNLESS(clone.size() == 3) 87 | `FAIL_UNLESS(clone.get(0).into_real().get() == 0.008) 88 | `FAIL_UNLESS(clone.get(1).is_array()) 89 | `FAIL_UNLESS(clone.get(1).into_array().size() == 2) 90 | `FAIL_UNLESS(clone.get(1).into_array().get(0).into_int().get() == 0) 91 | `FAIL_UNLESS(clone.get(1).into_array().get(1).into_array().size() == 0) 92 | `FAIL_UNLESS(clone.get(2).is_object()) 93 | `FAIL_UNLESS(clone.get(2).into_object().size() == 1) 94 | `FAIL_UNLESS(clone.get(2).into_object().get(str).into_bool().get() == 1) 95 | end `SVTEST_END 96 | 97 | 98 | // Compare json_array instances 99 | `SVTEST(compare_array_test) begin 100 | json_array jarray_a = json_array::from('{json_int::from(42), json_string::from("verilog")}); 101 | json_array jarray_b = jarray_a.clone().into_array(); 102 | // OK 103 | `FAIL_UNLESS(jarray_a.compare(jarray_a)) 104 | `FAIL_UNLESS(jarray_a.compare(jarray_b)) 105 | jarray_a.push_back(json_bool::from(0)); 106 | jarray_b.push_back(json_bool::from(0)); 107 | `FAIL_UNLESS(jarray_a.compare(jarray_b)) 108 | // Fail 109 | jarray_a = json_array::from('{json_int::from(41)}); 110 | jarray_b = json_array::from('{json_int::from(43)}); 111 | `FAIL_IF(jarray_a.compare(jarray_b)) 112 | jarray_a = json_array::from('{json_int::from(41)}); 113 | jarray_b = json_array::from('{json_int::from(41), json_int::from(42)}); 114 | `FAIL_IF(jarray_a.compare(jarray_b)) 115 | jarray_a = json_array::from('{json_int::from(41), json_int::from(42)}); 116 | jarray_b = json_array::from('{json_int::from(41)}); 117 | `FAIL_IF(jarray_a.compare(jarray_b)) 118 | jarray_a = json_array::from('{json_int::from(41)}); 119 | jarray_b = json_array::from('{}); 120 | `FAIL_IF(jarray_a.compare(jarray_b)) 121 | jarray_a = json_array::from('{}); 122 | jarray_b = json_array::from('{json_int::from(42)}); 123 | `FAIL_IF(jarray_a.compare(jarray_b)) 124 | jarray_a = json_array::from('{json_int::from(42)}); 125 | jarray_b = json_array::from('{null}); 126 | `FAIL_IF(jarray_a.compare(jarray_b)) 127 | jarray_a = json_array::from('{null}); 128 | jarray_b = json_array::from('{json_int::from(42)}); 129 | `FAIL_IF(jarray_a.compare(jarray_b)) 130 | end `SVTEST_END 131 | 132 | 133 | // Compare nested arrays 134 | `SVTEST(compare_array_nested_test) begin 135 | json_array jarray_a = json_array::from('{ 136 | json_int::from(42), 137 | json_object::from(empty_jvalue_map), 138 | json_object::from('{ 139 | "bar": json_string::from("hello"), 140 | "baz": json_array::from('{null, json_int::from(0)}) 141 | }) 142 | }); 143 | json_array jarray_b = jarray_a.clone().into_array(); 144 | 145 | // OK 146 | `FAIL_UNLESS(jarray_a.compare(jarray_b)) 147 | 148 | // Fail 149 | jarray_b.set(1, null); 150 | `FAIL_IF(jarray_a.compare(jarray_b)) 151 | jarray_b = jarray_a.clone().into_array(); 152 | 153 | jarray_b.get(2).into_object().set("baz", json_int::from(42)); 154 | `FAIL_IF(jarray_a.compare(jarray_b)) 155 | end `SVTEST_END 156 | 157 | 158 | // Compare json_array with other JSON values 159 | `SVTEST(compare_array_with_others_test) begin 160 | json_string jstring = json_string::from("1"); 161 | json_real jreal = json_real::from(1.0); 162 | json_int jint = json_int::from(1); 163 | json_bool jbool = json_bool::from(1); 164 | json_array jarray = json_array::from('{json_int::from(1)}); 165 | json_object jobject = json_object::from('{"int": json_int::from(1)}); 166 | // Comparsion is strict 167 | `FAIL_IF(jarray.compare(jreal)) 168 | `FAIL_IF(jarray.compare(jint)) 169 | `FAIL_IF(jarray.compare(jbool)) 170 | `FAIL_IF(jarray.compare(jobject)) 171 | `FAIL_IF(jarray.compare(jstring)) 172 | `FAIL_IF(jarray.compare(null)) 173 | end `SVTEST_END 174 | 175 | 176 | // Access internal values by index 177 | `SVTEST(getter_setter_test) begin 178 | json_int jint0 = json_int::from(42); 179 | json_int jint1 = json_int::from(-113); 180 | json_int jint2 = json_int::from(512); 181 | json_array jarray = json_array::from('{jint0, jint1}); 182 | `FAIL_UNLESS(jarray.get(0).into_int() == jint0) 183 | `FAIL_UNLESS(jarray.get(1).into_int() == jint1) 184 | jarray.set(0, jint2); 185 | `FAIL_UNLESS(jarray.get(0).into_int() == jint2) 186 | jarray.set(1, null); 187 | `FAIL_UNLESS(jarray.get(1) == null) 188 | end `SVTEST_END 189 | 190 | 191 | // Test json_array_encodable interface 192 | `SVTEST(encodable_test) begin 193 | json_array::values_t values = '{json_int::from(42), json_int::from(-113)}; 194 | json_array_encodable::values_t enc_values; 195 | json_array jarray = json_array::from(values); 196 | 197 | enc_values = jarray.to_json_encodable(); 198 | `FAIL_UNLESS(enc_values.size() == 2) 199 | `FAIL_UNLESS(enc_values[0] == values[0]) 200 | `FAIL_UNLESS(enc_values[1] == values[1]) 201 | end `SVTEST_END 202 | 203 | 204 | // Test is_* methods 205 | `SVTEST(is_something_test) begin 206 | json_array jarray = json_array::from('{}); 207 | json_value jvalue = jarray; 208 | 209 | `FAIL_IF(jvalue.is_object()) 210 | `FAIL_UNLESS(jvalue.is_array()) 211 | `FAIL_IF(jvalue.is_string()) 212 | `FAIL_IF(jvalue.is_int()) 213 | `FAIL_IF(jvalue.is_real()) 214 | `FAIL_IF(jvalue.is_bool()) 215 | end `SVTEST_END 216 | 217 | 218 | // Test match_* methods 219 | `SVTEST(matches_something_test) begin 220 | json_object jobject; 221 | json_array jarray; 222 | json_string jstring; 223 | json_int jint; 224 | json_real jreal; 225 | json_bool jbool; 226 | 227 | json_array orig_jarray = json_array::from('{}); 228 | json_value jvalue = orig_jarray; 229 | 230 | `FAIL_IF(jvalue.matches_object(jobject)) 231 | `FAIL_UNLESS(jvalue.matches_array(jarray)) 232 | `FAIL_IF(jvalue.matches_string(jstring)) 233 | `FAIL_IF(jvalue.matches_int(jint)) 234 | `FAIL_IF(jvalue.matches_real(jreal)) 235 | `FAIL_IF(jvalue.matches_bool(jbool)) 236 | 237 | `FAIL_UNLESS(jarray == orig_jarray) 238 | end `SVTEST_END 239 | 240 | 241 | // Test try_into_* methods 242 | `SVTEST(try_into_something_test) begin 243 | json_result#(json_object) res_jobject; 244 | json_result#(json_array) res_jarray; 245 | json_result#(json_string) res_jstring; 246 | json_result#(json_int) res_jint; 247 | json_result#(json_real) res_jreal; 248 | json_result#(json_bool) res_jbool; 249 | 250 | json_array orig_jarray = json_array::from('{}); 251 | json_value jvalue = orig_jarray; 252 | 253 | res_jobject = jvalue.try_into_object(); 254 | res_jarray = jvalue.try_into_array(); 255 | res_jstring = jvalue.try_into_string(); 256 | res_jint = jvalue.try_into_int(); 257 | res_jreal = jvalue.try_into_real(); 258 | res_jbool = jvalue.try_into_bool(); 259 | 260 | `FAIL_IF(res_jobject.is_ok()) 261 | `FAIL_UNLESS(res_jarray.is_ok()) 262 | `FAIL_IF(res_jstring.is_ok()) 263 | `FAIL_IF(res_jint.is_ok()) 264 | `FAIL_IF(res_jreal.is_ok()) 265 | `FAIL_IF(res_jbool.is_ok()) 266 | 267 | `FAIL_UNLESS(res_jarray.unwrap() == orig_jarray) 268 | end `SVTEST_END 269 | 270 | 271 | // Test into_* method 272 | `SVTEST(into_something_test) begin 273 | json_array res_jarray; 274 | json_array orig_jarray = json_array::from('{}); 275 | json_value jvalue = orig_jarray; 276 | 277 | res_jarray = jvalue.into_array(); 278 | end `SVTEST_END 279 | 280 | `SVUNIT_TESTS_END 281 | 282 | endmodule : json_array_unit_test 283 | -------------------------------------------------------------------------------- /tests/json_object_unit_test.sv: -------------------------------------------------------------------------------- 1 | `include "svunit_defines.svh" 2 | `include "test_utils_macros.svh" 3 | 4 | // Tests of `json_object` 5 | module json_object_unit_test; 6 | import svunit_pkg::svunit_testcase; 7 | import test_utils_pkg::*; 8 | import json_pkg::*; 9 | 10 | string name = "json_object_ut"; 11 | svunit_testcase svunit_ut; 12 | 13 | function void build(); 14 | svunit_ut = new(name); 15 | endfunction 16 | 17 | task setup(); 18 | svunit_ut.setup(); 19 | endtask 20 | 21 | task teardown(); 22 | svunit_ut.teardown(); 23 | endtask 24 | 25 | `SVUNIT_TESTS_BEGIN 26 | 27 | // Create json_object using constructor 28 | `SVTEST(create_new_test) begin 29 | string key = "foo"; 30 | json_object jobject; 31 | jobject = new('{"foo": json_bool::from(0)}); 32 | `FAIL_UNLESS(jobject.get(key).into_bool().get() == 0) 33 | end `SVTEST_END 34 | 35 | 36 | // Create json_object using static method 37 | `SVTEST(create_static_test) begin 38 | string key = "foo"; 39 | json_object jobject; 40 | jobject = json_object::from('{"foo": json_bool::from(0)}); 41 | `FAIL_UNLESS(jobject.get(key).into_bool().get() == 0) 42 | end `SVTEST_END 43 | 44 | 45 | // Clone json_object instance 46 | `SVTEST(clone_object_test) begin 47 | string str = "foo"; 48 | json_object orig = json_object::from('{"foo": json_int::from(12345)}); 49 | json_object clone = orig.clone().into_object(); 50 | `FAIL_IF(orig == clone) 51 | `FAIL_UNLESS(clone.size() == 1) 52 | `FAIL_UNLESS(clone.get(str).into_int().get() == 12345) 53 | end `SVTEST_END 54 | 55 | 56 | // Clone object with null items 57 | `SVTEST(clone_object_with_nulls_test) begin 58 | json_value jvalue; 59 | json_object orig = json_object::from('{"foo": json_string::from("blabla"), "bar": null}); 60 | json_object clone = orig.clone().into_object(); 61 | `FAIL_IF(orig == clone) 62 | `FAIL_UNLESS(clone.size() == 2) 63 | jvalue = clone.get("foo"); 64 | `FAIL_UNLESS_STR_EQUAL(jvalue.into_string().get(), "blabla") 65 | jvalue = clone.get("bar"); 66 | `FAIL_UNLESS(jvalue == null) 67 | end `SVTEST_END 68 | 69 | 70 | // Clone complex object 71 | `SVTEST(clone_object_complex_test) begin 72 | string str = "bar"; 73 | json_real jreal; 74 | json_array jarray; 75 | json_object jobject; 76 | json_object orig = json_object::from('{ 77 | "real": json_real::from(0.002), 78 | "array": json_array::from('{ 79 | json_array::from('{}), 80 | json_object::from('{"bar": json_bool::from(1)}) 81 | }) 82 | }); 83 | json_object clone = orig.clone().into_object(); 84 | `FAIL_IF(orig == clone) 85 | `FAIL_UNLESS(clone.size() == 2) 86 | 87 | jreal = clone.get("real").into_real(); 88 | `FAIL_UNLESS(jreal.get() == 0.002) 89 | 90 | jarray = clone.get("array").into_array(); 91 | `FAIL_UNLESS(jarray.size() == 2) 92 | `FAIL_UNLESS(jarray.get(0).into_array().size() == 0) 93 | 94 | jobject = jarray.get(1).into_object(); 95 | `FAIL_UNLESS(jobject.size() == 1) 96 | `FAIL_UNLESS(jobject.get(str).into_bool().get() == 1) 97 | end `SVTEST_END 98 | 99 | 100 | // Compare object instances 101 | `SVTEST(compare_object_test) begin 102 | json_object jobject_a = json_object::from('{"key": json_int::from(42)}); 103 | json_object jobject_b = jobject_a.clone().into_object(); 104 | // OK 105 | `FAIL_UNLESS(jobject_a.compare(jobject_a)) 106 | `FAIL_UNLESS(jobject_a.compare(jobject_b)) 107 | jobject_a.set("bar", json_bool::from(0)); 108 | jobject_b.set("bar", json_bool::from(0)); 109 | `FAIL_UNLESS(jobject_a.compare(jobject_b)) 110 | // Fail 111 | jobject_a = json_object::from('{"key": json_int::from(41)}); 112 | jobject_b = json_object::from('{"key": json_int::from(43)}); 113 | `FAIL_IF(jobject_a.compare(jobject_b)) 114 | jobject_a = json_object::from('{"key": json_int::from(41)}); 115 | jobject_b = json_object::from('{"keyy": json_int::from(41)}); 116 | `FAIL_IF(jobject_a.compare(jobject_b)) 117 | jobject_a = json_object::from('{"key": json_int::from(41)}); 118 | jobject_b = json_object::from(empty_jvalue_map); 119 | `FAIL_IF(jobject_a.compare(jobject_b)) 120 | jobject_a = json_object::from(empty_jvalue_map); 121 | jobject_b = json_object::from('{"key": json_int::from(42)}); 122 | `FAIL_IF(jobject_a.compare(jobject_b)) 123 | jobject_a = json_object::from('{"key": json_int::from(42)}); 124 | jobject_b = json_object::from('{"key": null}); 125 | `FAIL_IF(jobject_a.compare(jobject_b)) 126 | jobject_a = json_object::from('{"key": null}); 127 | jobject_b = json_object::from('{"key": json_int::from(42)}); 128 | `FAIL_IF(jobject_a.compare(jobject_b)) 129 | end `SVTEST_END 130 | 131 | 132 | // Compare nested objects 133 | `SVTEST(compare_object_nested_test) begin 134 | json_object jobject_a = json_object::from('{ 135 | "key1": json_int::from(42), 136 | "key2": json_object::from(empty_jvalue_map), 137 | "key3": json_object::from('{ 138 | "bar": json_string::from("hello"), 139 | "baz": json_array::from('{null, json_int::from(0)}) 140 | }) 141 | }); 142 | json_object jobject_b = jobject_a.clone().into_object(); 143 | 144 | // OK 145 | `FAIL_UNLESS(jobject_a.compare(jobject_b)) 146 | 147 | // Fail 148 | jobject_b.set("key2", null); 149 | `FAIL_IF(jobject_a.compare(jobject_b)) 150 | jobject_b = jobject_a.clone().into_object(); 151 | 152 | jobject_b.get("key3").into_object().set("baz", json_int::from(42)); 153 | `FAIL_IF(jobject_a.compare(jobject_b)) 154 | end `SVTEST_END 155 | 156 | // Compare object with other JSON values 157 | `SVTEST(compare_object_with_others_test) begin 158 | json_string jstring = json_string::from("1"); 159 | json_real jreal = json_real::from(1.0); 160 | json_int jint = json_int::from(1); 161 | json_bool jbool = json_bool::from(1); 162 | json_array jarray = json_array::from('{json_int::from(1)}); 163 | json_object jobject = json_object::from('{"int": json_int::from(1)}); 164 | // Comparsion is strict 165 | `FAIL_IF(jobject.compare(jreal)) 166 | `FAIL_IF(jobject.compare(jint)) 167 | `FAIL_IF(jobject.compare(jbool)) 168 | `FAIL_IF(jobject.compare(jarray)) 169 | `FAIL_IF(jobject.compare(jstring)) 170 | `FAIL_IF(jobject.compare(null)) 171 | end `SVTEST_END 172 | 173 | 174 | // Access internal values by key 175 | `SVTEST(getter_setter_test) begin 176 | json_int jint0 = json_int::from(42); 177 | json_int jint1 = json_int::from(-113); 178 | json_int jint2 = json_int::from(512); 179 | json_int jint_temp; 180 | json_value jvalue; 181 | json_object jobject = json_object::from('{"aaa": jint0, "bbb": jint1}); 182 | 183 | jint_temp = jobject.get("aaa").into_int(); 184 | `FAIL_UNLESS(jint_temp == jint0) 185 | jint_temp = jobject.get("bbb").into_int(); 186 | `FAIL_UNLESS(jint_temp == jint1) 187 | jobject.set("aaa", jint2); 188 | jint_temp = jobject.get("aaa").into_int(); 189 | `FAIL_UNLESS(jint_temp == jint2) 190 | jobject.set("ccc", null); 191 | jvalue = jobject.get("ccc"); 192 | `FAIL_UNLESS(jvalue == null) 193 | end `SVTEST_END 194 | 195 | 196 | // Test json_object_encodable interface 197 | `SVTEST(encodable_test) begin 198 | json_value_encodable val0; 199 | json_value_encodable val1; 200 | json_object::values_t values = '{"foo": json_int::from(42), "bar": json_int::from(-113)}; 201 | json_object_encodable::values_t enc_values; 202 | json_object jobject = json_object::from(values); 203 | 204 | enc_values = jobject.to_json_encodable(); 205 | `FAIL_UNLESS(enc_values.size() == 2) 206 | val0 = values["foo"]; 207 | val1 = enc_values["foo"]; 208 | `FAIL_UNLESS(val0 == val1) 209 | val0 = values["bar"]; 210 | val1 = enc_values["bar"]; 211 | `FAIL_UNLESS(val0 == val1) 212 | end `SVTEST_END 213 | 214 | 215 | // Test is_* methods 216 | `SVTEST(is_something_test) begin 217 | json_object jobject = json_object::from(empty_jvalue_map); 218 | json_value jvalue = jobject; 219 | 220 | `FAIL_UNLESS(jvalue.is_object()) 221 | `FAIL_IF(jvalue.is_array()) 222 | `FAIL_IF(jvalue.is_string()) 223 | `FAIL_IF(jvalue.is_int()) 224 | `FAIL_IF(jvalue.is_real()) 225 | `FAIL_IF(jvalue.is_bool()) 226 | end `SVTEST_END 227 | 228 | 229 | // Test match_* methods 230 | `SVTEST(matches_something_test) begin 231 | json_object jobject; 232 | json_array jarray; 233 | json_string jstring; 234 | json_int jint; 235 | json_real jreal; 236 | json_bool jbool; 237 | 238 | json_object orig_jobject = json_object::from(empty_jvalue_map); 239 | json_value jvalue = orig_jobject; 240 | 241 | `FAIL_UNLESS(jvalue.matches_object(jobject)) 242 | `FAIL_IF(jvalue.matches_array(jarray)) 243 | `FAIL_IF(jvalue.matches_string(jstring)) 244 | `FAIL_IF(jvalue.matches_int(jint)) 245 | `FAIL_IF(jvalue.matches_real(jreal)) 246 | `FAIL_IF(jvalue.matches_bool(jbool)) 247 | 248 | `FAIL_UNLESS(jobject == orig_jobject) 249 | end `SVTEST_END 250 | 251 | 252 | // Test try_into_* methods 253 | `SVTEST(try_into_something_test) begin 254 | json_result#(json_object) res_jobject; 255 | json_result#(json_array) res_jarray; 256 | json_result#(json_string) res_jstring; 257 | json_result#(json_int) res_jint; 258 | json_result#(json_real) res_jreal; 259 | json_result#(json_bool) res_jbool; 260 | 261 | json_object orig_jobject = json_object::from(empty_jvalue_map); 262 | json_value jvalue = orig_jobject; 263 | 264 | res_jobject = jvalue.try_into_object(); 265 | res_jarray = jvalue.try_into_array(); 266 | res_jstring = jvalue.try_into_string(); 267 | res_jint = jvalue.try_into_int(); 268 | res_jreal = jvalue.try_into_real(); 269 | res_jbool = jvalue.try_into_bool(); 270 | 271 | `FAIL_UNLESS(res_jobject.is_ok()) 272 | `FAIL_IF(res_jarray.is_ok()) 273 | `FAIL_IF(res_jstring.is_ok()) 274 | `FAIL_IF(res_jint.is_ok()) 275 | `FAIL_IF(res_jreal.is_ok()) 276 | `FAIL_IF(res_jbool.is_ok()) 277 | 278 | `FAIL_UNLESS(res_jobject.unwrap() == orig_jobject) 279 | end `SVTEST_END 280 | 281 | 282 | // Test into_* method 283 | `SVTEST(into_something_test) begin 284 | json_object res_jobject; 285 | json_object orig_jobject = json_object::from(empty_jvalue_map); 286 | json_value jvalue = orig_jobject; 287 | 288 | res_jobject = jvalue.into_object(); 289 | end `SVTEST_END 290 | 291 | `SVUNIT_TESTS_END 292 | 293 | endmodule : json_object_unit_test 294 | -------------------------------------------------------------------------------- /src/json_decoder.sv: -------------------------------------------------------------------------------- 1 | // JSON decoder 2 | class json_decoder; 3 | localparam byte CR = 8'd13; // 13=\r=CR - not in SV standard 4 | 5 | //---------------------------------------------------------------------------- 6 | // Public methods 7 | //---------------------------------------------------------------------------- 8 | // Try to load and decode string into JSON value 9 | extern static function json_result load_string(string str); 10 | 11 | // Try to load and decode file into JSON value 12 | extern static function json_result load_file(string path); 13 | 14 | //---------------------------------------------------------------------------- 15 | // Private properties 16 | //---------------------------------------------------------------------------- 17 | typedef struct { 18 | json_value value; 19 | int unsigned end_pos; 20 | } parsed_s; 21 | 22 | typedef json_result#(parsed_s) parser_result; 23 | 24 | protected const byte whitespace_chars[] = '{" ", "\t", "\n", CR}; 25 | 26 | protected const byte escape_lut[string] = '{ 27 | "\\\"": "\"", 28 | "\\\\": "\\", 29 | "\\/": "/", 30 | "\\f": "\f", 31 | "\\n": "\n", 32 | "\\r" : CR, 33 | "\\t": "\t" 34 | }; 35 | 36 | protected const byte hex_chars[] = '{ 37 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 38 | "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F" 39 | }; 40 | 41 | protected const byte value_start_chars[] = '{ 42 | "{", "[", "\"", "n", "t", "f", "-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" 43 | }; 44 | 45 | protected const byte digit_chars[] = '{ 46 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" 47 | }; 48 | 49 | protected int unsigned nesting_limit = 1024; 50 | 51 | //---------------------------------------------------------------------------- 52 | // Private methods 53 | //---------------------------------------------------------------------------- 54 | // Private constructor 55 | extern local function new(); 56 | 57 | // Scan string symbol by symbol to encounter and extract JSON values recursively. 58 | extern protected function parser_result parse_value( 59 | const ref string str, 60 | input int unsigned start_pos, 61 | input int unsigned nesting_lvl 62 | ); 63 | 64 | extern protected function parser_result parse_object( 65 | const ref string str, 66 | input int unsigned start_pos, 67 | input int unsigned nesting_lvl 68 | ); 69 | 70 | extern protected function parser_result parse_array( 71 | const ref string str, 72 | input int unsigned start_pos, 73 | input int unsigned nesting_lvl 74 | ); 75 | 76 | extern protected function parser_result parse_string(const ref string str, input int unsigned start_pos); 77 | 78 | extern protected function parser_result parse_number(const ref string str, input int unsigned start_pos); 79 | 80 | extern protected function parser_result parse_literal(const ref string str, input int unsigned start_pos); 81 | 82 | extern protected function parser_result check_trailing_chars( 83 | const ref string str, 84 | input int unsigned start_pos 85 | ); 86 | 87 | // Scan input string char by char ignoring any whitespaces and stop at first non-whitespace char. 88 | // Return error with last char position if non-whitespace char was not found or do not match expected ones. 89 | // Return OK and position of found char within string otherwise. 90 | extern protected function parser_result scan_until_token( 91 | const ref string str, 92 | input int unsigned start_pos, 93 | input byte expected_tokens [$] = '{} 94 | ); 95 | endclass : json_decoder 96 | 97 | 98 | function json_decoder::new(); 99 | endfunction : new 100 | 101 | 102 | function json_result json_decoder::load_string(string str); 103 | parsed_s parsed; 104 | json_error error; 105 | parser_result result; 106 | json_decoder decoder = new(); 107 | 108 | result = decoder.parse_value(str, 0, 0); 109 | case (1) 110 | result.matches_ok(parsed): begin 111 | parser_result check_result = decoder.check_trailing_chars(str, parsed.end_pos + 1); 112 | case (1) 113 | check_result.is_ok(): return json_result#()::ok(parsed.value); 114 | check_result.matches_err(error): return json_result#()::err(error); 115 | endcase 116 | end 117 | 118 | result.matches_err(error): return json_result#()::err(error); 119 | endcase 120 | endfunction : load_string 121 | 122 | 123 | function json_result json_decoder::load_file(string path); 124 | int file_descr; 125 | string file_text; 126 | 127 | // Try to open file 128 | file_descr = $fopen(path, "r"); 129 | if (file_descr == 0) begin 130 | return `JSON_ERR(json_error::FILE_NOT_OPENED, $sformatf("Failed to open the file '%s'!", path)); 131 | end 132 | 133 | // Read lines until the end of the file 134 | while (!$feof(file_descr)) begin 135 | string line; 136 | void'($fgets(line, file_descr)); 137 | file_text = {file_text, line}; 138 | end 139 | $fclose(file_descr); 140 | 141 | return load_string(file_text); 142 | endfunction : load_file 143 | 144 | 145 | function json_decoder::parser_result json_decoder::check_trailing_chars( 146 | const ref string str, 147 | input int unsigned start_pos 148 | ); 149 | json_error error; 150 | parsed_s parsed; 151 | parser_result result = scan_until_token(str, start_pos); 152 | case (1) 153 | // scan ok means that some token found, which is a failure 154 | result.matches_ok(parsed): return `JSON_SYNTAX_ERR(json_error::TRAILING_CHARS, str, parsed.end_pos); 155 | // scan error means that no token found, which is a success 156 | result.matches_err(error): return parser_result::ok('{null, str.len()-1}); 157 | endcase 158 | endfunction : check_trailing_chars 159 | 160 | 161 | function json_decoder::parser_result json_decoder::parse_value( 162 | const ref string str, 163 | input int unsigned start_pos, 164 | input int unsigned nesting_lvl 165 | ); 166 | parser_result result; 167 | json_error error; 168 | parsed_s parsed; 169 | 170 | int unsigned curr_pos = start_pos; 171 | 172 | // Skip all whitespaces until valid token 173 | result = scan_until_token(str, curr_pos, this.value_start_chars); 174 | case (1) 175 | result.matches_err_eq(json_error::EXPECTED_TOKEN, error): 176 | return `JSON_SYNTAX_ERR(json_error::EXPECTED_VALUE, str, error.json_pos); 177 | 178 | result.matches_err_eq(json_error::EOF_VALUE, error): 179 | return `JSON_SYNTAX_ERR(json_error::EOF_VALUE, str, error.json_pos); 180 | 181 | result.matches_err(error): return `JSON_INTERNAL_ERR($sformatf("Unexpected error %s", error.kind.name())); 182 | 183 | result.matches_ok(parsed): begin 184 | curr_pos = parsed.end_pos; // current character must start a value 185 | 186 | case (str[curr_pos]) inside 187 | "{": return parse_object(str, curr_pos + 1, nesting_lvl); 188 | "[": return parse_array(str, curr_pos + 1, nesting_lvl); 189 | "\"": return parse_string(str, curr_pos); 190 | "n", "t", "f": return parse_literal(str, curr_pos); 191 | "-", ["0":"9"]: return parse_number(str, curr_pos); 192 | 193 | default: return `JSON_SYNTAX_ERR(json_error::EXPECTED_VALUE, str, curr_pos); 194 | endcase 195 | end 196 | endcase 197 | endfunction : parse_value 198 | 199 | 200 | function json_decoder::parser_result json_decoder::parse_object( 201 | const ref string str, 202 | input int unsigned start_pos, 203 | input int unsigned nesting_lvl 204 | ); 205 | enum { 206 | PARSE_KEY, 207 | EXPECT_COLON, 208 | PARSE_VALUE, 209 | EXPECT_COMMA_OR_RIGHT_CURLY 210 | } state = PARSE_KEY; 211 | 212 | string key; 213 | json_value values [string]; 214 | 215 | json_error error; 216 | parser_result result; 217 | parsed_s parsed; 218 | 219 | int unsigned curr_pos = start_pos; 220 | bit trailing_comma = 1'b0; 221 | bit exit_parsing_loop = 1'b0; 222 | 223 | nesting_lvl++; 224 | if (nesting_lvl >= this.nesting_limit) begin 225 | return `JSON_SYNTAX_ERR(json_error::DEEP_NESTING, str, curr_pos); 226 | end 227 | 228 | while(!exit_parsing_loop) begin 229 | case (state) 230 | PARSE_KEY: begin 231 | result = parse_string(str, curr_pos); 232 | case(1) 233 | result.matches_err_eq(json_error::EXPECTED_DOUBLE_QUOTE, error): begin 234 | if (str[error.json_pos] == "}") begin 235 | if (trailing_comma) begin 236 | return `JSON_SYNTAX_ERR(json_error::TRAILING_COMMA, str, error.json_pos); 237 | end 238 | curr_pos = error.json_pos; 239 | exit_parsing_loop = 1'b1; // empty object parsed 240 | end else begin 241 | return result; 242 | end 243 | end 244 | 245 | result.matches_err(error): return result; 246 | 247 | result.matches_ok(parsed): begin 248 | key = parsed.value.into_string().get(); 249 | curr_pos = parsed.end_pos + 1; // move from last string token 250 | state = EXPECT_COLON; 251 | end 252 | endcase 253 | end 254 | 255 | EXPECT_COLON: begin 256 | result = scan_until_token(str, curr_pos, '{":"}); 257 | case(1) 258 | result.matches_err_eq(json_error::EXPECTED_TOKEN, error): 259 | return `JSON_SYNTAX_ERR(json_error::EXPECTED_COLON, str, error.json_pos); 260 | 261 | result.matches_err_eq(json_error::EOF_VALUE, error): 262 | return `JSON_SYNTAX_ERR(json_error::EOF_OBJECT, str, error.json_pos); 263 | 264 | result.matches_err(error): return `JSON_INTERNAL_ERR($sformatf("Unexpected error %s", error.kind.name())); 265 | 266 | result.matches_ok(parsed): begin 267 | curr_pos = parsed.end_pos + 1; // move from colon to next character 268 | state = PARSE_VALUE; 269 | end 270 | endcase 271 | end 272 | 273 | PARSE_VALUE: begin 274 | result = parse_value(str, curr_pos, nesting_lvl); 275 | case(1) 276 | result.matches_err(error): return result; 277 | 278 | result.matches_ok(parsed): begin 279 | values[key] = parsed.value; 280 | curr_pos = parsed.end_pos + 1; // move from last value token 281 | state = EXPECT_COMMA_OR_RIGHT_CURLY; 282 | end 283 | endcase 284 | end 285 | 286 | EXPECT_COMMA_OR_RIGHT_CURLY: begin 287 | result = scan_until_token(str, curr_pos, '{",", "}"}); 288 | case(1) 289 | result.matches_err_eq(json_error::EXPECTED_TOKEN, error): 290 | return `JSON_SYNTAX_ERR(json_error::EXPECTED_OBJECT_COMMA_OR_END, str, error.json_pos); 291 | 292 | result.matches_err_eq(json_error::EOF_VALUE, error): 293 | return `JSON_SYNTAX_ERR(json_error::EOF_OBJECT, str, error.json_pos); 294 | 295 | result.matches_err(error): return `JSON_INTERNAL_ERR($sformatf("Unexpected error %s", error.kind.name())); 296 | 297 | result.matches_ok(parsed): begin 298 | curr_pos = parsed.end_pos; 299 | if (str[curr_pos] == "}") begin 300 | exit_parsing_loop = 1'b1; // end of object 301 | end else begin 302 | trailing_comma = 1'b1; 303 | curr_pos++; // move to a symbol after comma 304 | state = PARSE_KEY; 305 | end 306 | end 307 | endcase 308 | end 309 | endcase 310 | end 311 | 312 | parsed.value = json_object::from(values); 313 | parsed.end_pos = curr_pos; 314 | return parser_result::ok(parsed); 315 | endfunction : parse_object 316 | 317 | 318 | function json_decoder::parser_result json_decoder::parse_array( 319 | const ref string str, 320 | input int unsigned start_pos, 321 | input int unsigned nesting_lvl 322 | ); 323 | enum { 324 | PARSE_VALUE, 325 | EXPECT_COMMA_OR_RIGHT_BRACE 326 | } state = PARSE_VALUE; 327 | 328 | json_value values[$]; 329 | 330 | json_error error; 331 | parser_result result; 332 | parsed_s parsed; 333 | 334 | int unsigned curr_pos = start_pos; 335 | bit trailing_comma = 1'b0; 336 | bit exit_parsing_loop = 1'b0; 337 | 338 | nesting_lvl++; 339 | if (nesting_lvl >= this.nesting_limit) begin 340 | return `JSON_SYNTAX_ERR(json_error::DEEP_NESTING, str, curr_pos); 341 | end 342 | 343 | while(!exit_parsing_loop) begin 344 | case (state) 345 | PARSE_VALUE: begin 346 | result = parse_value(str, curr_pos, nesting_lvl); 347 | case (1) 348 | result.matches_err_eq(json_error::EXPECTED_VALUE, error): begin 349 | if (str[error.json_pos] == "]") begin 350 | if (trailing_comma) begin 351 | return `JSON_SYNTAX_ERR(json_error::TRAILING_COMMA, str, error.json_pos); 352 | end 353 | curr_pos = error.json_pos; 354 | exit_parsing_loop = 1'b1; // empty array parsed 355 | end else begin 356 | return result; 357 | end 358 | end 359 | 360 | result.matches_err(error): return result; 361 | 362 | result.matches_ok(parsed): begin 363 | values.push_back(parsed.value); 364 | curr_pos = parsed.end_pos + 1; // move from last value token 365 | state = EXPECT_COMMA_OR_RIGHT_BRACE; 366 | end 367 | endcase 368 | end 369 | 370 | EXPECT_COMMA_OR_RIGHT_BRACE: begin 371 | result = scan_until_token(str, curr_pos, '{",", "]"}); 372 | case (1) 373 | result.matches_err_eq(json_error::EXPECTED_TOKEN, error): 374 | return `JSON_SYNTAX_ERR(json_error::EXPECTED_ARRAY_COMMA_OR_END, str, error.json_pos); 375 | 376 | result.matches_err_eq(json_error::EOF_VALUE, error): 377 | return `JSON_SYNTAX_ERR(json_error::EOF_ARRAY, str, error.json_pos); 378 | 379 | result.matches_err(error): return `JSON_INTERNAL_ERR($sformatf("Unexpected error %s", error.kind.name())); 380 | 381 | result.matches_ok(parsed): begin 382 | curr_pos = parsed.end_pos; 383 | if (str[curr_pos] == "]") begin 384 | exit_parsing_loop = 1'b1; // end of array 385 | end else begin 386 | trailing_comma = 1'b1; 387 | curr_pos++; // move to a symbol after comma 388 | state = PARSE_VALUE; 389 | end 390 | end 391 | endcase 392 | end 393 | endcase 394 | end 395 | 396 | parsed.value = json_array::from(values); 397 | parsed.end_pos = curr_pos; 398 | return parser_result::ok(parsed); 399 | endfunction : parse_array 400 | 401 | 402 | function json_decoder::parser_result json_decoder::parse_string(const ref string str, input int unsigned start_pos); 403 | enum { 404 | EXPECT_DOUBLE_QUOTE, 405 | SCAN_CHARS, 406 | PARSE_ESCAPE 407 | } state = EXPECT_DOUBLE_QUOTE; 408 | 409 | string value; 410 | 411 | json_error error; 412 | parser_result result; 413 | parsed_s parsed; 414 | 415 | int unsigned curr_pos = start_pos; 416 | int unsigned str_len = str.len(); 417 | bit exit_parsing_loop = 1'b0; 418 | 419 | while(!exit_parsing_loop) begin 420 | case (state) 421 | EXPECT_DOUBLE_QUOTE: begin 422 | result = scan_until_token(str, curr_pos, '{"\""}); 423 | case (1) 424 | result.matches_err_eq(json_error::EXPECTED_TOKEN, error): 425 | return `JSON_SYNTAX_ERR(json_error::EXPECTED_DOUBLE_QUOTE, str, error.json_pos); 426 | 427 | result.matches_err_eq(json_error::EOF_VALUE, error): 428 | return `JSON_SYNTAX_ERR(json_error::EOF_STRING, str, error.json_pos); 429 | 430 | result.matches_err(error): return `JSON_INTERNAL_ERR($sformatf("Unexpected error %s", error.kind.name())); 431 | 432 | result.matches_ok(parsed): begin 433 | curr_pos = parsed.end_pos + 1; 434 | state = SCAN_CHARS; 435 | end 436 | endcase 437 | end 438 | 439 | SCAN_CHARS: begin 440 | while (curr_pos < str_len) begin 441 | if (str[curr_pos] == "\\") begin 442 | curr_pos++; 443 | state = PARSE_ESCAPE; 444 | break; 445 | end else if (str[curr_pos] == "\"") begin 446 | exit_parsing_loop = 1'b1; 447 | break; 448 | end else if (str[curr_pos] < byte'('h20)) begin 449 | return `JSON_SYNTAX_ERR(json_error::INVALID_CHAR, str, curr_pos); 450 | end else begin 451 | value = {value, str[curr_pos++]}; 452 | end 453 | end 454 | if (curr_pos == str_len) begin 455 | return `JSON_SYNTAX_ERR(json_error::EOF_STRING, str, curr_pos - 1); 456 | end 457 | end 458 | 459 | PARSE_ESCAPE: begin 460 | string escape = str.substr(curr_pos - 1, curr_pos); 461 | if (escape_lut.exists(str.substr(curr_pos - 1, curr_pos)) == 1) begin 462 | value = {value, this.escape_lut[escape]}; 463 | curr_pos++; 464 | state = SCAN_CHARS; 465 | end else if (str[curr_pos] inside {"u", "b"}) begin 466 | // backspace and unicode escapes are not supported, so leave them as they are 467 | value = {value, escape}; 468 | curr_pos++; 469 | state = SCAN_CHARS; 470 | end else begin 471 | return `JSON_SYNTAX_ERR(json_error::INVALID_ESCAPE, str, curr_pos); 472 | end 473 | end 474 | endcase 475 | end 476 | 477 | parsed.value = json_string::from(value); 478 | parsed.end_pos = curr_pos; 479 | return parser_result::ok(parsed); 480 | endfunction : parse_string 481 | 482 | 483 | function json_decoder::parser_result json_decoder::parse_number(const ref string str, input int unsigned start_pos); 484 | enum { 485 | EXPECT_MINUS_OR_DIGIT, 486 | EXPECT_LEADING_DIGIT, 487 | PARSE_DIGITS, 488 | EXPECT_POINT_OR_EXP, 489 | PARSE_FRACTIONAL_DIGITS, 490 | EXPECT_EXPONENT_SIGN, 491 | PARSE_EXPONENT_DIGITS 492 | } state = EXPECT_MINUS_OR_DIGIT; 493 | 494 | string value; 495 | real real_value; 496 | longint int_value; 497 | bit is_real; 498 | 499 | json_error error; 500 | parser_result result; 501 | parsed_s parsed; 502 | 503 | int unsigned curr_pos = start_pos; 504 | int unsigned str_len = str.len(); 505 | bit exit_parsing_loop = 1'b0; 506 | 507 | while(!exit_parsing_loop) begin 508 | case (state) 509 | EXPECT_MINUS_OR_DIGIT: begin 510 | byte expected_tokens [$] = this.digit_chars; 511 | expected_tokens.push_back("-"); 512 | 513 | result = scan_until_token(str, curr_pos, expected_tokens); 514 | case (1) 515 | result.matches_err(error): return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, error.json_pos); 516 | 517 | result.matches_ok(parsed): begin 518 | curr_pos = parsed.end_pos; 519 | if (str[curr_pos] == "-") begin 520 | state = EXPECT_LEADING_DIGIT; 521 | end else if (str[curr_pos] == "0") begin 522 | state = EXPECT_POINT_OR_EXP; 523 | end else begin 524 | state = PARSE_DIGITS; 525 | end 526 | value = {value, str[curr_pos++]}; 527 | end 528 | endcase 529 | 530 | if (curr_pos == str_len) begin 531 | if (str[curr_pos - 1] == "-") begin 532 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos - 1); 533 | end 534 | exit_parsing_loop = 1'b1; 535 | end 536 | end 537 | 538 | EXPECT_LEADING_DIGIT: begin 539 | if (str[curr_pos] == "0") begin 540 | state = EXPECT_POINT_OR_EXP; 541 | end else if (str[curr_pos] inside {this.digit_chars}) begin 542 | state = PARSE_DIGITS; 543 | end else begin 544 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos); 545 | end 546 | 547 | value = {value, str[curr_pos++]}; 548 | if (curr_pos == str_len) begin 549 | exit_parsing_loop = 1'b1; 550 | end 551 | end 552 | 553 | EXPECT_POINT_OR_EXP: begin 554 | if (str[curr_pos] == ".") begin 555 | state = PARSE_FRACTIONAL_DIGITS; 556 | end else if (str[curr_pos] inside {"e", "E"}) begin 557 | state = EXPECT_EXPONENT_SIGN; 558 | end else begin 559 | exit_parsing_loop = 1'b1; 560 | break; 561 | end 562 | 563 | value = {value, str[curr_pos++]}; 564 | if (curr_pos == str_len) begin 565 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos - 1); 566 | end 567 | end 568 | 569 | PARSE_DIGITS: begin 570 | while (curr_pos < str_len) begin 571 | if (str[curr_pos] == ".") begin 572 | value = {value, str[curr_pos++]}; 573 | state = PARSE_FRACTIONAL_DIGITS; 574 | break; 575 | end else if (str[curr_pos] inside {"e", "E"}) begin 576 | value = {value, str[curr_pos++]}; 577 | state = EXPECT_EXPONENT_SIGN; 578 | break; 579 | end else if (str[curr_pos] inside {this.digit_chars}) begin 580 | value = {value, str[curr_pos++]}; 581 | end else begin 582 | exit_parsing_loop = 1'b1; 583 | break; 584 | end 585 | end 586 | 587 | if (curr_pos == str_len) begin 588 | if (str[curr_pos - 1] inside {".", "e", "E"}) begin 589 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos - 1); 590 | end 591 | exit_parsing_loop = 1'b1; 592 | end 593 | end 594 | 595 | PARSE_FRACTIONAL_DIGITS: begin 596 | is_real = 1'b1; 597 | 598 | if (!(str[curr_pos] inside {this.digit_chars})) begin 599 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos); 600 | end 601 | 602 | while (curr_pos < str_len) begin 603 | if (str[curr_pos] inside {"e", "E"}) begin 604 | value = {value, str[curr_pos++]}; 605 | state = EXPECT_EXPONENT_SIGN; 606 | break; 607 | end else if (str[curr_pos] inside {this.digit_chars}) begin 608 | value = {value, str[curr_pos++]}; 609 | end else begin 610 | exit_parsing_loop = 1'b1; 611 | break; 612 | end 613 | end 614 | 615 | if (curr_pos == str_len) begin 616 | if (str[curr_pos - 1] inside {"e", "E"}) begin 617 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos - 1); 618 | end 619 | exit_parsing_loop = 1'b1; 620 | end 621 | end 622 | 623 | EXPECT_EXPONENT_SIGN: begin 624 | is_real = 1'b1; 625 | if (!(str[curr_pos] inside {"-", "+", this.digit_chars})) begin 626 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos); 627 | end 628 | 629 | value = {value, str[curr_pos++]}; 630 | state = PARSE_EXPONENT_DIGITS; 631 | end 632 | 633 | PARSE_EXPONENT_DIGITS: begin 634 | if ((str[curr_pos - 1] inside {"-", "+"}) && !(str[curr_pos] inside {this.digit_chars})) begin 635 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos - 1); 636 | end 637 | 638 | while (curr_pos < str_len) begin 639 | if (str[curr_pos] inside {this.digit_chars}) begin 640 | value = {value, str[curr_pos++]}; 641 | end else begin 642 | exit_parsing_loop = 1'b1; 643 | break; 644 | end 645 | end 646 | if (curr_pos == str_len) begin 647 | exit_parsing_loop = 1'b1; 648 | end 649 | end 650 | endcase 651 | end 652 | 653 | parsed.end_pos = curr_pos - 1; 654 | if ((curr_pos < str_len) && !(str[curr_pos] inside {this.whitespace_chars, ",", "]", "}"})) begin 655 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, curr_pos); 656 | end else if (is_real && $sscanf(value, "%f", real_value) == 1) begin 657 | parsed.value = json_real::from(real_value); 658 | return parser_result::ok(parsed); 659 | end else if ($sscanf(value, "%d", int_value) == 1) begin 660 | parsed.value = json_int::from(int_value); 661 | return parser_result::ok(parsed); 662 | end else begin 663 | return `JSON_SYNTAX_ERR(json_error::INVALID_NUMBER, str, parsed.end_pos); 664 | end 665 | endfunction : parse_number 666 | 667 | 668 | function json_decoder::parser_result json_decoder::parse_literal(const ref string str, input int unsigned start_pos); 669 | string literal; 670 | string literal_expected; 671 | parsed_s parsed; 672 | int unsigned curr_pos = start_pos; 673 | 674 | case (str[curr_pos]) 675 | "t": begin 676 | literal_expected = "true"; 677 | parsed.end_pos = curr_pos + 3; 678 | parsed.value = json_bool::from(1); 679 | end 680 | 681 | "f": begin 682 | literal_expected = "false"; 683 | parsed.end_pos = curr_pos + 4; 684 | parsed.value = json_bool::from(0); 685 | end 686 | 687 | "n": begin 688 | literal_expected = "null"; 689 | parsed.end_pos = curr_pos + 3; 690 | parsed.value = null; 691 | end 692 | 693 | default: return `JSON_INTERNAL_ERR("Unreachable case branch"); 694 | endcase 695 | 696 | literal = str.substr(curr_pos, parsed.end_pos); 697 | if (literal == "") begin 698 | return `JSON_SYNTAX_ERR(json_error::EOF_LITERAL, str, curr_pos); 699 | end else if (literal != literal_expected)begin 700 | return `JSON_SYNTAX_ERR(json_error::INVALID_LITERAL, str, curr_pos); 701 | end else begin 702 | return parser_result::ok(parsed); 703 | end 704 | endfunction : parse_literal 705 | 706 | 707 | function json_decoder::parser_result json_decoder::scan_until_token( 708 | const ref string str, 709 | input int unsigned start_pos, 710 | input byte expected_tokens [$] = '{} 711 | ); 712 | int unsigned len = str.len(); 713 | int unsigned idx = start_pos; 714 | 715 | while ((str[idx] inside {this.whitespace_chars}) && (idx < len)) begin 716 | idx++; 717 | end 718 | 719 | if (idx == str.len()) begin 720 | return `JSON_SYNTAX_ERR(json_error::EOF_VALUE, "", idx - 1); 721 | end else if ((expected_tokens.size() > 0) && !(str[idx] inside {expected_tokens})) begin 722 | return `JSON_SYNTAX_ERR(json_error::EXPECTED_TOKEN, "", idx); 723 | end else begin 724 | parsed_s res; 725 | res.end_pos = idx; 726 | return parser_result::ok(res); 727 | end 728 | endfunction : scan_until_token 729 | --------------------------------------------------------------------------------