From 4d4720aa5ff3531e149d4544f8f9d5a762d5dc98 Mon Sep 17 00:00:00 2001 From: Matthew Pope Date: Mon, 25 Nov 2024 11:11:12 -0800 Subject: [PATCH] Adds tests for annotate, flatten, and parse_ion system macros --- conformance/grammar.isl | 2 +- conformance/system_macros/annotate.ion | 90 +++++++++++++++++++++++-- conformance/system_macros/flatten.ion | 64 ++++++++++++++++-- conformance/system_macros/parse_ion.ion | 87 ++++++++++++++++++++++-- 4 files changed, 229 insertions(+), 14 deletions(-) diff --git a/conformance/grammar.isl b/conformance/grammar.isl index 2c0faaa..5b681bd 100644 --- a/conformance/grammar.isl +++ b/conformance/grammar.isl @@ -231,7 +231,7 @@ type::{ type::{ name: model_annot, ordered_elements: [ - { valid_values: [Annot, "Annot"] }, + { valid_values: [annot, "annot"] }, model_content, { type: model_symtok, occurs: range::[0, max] } ] diff --git a/conformance/system_macros/annotate.ion b/conformance/system_macros/annotate.ion index 161ec53..fac3e9f 100644 --- a/conformance/system_macros/annotate.ion +++ b/conformance/system_macros/annotate.ion @@ -1,9 +1,91 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -// Test Cases: -// annotate can be invoked using any type of macro reference. -// annotate adds annotations to a value -// if the value has existing annotations, they are preserved and annotate prepends annotations to the existing annotations of the value +(ion_1_1 "annotate can be invoked" + (each "in text with an unqualified macro name" + (text " (:annotate (::) 0) ") + "in text with an unqualified macro address" + (text " (:2 (::) 0) ") + "in text with a qualified macro name" + (text " (:$ion::annotate (::) 0) ") + "in text using qualified system macro address 2" + (text " (:$ion::2 (::) 0) ") + "in binary using system macro address 2" + (binary "EF 02 00 60") + "in binary with a user macro address" + (binary "02 00 60") + (produces 0))) + +(ion_1_1 "annotate can add" + (each "0 annotations" + (text " (:annotate (::) a::b::c::d::e::0) ") + "1 annotations" + (text " (:annotate (:: a) b::c::d::e::0) ") + "2 annotations" + (text " (:annotate (:: a b) c::d::e::0) ") + "3 annotations" + (text " (:annotate (:: a b c) d::e::0) ") + "4 annotations" + (text " (:annotate (:: a b c d) e::0) ") + "5 annotations" + (text " (:annotate (:: a b c d e) 0) ") + (produces a::b::c::d::e::0))) + +(ion_1_1 "annotate can add annotations to" + (then "null" (text "(:annotate (:: a) null )") (produces a::null)) + (then "bool" (text "(:annotate (:: a) true )") (produces a::true)) + (then "int" (text "(:annotate (:: a) 2 )") (produces a::2)) + (then "float" (text "(:annotate (:: a) 3e0 )") (produces a::3e0)) + (then "decimal" (text "(:annotate (:: a) 4d0 )") (produces a::4d0)) + (then "timestamp" (text "(:annotate (:: a) 2024T )") (produces a::2024T)) + (then "string" (text "(:annotate (:: a) '''abc''' )") (produces a::"abc")) + (then "symbol" (text "(:annotate (:: a) abc )") (produces a::abc)) + (then "clob" (text "(:annotate (:: a) {{'''abc'''}} )") (produces a::{{"abc"}})) + // (then "blob" (text "(:annotate (:: a) {{+AB/}} )") (produces a::{{ +AB/ }})) + (then "list" (text "(:annotate (:: a) [0, 1, 2] )") (produces a::[0, 1, 2])) + (then "sexp" (text "(:annotate (:: a) (0 1 2) )") (produces a::(0 1 2))) + (then "struct" (text "(:annotate (:: a) {a:1} )") (produces a::{a:1})) + (then "the result of an e-expression" + (text " (:annotate (:: a) (:values 123))") + (produces a::123)) + (then "the result of a tdl macro invocation" + (mactab (macro foo () (.annotate (.. "a") (.values "b")))) + (text "(:foo)") + (produces a::"b")) + (then "the value of a tdl variable" + (mactab (macro foo (x) (.annotate (.. "a") (%x)))) + (text "(:foo 10)") + (produces a::10))) + +(ion_1_1 "the annotations argument" + (then "may be" + (then "an empty expression group" + (text "(:annotate (::) 0)") + (produces 0)) + (then "a symbol with unknown text" + (text "(:annotate (:: $0) true)") + // Could be (produces $0::true), but some implementations don't support $0 nicely. + (denotes (annot true 0))) + (each "a string" + (text "(:annotate (:: '''a''') 0)") + "a symbol" + (text "(:annotate (:: 'a') 0)") + "an expression that produces a text value" + (text "(:annotate (:: (:values a)) 0)") + (produces a::0)) + (each "an expression group with multiple text values" + (text "(:annotate (:: a b) 0)") + "an expression that produces multiple text values" + (text "(:annotate (:: (:values a b)) 0)") + (produces a::b::0))) + (then "may not be" + (each "any null" + (text "(:annotate (:: null) 0)") + (text "(:annotate (:: null.string) 0)") + (text "(:annotate (:: null.symbol) 0)") + "a non-text value" + (text "(:annotate (:: 1) 0)") + (signals "invalid argument")))) + // the first argument must be zero or more non-null text values ($0 is allowed) // the second argument must be a single expression that also expands to a single expression diff --git a/conformance/system_macros/flatten.ion b/conformance/system_macros/flatten.ion index f52d46f..af69e7a 100644 --- a/conformance/system_macros/flatten.ion +++ b/conformance/system_macros/flatten.ion @@ -1,8 +1,62 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -// Test Cases: -// flatten can be invoked using any type of macro reference. -// the argument values may be zero or more non-null lists and s-expressions -// flatten expands to a stream whose elements are the concatenated elements of all its arguments -// annotations on the argument (list and sexp) values are silently dropped +(ion_1_1 "flatten can be invoked" + (each "in text with an unqualified macro name" + (text " (:flatten (::)) ") + "in text with an unqualified macro address" + (text " (:19 (::)) ") + "in text with a qualified macro name" + (text " (:$ion::flatten (::)) ") + "in text using qualified system macro address 19" + (text " (:$ion::19 (::)) ") + "in binary using system macro address 19" + (binary "EF 13 00") + "in binary with a user macro address" + (binary "13 00") + (produces /*nothing*/))) + +(ion_1_1 "flatten creates a single, unannotated sexp from" + (then "0 values" + (text "(:flatten)") + (produces /*nothing*/)) + (each "one list" + (text "(:flatten [1, 2, 3])") + "multiple lists" + (text "(:flatten [1, 2] [3])") + (text "(:flatten [1] [2] [3])") + (text "(:flatten [] [1, 2, 3] [])") + (text "(:flatten [] [1] [] [2] [] [3] [])") + "one sexp" + (text "(:flatten (1 2 3))") + "multiple sexps" + (text "(:flatten (1 2) (3))") + (text "(:flatten (1) (2) (3))") + (text "(:flatten () (1 2 3) ())") + (text "(:flatten () (1) () (2) () (3) ())") + "a mix of lists and sexps" + (text "(:flatten () [1] (2) [3])") + (text "(:flatten (1) [2, 3] ())") + "annotated sequence values" + // Argument annotations are silently dropped. + (text "(:flatten a::() b::[1] c::(2) d::[3])") + (produces 1 2 3))) + +(ion_1_1 "the argument cannot be" + (each "null" + (text "(:flatten null)") + (text "(:flatten (1) null (2))") + "null.list" + (text "(:flatten null.list)") + (text "(:flatten (1) null.list (2))") + "null.sexp" + (text "(:flatten null.sexp)") + (text "(:flatten (1) null.sexp (2))") + "a non-sequence value" + (text "(:flatten {{ '''abc''' }})") + (text "(:flatten (1) {{ '''abc''' }} (2))") + (text "(:flatten 123)") + (text "(:flatten (1) 123 (2))") + (text "(:flatten { a: 1 })") + (text "(:flatten (1) { a: 1 } (2))") + (signals "invalid argument"))) diff --git a/conformance/system_macros/parse_ion.ion b/conformance/system_macros/parse_ion.ion index 516520f..e0f2d43 100644 --- a/conformance/system_macros/parse_ion.ion +++ b/conformance/system_macros/parse_ion.ion @@ -16,10 +16,89 @@ (binary "10 94 74 72 75 65") (produces true))) -// parse_ion can read Ion 1.0 and Ion 1.1, text and binary +(ion_1_1 "parse_ion can parse" + (each "Ion 1.0 text in a string literal" + (text '''(:parse_ion "$ion_1_0 0")''') + "Ion 1.0 text in a clob literal" + (text '''(:parse_ion {{ "$ion_1_0 0" }})''') + "Ion 1.0 text in a blob literal" + (text "(:parse_ion {{ JGlvbl8xXzAgMA== }})") + "Ion 1.0 binary in a blob literal" + (text "(:parse_ion {{ 4AEA6iA= }})") + "Ion 1.1 text in a string literal" + (text '''(:parse_ion "$ion_1_1 0")''') + "Ion 1.1 text in a clob literal" + (text '''(:parse_ion {{ "$ion_1_1 0" }})''') + "Ion 1.1 text in a blob literal" + (text "(:parse_ion {{ JGlvbl8xXzEgMA== }})") + "Ion 1.1 binary in a blob literal" + (text "(:parse_ion {{ 4AEB6mA= }})") + (produces 0))) -// parse_ion always produces user values. See https://github.com/amazon-ion/ion-docs/issues/365 for complications to be resolved. +(ion_1_1 "when invoked in TDL, parse_ion can parse" + (each "Ion 1.0 text in a string literal" + (mactab (macro foo () (.parse_ion "$ion_1_0 0"))) + "Ion 1.0 text in a clob literal" + (mactab (macro foo () (.parse_ion {{ "$ion_1_0 0" }}))) + "Ion 1.0 text in a blob literal" + (mactab (macro foo () (.parse_ion {{ JGlvbl8xXzAgMA== }}))) + "Ion 1.0 binary in a blob literal" + (mactab (macro foo () (.parse_ion {{ 4AEA6iA= }} ))) + "Ion 1.1 text in a string literal" + (mactab (macro foo () (.parse_ion "$ion_1_1 0"))) + "Ion 1.1 text in a clob literal" + (mactab (macro foo () (.parse_ion {{ "$ion_1_1 0" }}))) + "Ion 1.1 text in a blob literal" + (mactab (macro foo () (.parse_ion {{ JGlvbl8xXzEgMA== }}))) + "Ion 1.1 binary in a blob literal" + (mactab (macro foo () (.parse_ion {{ 4AEB6mA= }} ))) + (then (text "(:foo)") + (produces 0)))) -// parse_ion requires exactly one argument +// parse_ion requires exactly one argument, which must be a string or lob literal +(ion_1_1 "parse_ion" + (each "must have exactly one argument" + (text '''(:parse_ion "$ion_1_0" "0")''') + (text "(:parse_ion)") + "argument must not be any null " + (text "(:parse_ion null)") + (text "(:parse_ion null.blob)") + (text "(:parse_ion null.clob)") + (text "(:parse_ion null.string)") + "argument may not be a symbol" + (text "(:parse_ion '$ion_1_0 0')") + "argument may not be an expression group" + (text '''(:parse_ion (:: "$ion_1_0 0"))''') + "argument may not be an e-expression" + (text '''(:parse_ion (:values "$ion_1_0 0"))''') + (signals "invalid argument")) + (each "argument may not be a tdl macro invocation" + (mactab (macro foo () (.parse_ion (.values "$ion_1_1 0")))) + "argument may not be a tdl variable" + (mactab (macro bar (x) (.parse_ion (%x)))) + "argument may not be a special form" + (mactab (macro bar (x) (.parse_ion (.if_void (%x) "0" "1")))) + (signals "invalid macro definition"))) -// parse_ion argument must be a string or lob literal +(ion_1_1 "when the enclosing document's encoding context has symbols and macros" + (toplevel $ion_symbol_table::{symbols: ["a", "b", "c", "d"]}) + (mactab (macro pi () 3.14159)) + (each "embedded Ion 1.0 does not inherit any outer symbols" + (text '''(:parse_ion "$ion_1_0 $10")''') + "embedded Ion 1.1 does not inherit any outer symbols" + (text '''(:parse_ion "$ion_1_1 $66")''') + "embedded Ion 1.1 does not inherit any outer macros" + (text '''(:parse_ion "$ion_1_1 (:pi)")''') + (signals "invalid argument"))) + +// This may need to be updated once https://github.com/amazon-ion/ion-docs/issues/365 is resolved +(ion_1_1 "parse_ion always produces user values" + (then "from values that look like system values" + (text '''(:parse_ion "$ion_1_1 $ion_literal::$ion_symbol_table::{symbols:[]}")''') + (produces $ion_symbol_table::{symbols:[]})) + (each "from values that look like IVMs" + (text '''(:parse_ion "$ion_1_1 $ion_literal::$ion_1_0")''') + // TODO: These might not be correct depending on the semantics around quoted IVMs. + (text '''(:parse_ion "$ion_1_1 '$ion_1_0')''') + (text '''(:parse_ion "$ion_1_0 '$ion_1_0')''') + (produces $ion_1_0)))