diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 85a9fcfc..c7316f07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -153,7 +153,7 @@ jobs: miniconda-version: "latest" show-channel-urls: true - name: Install conda dependencies - run: conda install conda-build conda-verify -y + run: conda install conda-build conda-verify numpy -y - name: Build & test run: conda build python/conda - name: Install diff --git a/Dockerfile b/Dockerfile index c5de886d..edb1ec8a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,8 @@ RUN apt-get -qq update && \ ADD . /libqasm WORKDIR /libqasm -RUN conan profile detect -RUN conan build . -s:h compiler.cppstd=20 -o libqasm/*:build_tests=True -o libqasm/*:compat=True -o libqasm/*:tree_gen_build_tests=True -o libqasm/*:asan_enabled=True -b missing +RUN conan profile detect --force +RUN conan build . -pr=./conan/profiles/tests-debug-compat -b missing WORKDIR /libqasm/build/Release RUN ctest -C Release --output-on-failure diff --git a/include/cqasm-utils.hpp b/include/cqasm-utils.hpp index 08affbdc..e1e56d0a 100644 --- a/include/cqasm-utils.hpp +++ b/include/cqasm-utils.hpp @@ -4,14 +4,14 @@ #pragma once +#include +#include // ostringstream #include -namespace cqasm { - /** * Namespace for various utility functions. */ -namespace utils { +namespace cqasm::utils { /** * Makes a string lowercase. @@ -23,5 +23,21 @@ std::string to_lowercase(const std::string &name); */ bool equal_case_insensitive(const std::string &lhs, const std::string &rhs); -} // namespace utils -} // namespace cqasm +/** + * Returns a string with a JSON representation of a ParseResult or an AnalysisResult. + */ +template +std::string to_json(const Result &result) { + if (!result.errors.empty()) { + return fmt::format(R"({{"errors":["{}"]}})", // first quote of first error message, and + // last quote of last error message + fmt::join(result.errors, R"(",")")); // last quote of any intermediate error message, and + // first quote of the following + } else { + std::ostringstream oss{}; + result.root->dump_json(oss); + return oss.str(); + } +} + +} // namespace cqasm::utils diff --git a/include/v1x/cqasm-analysis-result.hpp b/include/v1x/cqasm-analysis-result.hpp new file mode 100644 index 00000000..2d126cf5 --- /dev/null +++ b/include/v1x/cqasm-analysis-result.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "cqasm-ast.hpp" +#include "cqasm-semantic.hpp" + +#include // runtime_error +#include // ostream +#include +#include + + +/** + * Namespace for the \ref cqasm::analyzer::Analyzer "Analyzer" class and support classes. + */ +namespace cqasm::v1x::analyzer { + + +/** + * Exception thrown by AnalysisResult::unwrap() when the cQASM file fails to + * parse. + */ +class AnalysisFailed: public std::runtime_error { +public: + AnalysisFailed() : std::runtime_error("cQASM analysis failed") {}; +}; + + +/** + * Analysis result class. + * + * An object of this type is returned by the various `analyze*()` methods of the Analyzer class. + * There are three possibilities. + * + * - Analysis was successful. + * In this case, \ref errors is empty, and \ref root contains a valid \ref semantic::Program "Program" tree. + * - Analysis failed. + * In this case, there is at least one string in \ref errors. + * \ref root may be an empty reference, a partial tree, or even a complete but somehow invalid tree. + * - Analysis failed spectacularly with an internal error, + * in which case an exception is thrown instead of this object being returned. + * + * If you don't want to manage printing error messages yourself and would like an exception upon failure in all cases, + * you can call unwrap() on the returned object. + */ +class AnalysisResult { +public: + /** + * Root node of the semantic tree, if analysis was successful. + * The node may be empty or incomplete if errors is nonempty; + * use unwrap() to ensure it's complete if you just want to use it and + * don't care about things like pretty-printing error messages for your users + * in a different way than what unwrap() does for you. + */ + ast::One root; + + /** + * List of accumulated errors. + * Analysis was successful if and only if `errors.empty()`. + */ + std::vector errors; + + /** + * "Unwraps" the result (as you would in Rust) to get the program node or an exception. + * The exception is always an AnalysisFailed, deriving from std::runtime_error. + * The actual error messages are in this case first written to the given output stream, defaulting to stderr. + */ + ast::One unwrap(std::ostream &out = std::cerr) const; + + /** + * Returns a string with a JSON representation of the AnalysisResult. + */ + std::string to_json() const; +}; + +} // namespace cqasm::v1x::analyzer diff --git a/include/v1x/cqasm-analyzer.hpp b/include/v1x/cqasm-analyzer.hpp index d86a1991..f6356ab2 100644 --- a/include/v1x/cqasm-analyzer.hpp +++ b/include/v1x/cqasm-analyzer.hpp @@ -9,13 +9,14 @@ #pragma once +#include "cqasm-analysis-result.hpp" #include "cqasm-ast.hpp" #include "cqasm-parse-helper.hpp" #include "cqasm-resolver.hpp" #include "cqasm-semantic.hpp" -#include #include +#include /** * Namespace for the \ref cqasm::analyzer::Analyzer "Analyzer" class and @@ -23,61 +24,6 @@ */ namespace cqasm::v1x::analyzer { -/** - * Exception thrown by AnalysisResult::unwrap() when the cQASM file fails to - * parse. - */ -class AnalysisFailed: public std::runtime_error { -public: - AnalysisFailed() : std::runtime_error("cQASM analysis failed") {}; -}; - -/** - * Analysis result class. - * - * An object of this type is returned by the various `analyze*()` methods of - * the Analyzer class. There are three possibilities. - * - * - Analysis was successful. In this case, \ref errors is empty, and - * \ref root contains a valid \ref semantic::Program "Program" tree. - * - Analysis failed. In this case, there is at least one string in - * \ref errors. \ref root may be an empty reference, a partial tree, - * or even a complete but somehow invalid tree. - * - Analysis failed spectacularly with an internal error, in which case an - * exception is thrown instead of this object being returned. - * - * If you don't want to manage printing error messages yourself and would like - * an exception upon failure in all cases, you can call unwrap() on the - * returned object. - */ -class AnalysisResult { -public: - - /** - * Root node of the semantic tree, if analysis was successful. The node may - * be empty or incomplete if errors is nonempty; use unwrap() to ensure it's - * complete if you just want to use it and don't care about things like - * pretty-printing error messages for your users in a different way than - * what unwrap() does for you. - */ - ast::One root; - - /** - * List of accumulated errors. Analysis was successful if and only if - * `errors.empty()`. - */ - std::vector errors; - - /** - * "Unwraps" the result (as you would in Rust) to get the program node or - * an exception. The exception is always an AnalysisFailed, deriving from - * std::runtime_error. The actual error messages are in this case first - * written to the given output stream, defaulting to stderr. - */ - ast::One unwrap(std::ostream &out = std::cerr) const; - -}; - /** * Main class used for analyzing cQASM files. * diff --git a/include/v1x/cqasm-parse-result.hpp b/include/v1x/cqasm-parse-result.hpp index 62e2a9da..4c40cc54 100644 --- a/include/v1x/cqasm-parse-result.hpp +++ b/include/v1x/cqasm-parse-result.hpp @@ -9,7 +9,8 @@ #include "cqasm-annotations.hpp" #include "cqasm-ast.hpp" -#include +#include +#include /** @@ -22,7 +23,6 @@ namespace cqasm::v1x::parser { */ class ParseResult { public: - /** * Root node of the AST, if analysis was sufficiently successful. * This may be set even if parsing was not ENTIRELY successful, @@ -36,6 +36,10 @@ class ParseResult { */ std::vector errors; + /** + * Returns a string with a JSON representation of the ParseResult. + */ + std::string to_json() const; }; } // namespace cqasm::v1x::parser diff --git a/include/v1x/cqasm-py.hpp b/include/v1x/cqasm-py.hpp index 1f0ffd8e..4dba536f 100644 --- a/include/v1x/cqasm-py.hpp +++ b/include/v1x/cqasm-py.hpp @@ -15,7 +15,10 @@ // Forward declarations for internal types. -namespace cqasm::v1x::analyzer { class Analyzer; } +namespace cqasm::v1x::analyzer { + class Analyzer; +} + /** * Main class for parsing and analyzing cQASM files with the v1.x API. @@ -37,10 +40,7 @@ class V1xAnalyzer { * the initial mappings and functions are not configurable at all. * The defaults for these are always used. */ - explicit V1xAnalyzer( - const std::string &max_version = "1.0", - bool without_defaults = false - ); + explicit V1xAnalyzer(const std::string &max_version = "1.0", bool without_defaults = false); /** * std::unique_ptr requires T to be a complete class for the ~T operation. @@ -66,10 +66,7 @@ class V1xAnalyzer { * Registers an error model. * The arguments are passed straight to error_model::ErrorModel's constructor. */ - void register_error_model( - const std::string &name, - const std::string ¶m_types = "" - ); + void register_error_model(const std::string &name, const std::string ¶m_types = ""); /** * Only parses the given file. @@ -79,18 +76,23 @@ class V1xAnalyzer { * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ - static std::vector parse_file( - const std::string &filename - ); + static std::vector parse_file(const std::string &filename); + + /** + * Counterpart of parse_file that returns a string with a JSON representation of the ParseResult. + */ + static std::string parse_file_to_json(const std::string &filename); /** * Same as parse_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ - static std::vector parse_string( - const std::string &data, - const std::string &filename = "" - ); + static std::vector parse_string(const std::string &data, const std::string &filename = ""); + + /** + * Counterpart of parse_string that returns a string with a JSON representation of the ParseResult. + */ + static std::string parse_string_to_json(const std::string &data, const std::string &filename = ""); /** * Parses and analyzes the given file. @@ -101,16 +103,21 @@ class V1xAnalyzer { * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ - std::vector analyze_file( - const std::string &filename - ) const; + std::vector analyze_file(const std::string &filename) const; + + /** + * Counterpart of analyze_file that returns a string with a JSON representation of the AnalysisResult. + */ + std::string analyze_file_to_json(const std::string &filename) const; /** * Same as analyze_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ - std::vector analyze_string( - const std::string &data, - const std::string &filename = "" - ) const; + std::vector analyze_string(const std::string &data, const std::string &filename = "") const; + + /** + * Counterpart of analyze_string that returns a string with a JSON representation of the AnalysisResult. + */ + std::string analyze_string_to_json(const std::string &data, const std::string &filename = "") const; }; diff --git a/include/v3x/cqasm-analysis-result.hpp b/include/v3x/cqasm-analysis-result.hpp new file mode 100644 index 00000000..7a752e36 --- /dev/null +++ b/include/v3x/cqasm-analysis-result.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "cqasm-ast.hpp" +#include "cqasm-semantic.hpp" + +#include // runtime_error +#include // ostream +#include +#include + + +/** + * Namespace for the \ref cqasm::analyzer::Analyzer "Analyzer" class and support classes. + */ +namespace cqasm::v3x::analyzer { + +/** + * Exception thrown by AnalysisResult::unwrap() when the cQASM file fails to parse. + */ +class AnalysisFailed: public std::runtime_error { +public: + AnalysisFailed() : std::runtime_error("cQASM analysis failed") {}; +}; + +/** + * Analysis result class. + * + * An object of this type is returned by the various `analyze*()` methods of the Analyzer class. + * There are three possibilities: + * + * - Analysis was successful. + * In this case, \ref errors is empty, and \ref root contains a valid \ref semantic::Program "Program" tree. + * - Analysis failed. + * In this case, there is at least one string in \ref errors. + * \ref root may be an empty reference, a partial tree, or even a complete but somehow invalid tree. + * - Analysis failed spectacularly with an internal error, + * in which case an exception is thrown instead of this object being returned. + * + * If you don't want to manage printing error messages yourself and would like an exception upon failure in all cases, + * you can call unwrap() on the returned object. + */ +class AnalysisResult { +public: + /** + * Root node of the semantic tree, if analysis was successful. + * The node may be empty or incomplete if errors is non-empty. + * Use unwrap() to ensure it's complete if you just want to use it and + * don't care about things like pretty-printing error messages for your users + * in a different way than what unwrap() does for you. + */ + ast::One root; + + /** + * List of accumulated errors. + * Analysis was successful if and only if `errors.empty()`. + */ + std::vector errors; + + /** + * "Unwraps" the result (as you would in Rust) to get the program node or an exception. + * The exception is always an AnalysisFailed, deriving from std::runtime_error. + * The actual error messages are in this case first written to the given output stream, defaulting to stderr. + */ + ast::One unwrap(std::ostream &out = std::cerr) const; + + /** + * Returns a string with a JSON representation of the AnalysisResult. + */ + std::string to_json() const; +}; + +} // namespace cqasm::v3x::analyzer diff --git a/include/v3x/cqasm-analyzer.hpp b/include/v3x/cqasm-analyzer.hpp index e8365499..8dd7c76c 100644 --- a/include/v3x/cqasm-analyzer.hpp +++ b/include/v3x/cqasm-analyzer.hpp @@ -8,13 +8,15 @@ #pragma once +#include "cqasm-analysis-result.hpp" +#include "cqasm-analyzer.hpp" #include "cqasm-ast.hpp" #include "cqasm-parse-helper.hpp" #include "cqasm-resolver.hpp" #include "cqasm-semantic.hpp" -#include #include +#include /** @@ -22,57 +24,6 @@ */ namespace cqasm::v3x::analyzer { -/** - * Exception thrown by AnalysisResult::unwrap() when the cQASM file fails to - * parse. - */ -class AnalysisFailed: public std::runtime_error { -public: - AnalysisFailed() : std::runtime_error("cQASM analysis failed") {}; -}; - -/** - * Analysis result class. - * - * An object of this type is returned by the various `analyze*()` methods of the Analyzer class. - * There are three possibilities: - * - * - Analysis was successful. - * In this case, \ref errors is empty, and \ref root contains a valid \ref semantic::Program "Program" tree. - * - Analysis failed. - * In this case, there is at least one string in \ref errors. - * \ref root may be an empty reference, a partial tree, or even a complete but somehow invalid tree. - * - Analysis failed spectacularly with an internal error, - * in which case an exception is thrown instead of this object being returned. - * - * If you don't want to manage printing error messages yourself and would like an exception upon failure in all cases, - * you can call unwrap() on the returned object. - */ -class AnalysisResult { -public: - /** - * Root node of the semantic tree, if analysis was successful. - * The node may be empty or incomplete if errors is non-empty; - * use unwrap() to ensure it's complete if you just want to use it and - * don't care about things like pretty-printing error messages for your users - * in a different way than what unwrap() does for you. - */ - ast::One root; - - /** - * List of accumulated errors. - * Analysis was successful if and only if `errors.empty()`. - */ - std::vector errors; - - /** - * "Unwraps" the result (as you would in Rust) to get the program node or an exception. - * The exception is always an AnalysisFailed, deriving from std::runtime_error. - * The actual error messages are in this case first written to the given output stream, defaulting to stderr. - */ - ast::One unwrap(std::ostream &out = std::cerr) const; -}; - /** * Main class used for analyzing cQASM files. * diff --git a/include/v3x/cqasm-parse-result.hpp b/include/v3x/cqasm-parse-result.hpp index a3884642..546b22c4 100644 --- a/include/v3x/cqasm-parse-result.hpp +++ b/include/v3x/cqasm-parse-result.hpp @@ -9,7 +9,8 @@ #include "cqasm-annotations.hpp" #include "cqasm-ast.hpp" -#include +#include +#include /** @@ -34,6 +35,11 @@ class ParseResult { * Analysis was successful if and only if errors.empty(). */ std::vector errors; + + /** + * Returns a string with a JSON representation of the ParseResult. + */ + std::string to_json() const; }; } // namespace cqasm::v3x::parser diff --git a/include/v3x/cqasm-py.hpp b/include/v3x/cqasm-py.hpp index 3f774de1..ed32309c 100644 --- a/include/v3x/cqasm-py.hpp +++ b/include/v3x/cqasm-py.hpp @@ -15,7 +15,9 @@ // Forward declarations for internal types. -namespace cqasm::v3x::analyzer { class Analyzer; } +namespace cqasm::v3x::analyzer { + class Analyzer; +} /** * Main class for parsing and analyzing cQASM files with the v3.x API. @@ -37,10 +39,7 @@ class V3xAnalyzer { * the initial mappings and functions are not configurable at all. * The defaults for these are always used. */ - explicit V3xAnalyzer( - const std::string &max_version = "3.0", - bool without_defaults = false - ); + V3xAnalyzer(const std::string &max_version = "3.0", bool without_defaults = false); /** * std::unique_ptr requires T to be a complete class for the ~T operation. @@ -63,18 +62,23 @@ class V3xAnalyzer { * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ - static std::vector parse_file( - const std::string &filename - ); + static std::vector parse_file(const std::string &filename); + + /** + * Counterpart of parse_file that returns a string with a JSON representation of the ParseResult. + */ + static std::string parse_file_to_json(const std::string &filename); /** * Same as parse_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ - static std::vector parse_string( - const std::string &data, - const std::string &filename = "" - ); + static std::vector parse_string(const std::string &data, const std::string &filename = ""); + + /** + * Counterpart of parse_string that returns a string with a JSON representation of the ParseResult. + */ + static std::string parse_string_to_json(const std::string &data, const std::string &filename = ""); /** * Parses and analyzes the given file. @@ -85,16 +89,21 @@ class V3xAnalyzer { * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ - std::vector analyze_file( - const std::string &filename - ) const; + std::vector analyze_file(const std::string &filename) const; + + /** + * Counterpart of analyze_file that returns a string with a JSON representation of the AnalysisResult. + */ + std::string analyze_file_to_json(const std::string &filename) const; /** * Same as analyze_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ - std::vector analyze_string( - const std::string &data, - const std::string &filename = "" - ) const; + std::vector analyze_string(const std::string &data, const std::string &filename = "") const; + + /** + * Counterpart of analyze_string that returns a string with a JSON representation of the AnalysisResult. + */ + std::string analyze_string_to_json(const std::string &data, const std::string &filename = "") const; }; diff --git a/python/conda/meta.yaml b/python/conda/meta.yaml index 786ddee0..d87c120e 100644 --- a/python/conda/meta.yaml +++ b/python/conda/meta.yaml @@ -7,8 +7,6 @@ source: build: number: 0 - script_env: - - NPROCS requirements: build: diff --git a/python/module/cqasm/v1x/__init__.py b/python/module/cqasm/v1x/__init__.py index 062b1ee8..afd55856 100644 --- a/python/module/cqasm/v1x/__init__.py +++ b/python/module/cqasm/v1x/__init__.py @@ -1,13 +1,21 @@ +import cqasm.v1x.ast as ast +import cqasm.v1x.semantic as semantic import libQasm class Analyzer(libQasm.V1xAnalyzer): + # parse_file and parse_string are static methods because they do not change the status of the analyzer + # Instead, they just invoke free functions that create a temporary instance of a parser + # analyze_file and analyze_string are not static methods because they change the status of the analyzer + + # parse_file, parse_string, analyze_file, and analyze_string return a vector of strings + # If the length of the vector is 1, the string is a serialization of the AST + # Otherwise, it is a list of errors @staticmethod def parse_file(*args): ret = libQasm.V1xAnalyzer.parse_file(*args) if len(ret) == 1: - import cqasm.v1x.ast as ast serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") deserialized_ast = ast.Root.deserialize(serialized_ast_bytes) @@ -18,7 +26,6 @@ def parse_file(*args): def parse_string(*args): ret = libQasm.V1xAnalyzer.parse_string(*args) if len(ret) == 1: - import cqasm.v1x.ast as ast serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") deserialized_ast = ast.Root.deserialize(serialized_ast_bytes) @@ -28,19 +35,31 @@ def parse_string(*args): def analyze_file(self, *args): ret = super().analyze_file(*args) if len(ret) == 1: - import cqasm.v1x.semantic as semantic serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") - deserialized_ast = semantic.Root.deserialize(serialized_ast_bytes) + deserialized_ast = semantic.Program.deserialize(serialized_ast_bytes) return deserialized_ast return [str(error) for error in ret[1:]] def analyze_string(self, *args): ret = super().analyze_string(*args) if len(ret) == 1: - import cqasm.v1x.semantic as semantic serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") - deserialized_ast = semantic.Root.deserialize(serialized_ast_bytes) + deserialized_ast = semantic.Program.deserialize(serialized_ast_bytes) return deserialized_ast return [str(error) for error in ret[1:]] + + @staticmethod + def parse_file_to_json(*args): + return libQasm.V1xAnalyzer.parse_file_to_json(*args) + + @staticmethod + def parse_string_to_json(*args): + return libQasm.V1xAnalyzer.parse_string_to_json(*args) + + def analyze_file_to_json(self, *args): + return super().analyze_file_to_json(*args) + + def analyze_string_to_json(self, *args): + return super().analyze_string_to_json(*args) diff --git a/python/module/cqasm/v3x/__init__.py b/python/module/cqasm/v3x/__init__.py index 38a271c8..7cbfe6b0 100644 --- a/python/module/cqasm/v3x/__init__.py +++ b/python/module/cqasm/v3x/__init__.py @@ -1,13 +1,21 @@ +import cqasm.v3x.ast as ast +import cqasm.v3x.semantic as semantic import libQasm class Analyzer(libQasm.V3xAnalyzer): + # parse_file and parse_string are static methods because they do not change the status of the analyzer + # Instead, they just invoke free functions that create a temporary instance of a parser + # analyze_file and analyze_string are not static methods because they change the status of the analyzer + + # parse_file, parse_string, analyze_file, and analyze_string return a vector of strings + # If the length of the vector is 1, the string is a serialization of the AST + # Otherwise, it is a list of errors @staticmethod def parse_file(*args): ret = libQasm.V3xAnalyzer.parse_file(*args) if len(ret) == 1: - import cqasm.v3x.ast as ast serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") deserialized_ast = ast.Root.deserialize(serialized_ast_bytes) @@ -18,7 +26,6 @@ def parse_file(*args): def parse_string(*args): ret = libQasm.V3xAnalyzer.parse_string(*args) if len(ret) == 1: - import cqasm.v3x.ast as ast serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") deserialized_ast = ast.Root.deserialize(serialized_ast_bytes) @@ -28,7 +35,6 @@ def parse_string(*args): def analyze_file(self, *args): ret = super().analyze_file(*args) if len(ret) == 1: - import cqasm.v3x.semantic as semantic serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") deserialized_ast = semantic.Program.deserialize(serialized_ast_bytes) @@ -38,9 +44,22 @@ def analyze_file(self, *args): def analyze_string(self, *args): ret = super().analyze_string(*args) if len(ret) == 1: - import cqasm.v3x.semantic as semantic serialized_ast_str = str(ret[0]) serialized_ast_bytes = serialized_ast_str.encode(encoding='utf-8', errors="surrogateescape") deserialized_ast = semantic.Program.deserialize(serialized_ast_bytes) return deserialized_ast return [str(error) for error in ret[1:]] + + @staticmethod + def parse_file_to_json(*args): + return libQasm.V3xAnalyzer.parse_file_to_json(*args) + + @staticmethod + def parse_string_to_json(*args): + return libQasm.V3xAnalyzer.parse_string_to_json(*args) + + def analyze_file_to_json(self, *args): + return super().analyze_file_to_json(*args) + + def analyze_string_to_json(self, *args): + return super().analyze_string_to_json(*args) diff --git a/res/v1x/parsing/grammar/expression-recovery/ast.golden.txt b/res/v1x/parsing/grammar/expression_recovery/ast.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/expression-recovery/ast.golden.txt rename to res/v1x/parsing/grammar/expression_recovery/ast.golden.txt diff --git a/res/v1x/parsing/grammar/expression-recovery/input.cq b/res/v1x/parsing/grammar/expression_recovery/input.cq similarity index 100% rename from res/v1x/parsing/grammar/expression-recovery/input.cq rename to res/v1x/parsing/grammar/expression_recovery/input.cq diff --git a/res/v1x/parsing/grammar/index-negative/ast.golden.txt b/res/v1x/parsing/grammar/index_negative/ast.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/index-negative/ast.golden.txt rename to res/v1x/parsing/grammar/index_negative/ast.golden.txt diff --git a/res/v1x/parsing/grammar/index-negative/input.cq b/res/v1x/parsing/grammar/index_negative/input.cq similarity index 100% rename from res/v1x/parsing/grammar/index-negative/input.cq rename to res/v1x/parsing/grammar/index_negative/input.cq diff --git a/res/v1x/parsing/grammar/index-positive/ast.golden.txt b/res/v1x/parsing/grammar/index_positive/ast.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/index-positive/ast.golden.txt rename to res/v1x/parsing/grammar/index_positive/ast.golden.txt diff --git a/res/v1x/parsing/grammar/index-positive/input.cq b/res/v1x/parsing/grammar/index_positive/input.cq similarity index 100% rename from res/v1x/parsing/grammar/index-positive/input.cq rename to res/v1x/parsing/grammar/index_positive/input.cq diff --git a/res/v1x/parsing/grammar/index-positive/semantic.1.1.golden.txt b/res/v1x/parsing/grammar/index_positive/semantic.1.1.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/index-positive/semantic.1.1.golden.txt rename to res/v1x/parsing/grammar/index_positive/semantic.1.1.golden.txt index c0893b28..047ab0a0 100644 --- a/res/v1x/parsing/grammar/index-positive/semantic.1.1.golden.txt +++ b/res/v1x/parsing/grammar/index_positive/semantic.1.1.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..5:1 + api_version: 1.1 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -94,6 +95,5 @@ Program( # input.cq:1:1..5:1 ] mappings: [] variables: [] - api_version: 1.1 ) diff --git a/res/v1x/parsing/grammar/index-positive/semantic.1.2.golden.txt b/res/v1x/parsing/grammar/index_positive/semantic.1.2.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/index-positive/semantic.1.2.golden.txt rename to res/v1x/parsing/grammar/index_positive/semantic.1.2.golden.txt index 23b6e33f..3b05b875 100644 --- a/res/v1x/parsing/grammar/index-positive/semantic.1.2.golden.txt +++ b/res/v1x/parsing/grammar/index_positive/semantic.1.2.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..5:1 + api_version: 1.2 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -98,6 +99,5 @@ Program( # input.cq:1:1..5:1 ] mappings: [] variables: [] - api_version: 1.2 ) diff --git a/res/v1x/parsing/grammar/subcircuit-default/ast.golden.txt b/res/v1x/parsing/grammar/subcircuit_default/ast.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-default/ast.golden.txt rename to res/v1x/parsing/grammar/subcircuit_default/ast.golden.txt diff --git a/res/v1x/parsing/grammar/subcircuit-default/input.cq b/res/v1x/parsing/grammar/subcircuit_default/input.cq similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-default/input.cq rename to res/v1x/parsing/grammar/subcircuit_default/input.cq diff --git a/res/v1x/parsing/grammar/subcircuit-default/semantic.1.0.golden.txt b/res/v1x/parsing/grammar/subcircuit_default/semantic.1.0.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-default/semantic.1.0.golden.txt rename to res/v1x/parsing/grammar/subcircuit_default/semantic.1.0.golden.txt index 97325b36..10330f4c 100644 --- a/res/v1x/parsing/grammar/subcircuit-default/semantic.1.0.golden.txt +++ b/res/v1x/parsing/grammar/subcircuit_default/semantic.1.0.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..15:1 + api_version: 1.0 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -172,6 +173,5 @@ Program( # input.cq:1:1..15:1 ] mappings: [] variables: [] - api_version: 1.0 ) diff --git a/res/v1x/parsing/grammar/subcircuit-default/semantic.1.1.golden.txt b/res/v1x/parsing/grammar/subcircuit_default/semantic.1.1.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-default/semantic.1.1.golden.txt rename to res/v1x/parsing/grammar/subcircuit_default/semantic.1.1.golden.txt index 717211bb..86e670f9 100644 --- a/res/v1x/parsing/grammar/subcircuit-default/semantic.1.1.golden.txt +++ b/res/v1x/parsing/grammar/subcircuit_default/semantic.1.1.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..15:1 + api_version: 1.1 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -172,6 +173,5 @@ Program( # input.cq:1:1..15:1 ] mappings: [] variables: [] - api_version: 1.1 ) diff --git a/res/v1x/parsing/grammar/subcircuit-default/semantic.1.2.golden.txt b/res/v1x/parsing/grammar/subcircuit_default/semantic.1.2.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-default/semantic.1.2.golden.txt rename to res/v1x/parsing/grammar/subcircuit_default/semantic.1.2.golden.txt index 0eb4c87f..57a63250 100644 --- a/res/v1x/parsing/grammar/subcircuit-default/semantic.1.2.golden.txt +++ b/res/v1x/parsing/grammar/subcircuit_default/semantic.1.2.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..15:1 + api_version: 1.2 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -184,6 +185,5 @@ Program( # input.cq:1:1..15:1 ] mappings: [] variables: [] - api_version: 1.2 ) diff --git a/res/v1x/parsing/grammar/subcircuit-no-default/ast.golden.txt b/res/v1x/parsing/grammar/subcircuit_no_default/ast.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-no-default/ast.golden.txt rename to res/v1x/parsing/grammar/subcircuit_no_default/ast.golden.txt diff --git a/res/v1x/parsing/grammar/subcircuit-no-default/input.cq b/res/v1x/parsing/grammar/subcircuit_no_default/input.cq similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-no-default/input.cq rename to res/v1x/parsing/grammar/subcircuit_no_default/input.cq diff --git a/res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.0.golden.txt b/res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.0.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.0.golden.txt rename to res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.0.golden.txt index f936243a..252530a0 100644 --- a/res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.0.golden.txt +++ b/res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.0.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..11:1 + api_version: 1.0 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -75,6 +76,5 @@ Program( # input.cq:1:1..11:1 ] mappings: [] variables: [] - api_version: 1.0 ) diff --git a/res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.1.golden.txt b/res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.1.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.1.golden.txt rename to res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.1.golden.txt index dca39c96..2f6dd317 100644 --- a/res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.1.golden.txt +++ b/res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.1.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..11:1 + api_version: 1.1 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -75,6 +76,5 @@ Program( # input.cq:1:1..11:1 ] mappings: [] variables: [] - api_version: 1.1 ) diff --git a/res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.2.golden.txt b/res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.2.golden.txt similarity index 100% rename from res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.2.golden.txt rename to res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.2.golden.txt index f9fd7fc6..9369b8c3 100644 --- a/res/v1x/parsing/grammar/subcircuit-no-default/semantic.1.2.golden.txt +++ b/res/v1x/parsing/grammar/subcircuit_no_default/semantic.1.2.golden.txt @@ -1,5 +1,6 @@ SUCCESS Program( # input.cq:1:1..11:1 + api_version: 1.2 version: < Version( # input.cq:1:9..12 items: 1.0 @@ -87,6 +88,5 @@ Program( # input.cq:1:1..11:1 ] mappings: [] variables: [] - api_version: 1.2 ) diff --git a/res/v3x/parsing/sgmq/cnot_q_at_0_and_1_q_at_3_and_2/ast.golden.json b/res/v3x/parsing/sgmq/cnot_q_at_0_and_1_q_at_3_and_2/ast.golden.json new file mode 100644 index 00000000..249bd8e0 --- /dev/null +++ b/res/v3x/parsing/sgmq/cnot_q_at_0_and_1_q_at_3_and_2/ast.golden.json @@ -0,0 +1 @@ +{"Program":{"version":{"Version":{"items":"3","source_location":"input.cq:1:9..10"}},"statements":{"StatementList":{"items":[{"Variables":{"names":[{"Identifier":{"name":"q"}}],"typ":{"Identifier":{"name":"qubit"}},"size":{"IntegerLiteral":{"value":"4"}},"annotations":"[]","source_location":"input.cq:3:10..11"}},{"Instruction":{"name":{"Identifier":{"name":"cnot"}},"condition":"-","operands":{"ExpressionList":{"items":[{"Index":{"expr":{"Identifier":{"name":"q"}},"indices":{"IndexList":{"items":[{"IndexItem":{"index":{"IntegerLiteral":{"value":"0","source_location":"input.cq:5:8..9"}}}},{"IndexItem":{"index":{"IntegerLiteral":{"value":"1","source_location":"input.cq:5:11..12"}}}}]}},"source_location":"input.cq:5:6..7"}},{"Index":{"expr":{"Identifier":{"name":"q"}},"indices":{"IndexList":{"items":[{"IndexItem":{"index":{"IntegerLiteral":{"value":"3","source_location":"input.cq:5:17..18"}}}},{"IndexItem":{"index":{"IntegerLiteral":{"value":"2","source_location":"input.cq:5:19..20"}}}}]}},"source_location":"input.cq:5:15..16"}}]}},"annotations":"[]","source_location":"input.cq:5:1..5"}}]}}}} \ No newline at end of file diff --git a/res/v3x/parsing/sgmq/cnot_q_at_0_and_1_q_at_3_and_2/semantic.3.0.golden.json b/res/v3x/parsing/sgmq/cnot_q_at_0_and_1_q_at_3_and_2/semantic.3.0.golden.json new file mode 100644 index 00000000..41fafc05 --- /dev/null +++ b/res/v3x/parsing/sgmq/cnot_q_at_0_and_1_q_at_3_and_2/semantic.3.0.golden.json @@ -0,0 +1 @@ +{"Program":{"api_version":"3.0","version":{"Version":{"items":"3"}},"statements":[{"Instruction":{"instruction":"cnot(qubit array, qubit array)","name":"cnot","operands":[{"IndexRef":{"variable":{"Variable":{"name":"q","typ":{"QubitArray":{"size":"4"}},"annotations":"[]"}},"indices":[{"ConstInt":{"value":"0"}},{"ConstInt":{"value":"1"}}]}},{"IndexRef":{"variable":{"Variable":{"name":"q","typ":{"QubitArray":{"size":"4"}},"annotations":"[]"}},"indices":[{"ConstInt":{"value":"3"}},{"ConstInt":{"value":"2"}}]}}],"condition":{"ConstBool":{"value":"1"}},"annotations":"[]"}}],"variables":[{"Variable":{"name":"q","typ":{"QubitArray":{"size":"4"}},"annotations":"[]"}}]}} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 292f6e39..7306d161 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -67,7 +67,7 @@ endif() # This exposes the generate_tree() function. FetchContent_Declare(tree-gen GIT_REPOSITORY https://github.com/QuTech-Delft/tree-gen.git - GIT_TAG "9f54878240f6ae5df9c35cbf273cfbfb6f76679f" + GIT_TAG "a661b4e91e0f537073fe48f0b9c032f52c5ab20a" ) FetchContent_MakeAvailable(tree-gen) diff --git a/src/v1x/CMakeLists.txt b/src/v1x/CMakeLists.txt index 055492f5..c0767f4b 100644 --- a/src/v1x/CMakeLists.txt +++ b/src/v1x/CMakeLists.txt @@ -1,9 +1,11 @@ # List of non-generated sources. set(CQASM_V1X_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-analysis-result.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-analyzer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-error-model.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-instruction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-parse-helper.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-parse-result.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-primitives.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-py.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-resolver.cpp" diff --git a/src/v1x/cqasm-analysis-result.cpp b/src/v1x/cqasm-analysis-result.cpp new file mode 100644 index 00000000..3c17ffaa --- /dev/null +++ b/src/v1x/cqasm-analysis-result.cpp @@ -0,0 +1,11 @@ +#include "cqasm-utils.hpp" +#include "v1x/cqasm-analysis-result.hpp" + + +namespace cqasm::v1x::analyzer { + +std::string AnalysisResult::to_json() const { + return cqasm::utils::to_json(*this); +} + +} // namespace cqasm::v1x::analyzer diff --git a/src/v1x/cqasm-parse-result.cpp b/src/v1x/cqasm-parse-result.cpp new file mode 100644 index 00000000..ed623c82 --- /dev/null +++ b/src/v1x/cqasm-parse-result.cpp @@ -0,0 +1,11 @@ +#include "cqasm-utils.hpp" +#include "v1x/cqasm-parse-result.hpp" + + +namespace cqasm::v1x::parser { + +std::string ParseResult::to_json() const { + return cqasm::utils::to_json(*this); +} + +} // namespace cqasm::v1x::parser diff --git a/src/v1x/cqasm-py.cpp b/src/v1x/cqasm-py.cpp index a869d8cc..8a2b4c9b 100644 --- a/src/v1x/cqasm-py.cpp +++ b/src/v1x/cqasm-py.cpp @@ -2,6 +2,7 @@ * Implementation for the internal Python-wrapped functions and classes. */ +#include "cqasm-utils.hpp" #include "cqasm-version.hpp" #include "v1x/cqasm-analyzer.hpp" #include "v1x/cqasm-parse-helper.hpp" @@ -64,10 +65,7 @@ void V1xAnalyzer::register_instruction( * Registers an error model. * The arguments are passed straight to error_model::ErrorModel's constructor. */ -void V1xAnalyzer::register_error_model( - const std::string &name, - const std::string ¶m_types -) { +void V1xAnalyzer::register_error_model(const std::string &name, const std::string ¶m_types) { analyzer->register_error_model(name, param_types); } @@ -79,9 +77,7 @@ void V1xAnalyzer::register_error_model( * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ -std::vector V1xAnalyzer::parse_file( - const std::string &filename -) { +std::vector V1xAnalyzer::parse_file(const std::string &filename) { if (auto parse_result = v1x::parser::parse_file(filename); parse_result.errors.empty()) { return std::vector{ tree::base::serialize(parse_result.root) }; } else { @@ -90,14 +86,18 @@ std::vector V1xAnalyzer::parse_file( } } +/** + * Counterpart of parse_file that returns a string with a JSON representation of the ParseResult. + */ +std::string V1xAnalyzer::parse_file_to_json(const std::string &filename) { + return cqasm::utils::to_json(v1x::parser::parse_file(filename)); +} + /** * Same as parse_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ -std::vector V1xAnalyzer::parse_string( - const std::string &data, - const std::string &filename -) { +std::vector V1xAnalyzer::parse_string(const std::string &data, const std::string &filename) { if (auto parse_result = v1x::parser::parse_string(data, filename); parse_result.errors.empty()) { return std::vector{ tree::base::serialize(parse_result.root) }; } else { @@ -106,6 +106,13 @@ std::vector V1xAnalyzer::parse_string( } } +/** + * Counterpart of parse_string that returns a string with a JSON representation of the ParseResult. + */ +std::string V1xAnalyzer::parse_string_to_json(const std::string &data, const std::string &filename) { + return cqasm::utils::to_json(v1x::parser::parse_string(data, filename)); +} + /** * Parses and analyzes the given file. * If the file is written in a later file version, @@ -115,9 +122,7 @@ std::vector V1xAnalyzer::parse_string( * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ -[[nodiscard]] std::vector V1xAnalyzer::analyze_file( - const std::string &filename -) const { +[[nodiscard]] std::vector V1xAnalyzer::analyze_file(const std::string &filename) const { auto analysis_result = analyzer->analyze( [=](){ return cqasm::version::parse_file(filename); }, [=](){ return v1x::parser::parse_file(filename); } @@ -130,14 +135,21 @@ std::vector V1xAnalyzer::parse_string( } } +/** + * Counterpart of analyze_file that returns a string with a JSON representation of the AnalysisResult. + */ +[[nodiscard]] std::string V1xAnalyzer::analyze_file_to_json(const std::string &filename) const { + return cqasm::utils::to_json(analyzer->analyze( + [=](){ return cqasm::version::parse_file(filename); }, + [=](){ return v1x::parser::parse_file(filename); } + )); +} + /** * Same as analyze_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ -[[nodiscard]] std::vector V1xAnalyzer::analyze_string( - const std::string &data, - const std::string &filename -) const { +[[nodiscard]] std::vector V1xAnalyzer::analyze_string(const std::string &data, const std::string &filename) const { auto analysis_result = analyzer->analyze( [=](){ return cqasm::version::parse_string(data, filename); }, [=](){ return v1x::parser::parse_string(data, filename); } @@ -149,3 +161,13 @@ std::vector V1xAnalyzer::parse_string( return analysis_result.errors; } } + +/** + * Counterpart of analyze_string that returns a string with a JSON representation of the AnalysisResult. + */ +[[nodiscard]] std::string V1xAnalyzer::analyze_string_to_json(const std::string &data, const std::string &filename) const { + return cqasm::utils::to_json(analyzer->analyze( + [=](){ return cqasm::version::parse_string(data, filename); }, + [=](){ return v1x::parser::parse_string(data, filename); } + )); +} diff --git a/src/v3x/CMakeLists.txt b/src/v3x/CMakeLists.txt index b034886b..8bb3fc78 100644 --- a/src/v3x/CMakeLists.txt +++ b/src/v3x/CMakeLists.txt @@ -4,9 +4,11 @@ set(CQASM_V3X_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/BuildTreeGenAstVisitor.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CustomErrorListener.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ScannerAntlr.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-analysis-result.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-analyzer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-instruction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-parse-helper.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-parse-result.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-primitives.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-py.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/cqasm-resolver.cpp" diff --git a/src/v3x/cqasm-analysis-result.cpp b/src/v3x/cqasm-analysis-result.cpp new file mode 100644 index 00000000..d1418309 --- /dev/null +++ b/src/v3x/cqasm-analysis-result.cpp @@ -0,0 +1,11 @@ +#include "cqasm-utils.hpp" +#include "v3x/cqasm-analysis-result.hpp" + + +namespace cqasm::v3x::analyzer { + +std::string AnalysisResult::to_json() const { + return cqasm::utils::to_json(*this); +} + +} // namespace cqasm::v3x::analyzer diff --git a/src/v3x/cqasm-parse-result.cpp b/src/v3x/cqasm-parse-result.cpp new file mode 100644 index 00000000..b28b3f2a --- /dev/null +++ b/src/v3x/cqasm-parse-result.cpp @@ -0,0 +1,11 @@ +#include "cqasm-utils.hpp" +#include "v3x/cqasm-parse-result.hpp" + + +namespace cqasm::v3x::parser { + +std::string ParseResult::to_json() const { + return cqasm::utils::to_json(*this); +} + +} // namespace cqasm::v3x::parser diff --git a/src/v3x/cqasm-py.cpp b/src/v3x/cqasm-py.cpp index e13a4993..7da1d098 100644 --- a/src/v3x/cqasm-py.cpp +++ b/src/v3x/cqasm-py.cpp @@ -2,6 +2,7 @@ * Implementation for the internal Python-wrapped functions and classes. */ +#include "cqasm-utils.hpp" #include "cqasm-version.hpp" #include "v3x/cqasm-analyzer.hpp" #include "v3x/cqasm-parse-helper.hpp" @@ -54,9 +55,7 @@ void V3xAnalyzer::register_instruction(const std::string &name, const std::strin * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ -std::vector V3xAnalyzer::parse_file( - const std::string &filename -) { +std::vector V3xAnalyzer::parse_file(const std::string &filename) { if (auto parse_result = v3x::parser::parse_file(filename); parse_result.errors.empty()) { return std::vector{ tree::base::serialize(parse_result.root) }; } else { @@ -65,14 +64,18 @@ std::vector V3xAnalyzer::parse_file( } } +/** + * Counterpart of parse_file that returns a string with a JSON representation of the ParseResult. + */ +std::string V3xAnalyzer::parse_file_to_json(const std::string &filename) { + return cqasm::utils::to_json(v3x::parser::parse_file(filename)); +} + /** * Same as parse_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ -std::vector V3xAnalyzer::parse_string( - const std::string &data, - const std::string &filename -) { +std::vector V3xAnalyzer::parse_string(const std::string &data, const std::string &filename) { if (auto parse_result = v3x::parser::parse_string(data, filename); parse_result.errors.empty()) { return std::vector{ tree::base::serialize(parse_result.root) }; } else { @@ -81,6 +84,13 @@ std::vector V3xAnalyzer::parse_string( } } +/** + * Counterpart of parse_string that returns a string with a JSON representation of the ParseResult. + */ +std::string V3xAnalyzer::parse_string_to_json(const std::string &data, const std::string &filename) { + return cqasm::utils::to_json(v3x::parser::parse_string(data, filename)); +} + /** * Parses and analyzes the given file. * If the file is written in a later file version, @@ -90,9 +100,7 @@ std::vector V3xAnalyzer::parse_string( * Any additional strings represent error messages. * Notice that the AST and error messages won't be available at the same time. */ -std::vector V3xAnalyzer::analyze_file( - const std::string &filename -) const { +std::vector V3xAnalyzer::analyze_file(const std::string &filename) const { auto analysis_result = analyzer->analyze( [=](){ return cqasm::version::parse_file(filename); }, [=](){ return v3x::parser::parse_file(filename); } @@ -105,14 +113,21 @@ std::vector V3xAnalyzer::analyze_file( } } +/** + * Counterpart of analyze_file that returns a string with a JSON representation of the AnalysisResult. + */ +[[nodiscard]] std::string V3xAnalyzer::analyze_file_to_json(const std::string &filename) const { + return cqasm::utils::to_json(analyzer->analyze( + [=](){ return cqasm::version::parse_file(filename); }, + [=](){ return v3x::parser::parse_file(filename); } + )); +} + /** * Same as analyze_file(), but instead receives the file contents directly. * The filename, if specified, is only used when reporting errors. */ -std::vector V3xAnalyzer::analyze_string( - const std::string &data, - const std::string &filename -) const { +std::vector V3xAnalyzer::analyze_string(const std::string &data, const std::string &filename) const { auto analysis_result = analyzer->analyze( [=](){ return cqasm::version::parse_string(data, filename); }, [=](){ return v3x::parser::parse_string(data, filename); } @@ -124,3 +139,13 @@ std::vector V3xAnalyzer::analyze_string( return analysis_result.errors; } } + +/** + * Counterpart of analyze_string that returns a string with a JSON representation of the AnalysisResult. + */ +[[nodiscard]] std::string V3xAnalyzer::analyze_string_to_json(const std::string &data, const std::string &filename) const { + return cqasm::utils::to_json(analyzer->analyze( + [=](){ return cqasm::version::parse_string(data, filename); }, + [=](){ return v3x::parser::parse_string(data, filename); } + )); +} diff --git a/test/cqasm-utils.cpp b/test/cqasm-utils.cpp index 7bbeabbf..c455d2ed 100644 --- a/test/cqasm-utils.cpp +++ b/test/cqasm-utils.cpp @@ -1,7 +1,11 @@ #include "cqasm-utils.hpp" +#include "v1x/cqasm.hpp" // default_analyzer +#include "v3x/cqasm.hpp" // default_analyzer +#include #include +namespace fs = std::filesystem; using namespace cqasm::utils; @@ -21,3 +25,80 @@ TEST(equal_case_insensitive, both_contain_lowercase_and_uppercase) { TEST(equal_case_insensitive, both_contain_numbers) { EXPECT_TRUE(equal_case_insensitive("[abCD!]", "[ABcd!]")); } TEST(equal_case_insensitive, non_contain_letters) { EXPECT_TRUE(equal_case_insensitive("123", "123")); } TEST(equal_case_insensitive, different_strings) { EXPECT_FALSE(equal_case_insensitive("123", "ABC")); } + + +TEST(to_json, v1x_parser_errors) { + auto input_file_path = fs::path{"res"}/"v1x"/"parsing"/"grammar"/"expression_recovery"/"input.cq"; + cqasm::v1x::analyzer::Analyzer analyzer{}; + auto semantic_ast_result = analyzer.analyze_file(input_file_path.generic_string()); + auto json_result = to_json(semantic_ast_result); + auto expected_json_result = std::string{ + R"delim({"errors":["res/v1x/parsing/grammar/expression_recovery/input.cq:4:21: syntax error, unexpected ','","res/v1x/parsing/grammar/expression_recovery/input.cq:4:31: syntax error, unexpected ','","res/v1x/parsing/grammar/expression_recovery/input.cq:4:37: syntax error, unexpected ')', expecting ']'","res/v1x/parsing/grammar/expression_recovery/input.cq:4:61: syntax error, unexpected ','","res/v1x/parsing/grammar/expression_recovery/input.cq:4:67: syntax error, unexpected ')', expecting ',' or ']'","Failed to parse res/v1x/parsing/grammar/expression_recovery/input.cq"]})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} +TEST(to_json, v1x_parser_ast) { + auto input_file_path = fs::path{"res"}/"v1x"/"parsing"/"misc"/"wait_not_ok_1"/"input.cq"; + auto ast_result = cqasm::v1x::parser::parse_file(input_file_path.generic_string()); + auto json_result = to_json(ast_result); + auto expected_json_result = std::string{ + R"delim({"Program":{"version":{"Version":{"items":"1.0","source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:1:9..12"}},"num_qubits":{"IntegerLiteral":{"value":"2","source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:2:8..9"}},"statements":{"StatementList":{"items":[{"Bundle":{"items":[{"Instruction":{"name":{"Identifier":{"name":"wait","source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:4:1..5"}},"condition":"-","operands":{"ExpressionList":{"items":[{"IntegerLiteral":{"value":"1","source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:4:6..7"}}],"source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:4:6..7"}},"annotations":"[]","source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:4:1..7"}}],"annotations":"[]","source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:4:1..7"}}],"source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:2:1..5:7"}},"source_location":"res/v1x/parsing/misc/wait_not_ok_1/input.cq:1:1..5:1"}})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} +TEST(to_json, v1x_analyzer_errors) { + auto input_file_path = fs::path{"res"}/"v1x"/"parsing"/"misc"/"wait_not_ok_1"/"input.cq"; + auto semantic_ast_result = cqasm::v1x::default_analyzer().analyze_file(input_file_path.generic_string()); + auto json_result = to_json(semantic_ast_result); + auto expected_json_result = std::string{ + R"delim({"errors":["Error at res/v1x/parsing/misc/wait_not_ok_1/input.cq:4:1..7: failed to resolve overload for wait with argument pack (int)"]})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} +TEST(to_json, v1x_analyzer_ast) { + auto input_file_path = fs::path{"res"}/"v1x"/"parsing"/"grammar"/"map"/"input.cq"; + auto semantic_ast_result = cqasm::v1x::default_analyzer().analyze_file(input_file_path.generic_string()); + auto json_result = to_json(semantic_ast_result); + auto expected_json_result = std::string{ + R"delim({"Program":{"api_version":"1.0","version":{"Version":{"items":"1.0","source_location":"res/v1x/parsing/grammar/map/input.cq:1:9..12"}},"num_qubits":"10","error_model":"-","subcircuits":"[]","mappings":[{"Mapping":{"name":"three","value":{"ConstInt":{"value":"3","source_location":"res/v1x/parsing/grammar/map/input.cq:4:5..6"}},"annotations":[{"AnnotationData":{"interface":"first","operation":"annot","operands":"[]","source_location":"res/v1x/parsing/grammar/map/input.cq:4:15..26"}}],"source_location":"res/v1x/parsing/grammar/map/input.cq:4:1..26"}},{"Mapping":{"name":"also_three","value":{"ConstInt":{"value":"3","source_location":"res/v1x/parsing/grammar/map/input.cq:5:18..23"}},"annotations":[{"AnnotationData":{"interface":"second","operation":"annot","operands":"[]","source_location":"res/v1x/parsing/grammar/map/input.cq:5:25..37"}},{"AnnotationData":{"interface":"third","operation":"annot","operands":"[]","source_location":"res/v1x/parsing/grammar/map/input.cq:5:39..50"}}],"source_location":"res/v1x/parsing/grammar/map/input.cq:5:1..50"}}],"variables":"[]","source_location":"res/v1x/parsing/grammar/map/input.cq:1:1..6:1"}})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} + +TEST(to_json, v3x_parser_errors) { + auto input_file_path = fs::path{"res"}/"v3x"/"parsing"/"bit_array_definition"/"bit_array_of_3_14"/"input.cq"; + cqasm::v3x::analyzer::Analyzer analyzer{}; + auto semantic_ast_result = analyzer.analyze_file(input_file_path.generic_string()); + auto json_result = to_json(semantic_ast_result); + auto expected_json_result = std::string{ + R"delim({"errors":[":3:5: mismatched input '3.14' expecting INTEGER_LITERAL"]})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} +TEST(to_json, v3x_parser_ast) { + auto input_file_path = fs::path{"res"}/"v3x"/"parsing"/"bit_array_definition"/"bit_array_of_0_b"/"input.cq"; + auto ast_result = cqasm::v3x::parser::parse_file(input_file_path.generic_string()); + auto json_result = to_json(ast_result); + auto expected_json_result = std::string{ + R"delim({"Program":{"version":{"Version":{"items":"3","source_location":":1:9..10"}},"statements":{"StatementList":{"items":[{"Variables":{"names":[{"Identifier":{"name":"b"}}],"typ":{"Identifier":{"name":"bit"}},"size":{"IntegerLiteral":{"value":"0"}},"annotations":"[]","source_location":":3:8..9"}}]}}}})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} +TEST(to_json, v3x_analyzer_errors) { + auto input_file_path = fs::path{"res"}/"v3x"/"parsing"/"bit_array_definition"/"bit_array_of_0_b"/"input.cq"; + auto semantic_ast_result = cqasm::v3x::default_analyzer().analyze_file(input_file_path.generic_string()); + auto json_result = to_json(semantic_ast_result); + auto expected_json_result = std::string{ + R"delim({"errors":["Error at :3:8..9: declaring bit array of size <= 0"]})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} +TEST(to_json, v3x_analyzer_ast) { + auto input_file_path = fs::path{"res"}/"v3x"/"parsing"/"bit_array_definition"/"bit_array_of_17_b"/"input.cq"; + auto semantic_ast_result = cqasm::v3x::default_analyzer().analyze_file(input_file_path.generic_string()); + auto json_result = to_json(semantic_ast_result); + auto expected_json_result = std::string{ + R"delim({"Program":{"api_version":"3.0","version":{"Version":{"items":"3"}},"statements":"[]","variables":[{"Variable":{"name":"b","typ":{"BitArray":{"size":"17"}},"annotations":"[]"}}]}})delim" + }; + EXPECT_EQ(json_result, expected_json_result); +} diff --git a/test/v1x/__init__.py b/test/v1x/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/v1x/python/__init__.py b/test/v1x/python/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/v1x/python/test_analysis_result.py b/test/v1x/python/test_analysis_result.py new file mode 100644 index 00000000..791aeb5f --- /dev/null +++ b/test/v1x/python/test_analysis_result.py @@ -0,0 +1,21 @@ +import unittest + +import cqasm.v1x as cq + + +class TestAnalysisResult(unittest.TestCase): + def test_to_json_with_analyzer_errors(self): + # res/v1x/parsing/misc/wait_not_ok_1 + program_str = "version 1.0; qubits 2; wait 1" + v1x_analyzer = cq.Analyzer() + actual_errors_json = v1x_analyzer.analyze_string_to_json(program_str) + expected_errors_json = '''{"errors":["Error at :1:24..30: failed to resolve overload for wait with argument pack (int)"]}''' + self.assertEqual(actual_errors_json, expected_errors_json) + + def test_to_json_with_analyzer_ast(self): + # res/v1x/parsing/grammar/map + program_str = "version 1.0; qubits 10; map 3, three @first.annot; map also_three = three @second.annot @third.annot" + v1x_analyzer = cq.Analyzer() + actual_ast_json = v1x_analyzer.analyze_string_to_json(program_str) + expected_ast_json = '''{"Program":{"api_version":"1.0","version":{"Version":{"items":"1.0","source_location":":1:9..12"}},"num_qubits":"10","error_model":"-","subcircuits":"[]","mappings":[{"Mapping":{"name":"three","value":{"ConstInt":{"value":"3","source_location":":1:29..30"}},"annotations":[{"AnnotationData":{"interface":"first","operation":"annot","operands":"[]","source_location":":1:39..50"}}],"source_location":":1:25..50"}},{"Mapping":{"name":"also_three","value":{"ConstInt":{"value":"3","source_location":":1:69..74"}},"annotations":[{"AnnotationData":{"interface":"second","operation":"annot","operands":"[]","source_location":":1:76..88"}},{"AnnotationData":{"interface":"third","operation":"annot","operands":"[]","source_location":":1:90..101"}}],"source_location":":1:52..101"}}],"variables":"[]","source_location":":1:1..101"}}''' + self.assertEqual(actual_ast_json, expected_ast_json) diff --git a/test/v1x/python/test_parse_result.py b/test/v1x/python/test_parse_result.py new file mode 100644 index 00000000..08b0e19b --- /dev/null +++ b/test/v1x/python/test_parse_result.py @@ -0,0 +1,21 @@ +import unittest + +import cqasm.v1x as cq + + +class TestParseResult(unittest.TestCase): + def test_to_json_with_parser_errors(self): + # res/v1x/parsing/grammar/expression_recovery + program_str = "version 1.0; qubits 10; display @test.test([, 1, 2, 3+, 4, 5) @test.test([, 1, 2, 3+, 4, 5)" + v1x_analyzer = cq.Analyzer() + actual_errors_json = v1x_analyzer.parse_string_to_json(program_str) + expected_errors_json = '''{"errors":[":1:45: syntax error, unexpected ','",":1:55: syntax error, unexpected ','",":1:61: syntax error, unexpected ')', expecting ']'",":1:85: syntax error, unexpected ','",":1:91: syntax error, unexpected ')', expecting ',' or ']'","Failed to parse "]}''' + self.assertEqual(actual_errors_json, expected_errors_json) + + def test_to_json_with_parser_ast(self): + # res/v1x/parsing/misc/wait_not_ok_1 + program_str = "version 1.0; qubits 2; wait 1" + v1x_analyzer = cq.Analyzer() + actual_ast_json = v1x_analyzer.parse_string_to_json(program_str) + expected_ast_json = '''{"Program":{"version":{"Version":{"items":"1.0","source_location":":1:9..12"}},"num_qubits":{"IntegerLiteral":{"value":"2","source_location":":1:21..22"}},"statements":{"StatementList":{"items":[{"Bundle":{"items":[{"Instruction":{"name":{"Identifier":{"name":"wait","source_location":":1:24..28"}},"condition":"-","operands":{"ExpressionList":{"items":[{"IntegerLiteral":{"value":"1","source_location":":1:29..30"}}],"source_location":":1:29..30"}},"annotations":"[]","source_location":":1:24..30"}}],"annotations":"[]","source_location":":1:24..30"}}],"source_location":":1:22..30"}},"source_location":":1:1..30"}}''' + self.assertEqual(actual_ast_json, expected_ast_json) diff --git a/test/v3x/cpp/parsing.cpp b/test/v3x/cpp/parsing.cpp index 83bf9bf6..b7c06cc3 100644 --- a/test/v3x/cpp/parsing.cpp +++ b/test/v3x/cpp/parsing.cpp @@ -32,21 +32,33 @@ class ParsingTest : public ::testing::Test { cq3x::parser::ParseResult parse_result{}; parse_result = cq3x::parser::parse_string(input, "input.cq"); - // Check the parse result - std::string ast_actual_file_contents{}; - if (parse_result.errors.empty()) { - ast_actual_file_contents = fmt::format("SUCCESS\n{}\n", *parse_result.root); - } else { - ast_actual_file_contents = fmt::format("ERROR\n{}\n", fmt::join(parse_result.errors, "\n")); - } - - auto ast_actual_file_path = path_ / "ast.actual.txt"; - cqasm::test::write_file(ast_actual_file_path, ast_actual_file_contents); + // Check the debug dump of the parse result + std::string ast_actual_file_contents = parse_result.errors.empty() + ? fmt::format("SUCCESS\n{}\n", *parse_result.root) + : fmt::format("ERROR\n{}\n", fmt::join(parse_result.errors, "\n")); std::string ast_golden_file_contents{}; + auto ast_actual_file_path = path_ / "ast.actual.txt"; auto ast_golden_file_path = path_ / "ast.golden.txt"; + cqasm::test::write_file(ast_actual_file_path, ast_actual_file_contents); EXPECT_TRUE(cqasm::test::read_file(ast_golden_file_path, ast_golden_file_contents)); EXPECT_TRUE(ast_actual_file_contents == ast_golden_file_contents); + // Check the JSON dump of the parse result + if (parse_result.errors.empty()) { + if (auto json_golden_file_path = path_ / "ast.golden.json"; fs::exists(json_golden_file_path)) { + auto json_actual_file_path = path_ / "ast.actual.json"; + std::string json_actual_file_contents{}; + std::string json_golden_file_contents{}; + { + std::ofstream json_actual_ofs{ json_actual_file_path }; + parse_result.root->dump_json(json_actual_ofs); + } + EXPECT_TRUE(cqasm::test::read_file(json_actual_file_path, json_actual_file_contents)); + EXPECT_TRUE(cqasm::test::read_file(json_golden_file_path, json_golden_file_contents)); + EXPECT_TRUE(json_actual_file_contents == json_golden_file_contents); + } + } + // Stop if parsing failed if (!parse_result.errors.empty()) { return; @@ -86,20 +98,34 @@ class ParsingTest : public ::testing::Test { // Run the actual semantic analysis auto analysis_result = analyzer.analyze(*parse_result.root->as_program()); - // Check the analysis results - std::string semantic_actual_file_contents{}; - if (analysis_result.errors.empty()) { - semantic_actual_file_contents = fmt::format("SUCCESS\n{}\n", *analysis_result.root); - } else { - semantic_actual_file_contents = fmt::format("ERROR\n{}\n", fmt::join(analysis_result.errors, "\n")); - } + // Check the debug dump of the analysis result + std::string semantic_actual_file_contents = analysis_result.errors.empty() + ? fmt::format("SUCCESS\n{}\n", *analysis_result.root) + : fmt::format("ERROR\n{}\n", fmt::join(analysis_result.errors, "\n")); auto semantic_actual_file_path = path_ / fmt::format("semantic.{}.actual.txt", api_version); + auto semantic_golden_file_path = path_ / fmt::format("semantic.{}.golden.txt", api_version); cqasm::test::write_file(semantic_actual_file_path, semantic_actual_file_contents); std::string semantic_golden_file_contents{}; - auto semantic_golden_file_path = path_ / fmt::format("semantic.{}.golden.txt", api_version); EXPECT_TRUE(cqasm::test::read_file(semantic_golden_file_path, semantic_golden_file_contents)); EXPECT_TRUE(semantic_actual_file_contents == semantic_golden_file_contents); + // Check the JSON dump of the analysis result + if (analysis_result.errors.empty()) { + if (auto json_golden_file_path = path_ / fmt::format("semantic.{}.golden.json", api_version); + fs::exists(json_golden_file_path)) { + auto json_actual_file_path = path_ / fmt::format("semantic.{}.actual.json", api_version); + std::string json_actual_file_contents{}; + std::string json_golden_file_contents{}; + { + std::ofstream json_actual_ofs{ json_actual_file_path }; + analysis_result.root->dump_json(json_actual_ofs); + } + EXPECT_TRUE(cqasm::test::read_file(json_actual_file_path, json_actual_file_contents)); + EXPECT_TRUE(cqasm::test::read_file(json_golden_file_path, json_golden_file_contents)); + EXPECT_TRUE(json_actual_file_contents == json_golden_file_contents); + } + } + if (analysis_result.errors.empty()) { ::tree::base::serialize(analysis_result.root); } diff --git a/test/v3x/python/test_analysis_result.py b/test/v3x/python/test_analysis_result.py new file mode 100644 index 00000000..27a44db7 --- /dev/null +++ b/test/v3x/python/test_analysis_result.py @@ -0,0 +1,21 @@ +import unittest + +import cqasm.v3x as cq + + +class TestAnalysisResult(unittest.TestCase): + def test_to_json_with_analyzer_errors(self): + # res/v3x/parsing/bit_array_definition/bit_array_of_0_b + program_str = "version 3; bit[0] b" + v3x_analyzer = cq.Analyzer() + actual_errors_json = v3x_analyzer.analyze_string_to_json(program_str) + expected_errors_json = '''{"errors":["Error at :1:19..20: declaring bit array of size <= 0"]}''' + self.assertEqual(actual_errors_json, expected_errors_json) + + def test_to_json_with_analyzer_ast(self): + # res/v3x/parsing/bit_array_definition/bit_array_of_17_b + program_str = "version 3; bit[17] b" + v3x_analyzer = cq.Analyzer() + actual_ast_json = v3x_analyzer.analyze_string_to_json(program_str) + expected_ast_json = '''{"Program":{"api_version":"3.0","version":{"Version":{"items":"3"}},"statements":"[]","variables":[{"Variable":{"name":"b","typ":{"BitArray":{"size":"17"}},"annotations":"[]"}}]}}''' + self.assertEqual(actual_ast_json, expected_ast_json) diff --git a/test/v3x/python/test_parse_result.py b/test/v3x/python/test_parse_result.py new file mode 100644 index 00000000..2ff80b72 --- /dev/null +++ b/test/v3x/python/test_parse_result.py @@ -0,0 +1,21 @@ +import unittest + +import cqasm.v3x as cq + + +class TestParseResult(unittest.TestCase): + def test_to_json_with_parser_errors(self): + # res/v3x/parsing/bit_array_definition/bit_array_of_3_14 + program_str = "version 3; bit[3.14]" + v3x_analyzer = cq.Analyzer() + actual_errors_json = v3x_analyzer.parse_string_to_json(program_str) + expected_errors_json = '''{"errors":["Error at :1:19..20: declaring bit array of size <= 0"]}''' + self.assertEqual(expected_errors_json, expected_errors_json) + + def test_to_json_with_parser_ast(self): + # res/v3x/parsing/bit_array_definition/bit_array_of_0_b + program_str = "version 1.0; bit[0] b" + v3x_analyzer = cq.Analyzer() + actual_ast_json = v3x_analyzer.parse_string_to_json(program_str) + expected_ast_json = '''{"Program":{"version":{"Version":{"items":"1.0","source_location":":1:9..12"}},"statements":{"StatementList":{"items":[{"Variables":{"names":[{"Identifier":{"name":"b"}}],"typ":{"Identifier":{"name":"bit"}},"size":{"IntegerLiteral":{"value":"0"}},"annotations":"[]","source_location":":1:21..22"}}]}}}}''' + self.assertEqual(actual_ast_json, expected_ast_json)