jsonnet 0.3.1.1 → 0.4.0.0
raw patch · 124 files changed
+4231/−798 lines, 124 filesdep +hedgehogdep +prettyprinterdep +tasty-hedgehogdep −ansi-wl-pprintdep −deriving-compatdep −hashabledep ~aesondep ~basedep ~bytestring
Dependencies added: hedgehog, prettyprinter, tasty-hedgehog
Dependencies removed: ansi-wl-pprint, deriving-compat, hashable, semigroupoids, tasty-hunit
Dependency ranges changed: aeson, base, bytestring, containers, data-fix, filepath, lens, megaparsec, mtl, optparse-applicative, parser-combinators, template-haskell, text, th-utilities, transformers-compat, unbound-generics, vector
Files
- README.md +16/−10
- app/Main.hs +127/−26
- benchmarks/Bench.hs +21/−17
- jsonnet.cabal +66/−46
- src/Language/Jsonnet.hs +61/−17
- src/Language/Jsonnet/Annotate.hs +7/−2
- src/Language/Jsonnet/Check.hs +9/−15
- src/Language/Jsonnet/Common.hs +91/−70
- src/Language/Jsonnet/Core.hs +11/−19
- src/Language/Jsonnet/Desugar.hs +18/−18
- src/Language/Jsonnet/Error.hs +21/−23
- src/Language/Jsonnet/Eval.hs +32/−40
- src/Language/Jsonnet/Eval/Monad.hs +25/−26
- src/Language/Jsonnet/Parser.hs +90/−39
- src/Language/Jsonnet/Parser/SrcSpan.hs +3/−5
- src/Language/Jsonnet/Pretty.hs +309/−133
- src/Language/Jsonnet/Std/Lib.hs +20/−51
- src/Language/Jsonnet/Std/TH.hs +0/−3
- src/Language/Jsonnet/Syntax.hs +44/−28
- src/Language/Jsonnet/Syntax/Annotated.hs +3/−5
- src/Language/Jsonnet/TH.hs +24/−98
- src/Language/Jsonnet/Value.hs +27/−14
- stdlib/std.jsonnet +197/−37
- test/Language/Jsonnet/Test/Golden.hs +51/−0
- test/Language/Jsonnet/Test/Roundtrip.hs +201/−0
- test/Main.hs +16/−0
- test/golden/Test.hs +0/−56
- test/golden/arith-float.golden +1/−0
- test/golden/arith-float.jsonnet +136/−0
- test/golden/array.golden +1/−0
- test/golden/array.jsonnet +58/−0
- test/golden/comments.golden +1/−0
- test/golden/comments.jsonnet +43/−0
- test/golden/condition.golden +1/−0
- test/golden/condition.jsonnet +41/−0
- test/golden/empty.golden +1/−0
- test/golden/empty.jsonnet +1/−0
- test/golden/error.01.golden +5/−0
- test/golden/error.01.jsonnet +20/−0
- test/golden/error.array_index_string.golden +2/−0
- test/golden/error.array_index_string.jsonnet +14/−0
- test/golden/error.array_large_index.golden +2/−0
- test/golden/error.array_large_index.jsonnet +14/−0
- test/golden/error.assert.fail1.golden +2/−0
- test/golden/error.assert.fail1.jsonnet +19/−0
- test/golden/error.assert.fail2.golden +3/−0
- test/golden/error.assert.fail2.jsonnet +5/−0
- test/golden/error.divide-zero.golden +2/−0
- test/golden/error.divide-zero.jsonnet +14/−0
- test/golden/error.duplicate_local_var.golden +2/−0
- test/golden/error.duplicate_local_var.jsonnet +2/−0
- test/golden/error.field-not-exist.golden +2/−0
- test/golden/error.field-not-exist.jsonnet +14/−0
- test/golden/error.function_arg_positional_after_named.golden +2/−0
- test/golden/error.function_arg_positional_after_named.jsonnet +16/−0
- test/golden/error.function_duplicate_param.golden +2/−0
- test/golden/error.function_duplicate_param.jsonnet +16/−0
- test/golden/error.function_no_such_param.golden +3/−0
- test/golden/error.function_no_such_param.jsonnet +2/−0
- test/golden/error.function_too_many_args.golden +3/−0
- test/golden/error.function_too_many_args.jsonnet +16/−0
- test/golden/error.index-out-of-bounds.golden +2/−0
- test/golden/error.index-out-of-bounds.jsonnet +3/−0
- test/golden/error.invalid-key.golden +2/−0
- test/golden/error.invalid-key.jsonnet +5/−0
- test/golden/error.sanity.golden +3/−0
- test/golden/error.sanity.jsonnet +14/−0
- test/golden/format.golden +1/−0
- test/golden/format.jsonnet +310/−0
- test/golden/functions.golden +1/−0
- test/golden/functions.jsonnet +58/−0
- test/golden/hello-world.golden +3/−0
- test/golden/hello-world.jsonnet +4/−0
- test/golden/hidden.golden +5/−0
- test/golden/hidden.jsonnet +10/−0
- test/golden/importbin_nonutf8.golden +5/−0
- test/golden/importbin_nonutf8.jsonnet +1/−0
- test/golden/imports.golden +1/−0
- test/golden/imports.jsonnet +6/−0
- test/golden/imports.libsonnet +4/−0
- test/golden/importstr-nonexistent.golden +2/−0
- test/golden/importstr-nonexistent.jsonnet +1/−0
- test/golden/importstr-twice.golden +1/−0
- test/golden/importstr-twice.jsonnet +3/−0
- test/golden/importstr.golden +1/−0
- test/golden/importstr.jsonnet +1/−0
- test/golden/lazy.golden +1/−0
- test/golden/lazy.jsonnet +4/−0
- test/golden/lib/capture_std.libsonnet +14/−0
- test/golden/lib/capture_std_func.libsonnet +14/−0
- test/golden/local.golden +1/−0
- test/golden/local.jsonnet +33/−0
- test/golden/manifest-json-ex.golden +1/−0
- test/golden/manifest-json-ex.jsonnet +8/−0
- test/golden/manifest-yaml-doc.golden +1/−0
- test/golden/manifest-yaml-doc.jsonnet +7/−0
- test/golden/null.golden +1/−0
- test/golden/null.jsonnet +21/−0
- test/golden/object-methods.golden +1/−0
- test/golden/object-methods.jsonnet +7/−0
- test/golden/object.golden +1/−0
- test/golden/object.jsonnet +114/−0
- test/golden/oop.golden +1/−0
- test/golden/oop.jsonnet +88/−0
- test/golden/oop_extra.golden +1/−0
- test/golden/oop_extra.jsonnet +57/−0
- test/golden/outermost.golden +22/−0
- test/golden/outermost.jsonnet +24/−0
- test/golden/precedence.golden +1/−0
- test/golden/precedence.jsonnet +41/−0
- test/golden/recursive-functions.golden +1/−0
- test/golden/recursive-functions.jsonnet +49/−0
- test/golden/slice.sugar.golden +1/−0
- test/golden/slice.sugar.jsonnet +178/−0
- test/golden/std-all-hidden.golden +1/−0
- test/golden/std-all-hidden.jsonnet +14/−0
- test/golden/stdlib.golden +1/−0
- test/golden/stdlib.jsonnet +1011/−0
- test/golden/unicode.golden +1/−0
- test/golden/unicode.jsonnet +36/−0
- test/golden/unparsed.golden +17/−0
- test/golden/unparsed.jsonnet +32/−0
- test/golden/verbatim-strings.golden +1/−0
- test/golden/verbatim-strings.jsonnet +21/−0
README.md view
@@ -1,8 +1,7 @@ # haskell-jsonnet [](https://github.com/moleike/haskell-jsonnet/actions)-[](https://hackage.haskell.org/package/jsonnet)-+[](https://hackage.haskell.org/package/jsonnet) A full-fledged Haskell implementation of the [Jsonnet][jsonnet] spec. For an introduction to the language itself, see the [tutorial][tutorial] or language [reference][reference].@@ -12,23 +11,24 @@ Here is the implementation status of the main language features: -- [X] array and object comprehension+- [X] array and object comprehension (but see https://github.com/moleike/haskell-jsonnet/issues/46) - [X] array slices - [X] Python-style string formatting - [X] text blocks - [X] verbatim strings - [X] object-level locals-- [ ] object-level asserts+- [ ] object-level asserts (https://github.com/moleike/haskell-jsonnet/issues/39) - [X] keyword parameters - [X] default arguments-- [ ] top-level arguments-- [ ] external variables-- [X] hidden fields ([@CristhianMotoche](https://github.com/CristhianMotoche)) +- [ ] top-level arguments (https://github.com/moleike/haskell-jsonnet/issues/24)+- [X] external variables (by Berk Özkütük)+- [X] hidden fields (by Cristhian Motoche) - [X] tailstrict annotation - [X] outermost object reference `$`-- [X] mixin inheritence (operator `+` with `self` and `super`)+- [X] mixin inheritence and open recursion (operator `+` with `self` and `super`) - [X] field composition (operator `+:`)-- [ ] multiple file output+- [ ] multiple file output (https://github.com/moleike/haskell-jsonnet/issues/32)+- [X] verbatim imports (by Berk Özkütük) ## Build @@ -51,7 +51,9 @@ ```console % hs-jsonnet --help Usage: hs-jsonnet [-v|--version] [-e|--exec] [<filename>] - [-o|--output-file <filename>] [-S|--string]+ [-o|--output-file <filename>] [-S|--string] + [-V|--ext-str VAR=VALUE] [--ext-str-file FILE] + [--ext-code VAR=EXPR] [--ext-code-file FILE] Available options: -v,--version Print version of the program@@ -61,6 +63,10 @@ -o,--output-file <filename> Write to the output file rather than stdout -S,--string Expect a string, manifest as plain text+ -V,--ext-str VAR=VALUE External string variable+ --ext-str-file FILE External string variable as file+ --ext-code VAR=EXPR External code variable+ --ext-code-file FILE External code variable as file ``` ## Output formats
app/Main.hs view
@@ -1,13 +1,9 @@ {-# LANGUAGE ApplicativeDo #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-} module Main where -import Control.Monad.Except import qualified Data.Aeson as JSON+import Data.Bifunctor (first, second) import Data.ByteString.Builder (toLazyByteString) import qualified Data.ByteString.Lazy as L import Data.ByteString.Lazy.Char8 as LC (putStrLn)@@ -17,15 +13,16 @@ import Data.Text.Encoding (encodeUtf8Builder) import qualified Data.Text.IO as TIO import Data.Version (showVersion)+import Data.Void (Void) import Language.Jsonnet-import Language.Jsonnet.Annotate-import Language.Jsonnet.Desugar import Language.Jsonnet.Error-import Language.Jsonnet.Eval--- import Language.Jsonnet.Value-import Options.Applicative+import Language.Jsonnet.Pretty (ppJson, prettyError)+import Options.Applicative hiding (str) import Paths_jsonnet (version)-import Text.PrettyPrint.ANSI.Leijen (Pretty, pretty)+import System.Environment (lookupEnv)+import System.Exit (die)+import qualified Text.Megaparsec as MP+import qualified Text.Megaparsec.Char as MPC main :: IO () main = do@@ -40,17 +37,21 @@ src <- readSource input conf <- mkConfig opts outp <- interpret conf src- either printError (printResult output format) outp+ either printError (printResult output outputMode format) outp -printResult :: Output -> Format -> JSON.Value -> IO ()-printResult outp Json val =- writeOutput (JSON.encode val) outp-printResult outp Plaintext (JSON.String s) =- writeOutput (encodeToLazyByteString s) outp+encodeToLazyByteString :: Text -> L.ByteString+encodeToLazyByteString = toLazyByteString . encodeUtf8Builder++printResult :: Output -> OutputMode -> Format -> JSON.Value -> IO ()+printResult outp outputMode Json val =+ writeOutput (encode val) outp where- encodeToLazyByteString =- toLazyByteString . encodeUtf8Builder-printResult _ Plaintext _ =+ encode = case outputMode of+ Pretty -> encodeToLazyByteString . T.pack . show . ppJson 4+ Compact -> JSON.encode+printResult outp _ Plaintext (JSON.String s) =+ writeOutput (encodeToLazyByteString s) outp+printResult _ _ Plaintext _ = print "Runtime error: expected string result" writeOutput :: L.ByteString -> Output -> IO ()@@ -59,7 +60,7 @@ Stdout -> LC.putStrLn bs printError :: Error -> IO ()-printError = print . pretty+printError = print . prettyError readSource :: Input -> IO Text readSource = \case@@ -67,13 +68,22 @@ FileInput path -> TIO.readFile path ExecInput str -> pure (T.pack str) +mkExtVar :: (Text, Either ExtVarType ExtVar) -> IO (Text, ExtVar)+mkExtVar = \case+ (s, Right extVar) -> pure (s, extVar)+ (envVar, Left extVarType) ->+ lookupEnv (T.unpack envVar) >>= \case+ Just extVar -> pure $ (envVar, ExtVar extVarType (Inline (T.pack extVar)))+ Nothing -> die "No env variable"+ mkConfig :: Options -> IO Config mkConfig Options {..} = do let fname = case input of Stdin -> "" FileInput path -> path ExecInput _ -> ""- pure Config {..}+ extVars' <- constructExtVars =<< traverse mkExtVar extVars+ pure Config {fname = fname, extVars = extVars'} fileOutput :: Parser Output fileOutput =@@ -100,6 +110,13 @@ parseOpts = do input <- mkInput <$> exec <*> fileInput output <- fileOutput+ outputMode <-+ flag+ Pretty+ Compact+ ( long "compact"+ <> help "Produce a compact JSON output"+ ) format <- flag Json@@ -108,12 +125,88 @@ <> short 'S' <> help "Expect a string, manifest as plain text" )- pure Options {..}+ extStrs <- parseExtStr+ extStrFiles <- parseExtStrFile+ extCodes <- parseExtCode+ extCodeFiles <- parseExtCodeFile+ pure Options {extVars = extStrs <> extStrFiles <> extCodes <> extCodeFiles, ..} +parseExtStr :: Parser [(Text, Either ExtVarType ExtVar)]+parseExtStr =+ many $+ option+ (second (interpretAs ExtStr) <$> extVarParser)+ ( long "ext-str"+ <> short 'V'+ <> metavar "VAR=VALUE"+ <> help "External string variable"+ )++parseExtStrFile :: Parser [(Text, Either ExtVarType ExtVar)]+parseExtStrFile =+ many $+ option+ (second (Right . ExtVar ExtStr) <$> extVarFileParser)+ ( long "ext-str-file"+ <> help "External string variable as file"+ <> metavar "FILE"+ <> action "file"+ )++parseExtCode :: Parser [(Text, Either ExtVarType ExtVar)]+parseExtCode =+ many $+ option+ (second (interpretAs ExtCode) <$> extVarParser)+ ( long "ext-code"+ <> metavar "VAR=EXPR"+ <> help "External code variable"+ )++parseExtCodeFile :: Parser [(Text, Either ExtVarType ExtVar)]+parseExtCodeFile =+ many $+ option+ (second (Right . ExtVar ExtCode) <$> extVarFileParser)+ ( long "ext-code-file"+ <> help "External code variable as file"+ <> metavar "FILE"+ <> action "file"+ )++interpretAs :: ExtVarType -> Maybe ExtVarContent -> Either ExtVarType ExtVar+interpretAs t = \case+ Nothing -> Left t+ Just s -> Right $ ExtVar t s++extVarParser :: ReadM (Text, Maybe ExtVarContent)+extVarParser = eitherReader f+ where+ f = first MP.errorBundlePretty . MP.runParser (MP.try pair <|> envVar) ""++ pair :: MP.Parsec Void String (Text, Maybe ExtVarContent)+ pair = second (Just . Inline . T.pack) <$> parsePair++ envVar :: MP.Parsec Void String (Text, Maybe ExtVarContent)+ envVar = (,Nothing) . T.pack <$> MP.someTill MPC.asciiChar MP.eof++extVarFileParser :: ReadM (Text, ExtVarContent)+extVarFileParser = eitherReader f+ where+ f = first MP.errorBundlePretty . MP.runParser (second File <$> parsePair) ""++parsePair :: MP.Parsec Void String (Text, String)+parsePair =+ first T.pack+ <$> liftA2+ (,)+ (MP.someTill MPC.asciiChar (MPC.char '='))+ (MP.someTill MPC.asciiChar MP.eof)+ mkInput :: Bool -> Maybe String -> Input-mkInput exec = \case+mkInput exec' = \case Nothing -> Stdin- Just e | exec -> ExecInput e+ Just e | exec' -> ExecInput e Just p | otherwise -> FileInput p exec :: Parser Bool@@ -145,8 +238,11 @@ data Options = Options { output :: Output,+ outputMode :: OutputMode, format :: Format,- input :: Input+ input :: Input,+ -- | ExtVarType determines the interpretation of the environment variable+ extVars :: [(Text, Either ExtVarType ExtVar)] } data Input@@ -158,6 +254,11 @@ data Output = FileOutput FilePath | Stdout+ deriving (Eq, Show)++data OutputMode+ = Pretty+ | Compact deriving (Eq, Show) data Format = Json | Plaintext
benchmarks/Bench.hs view
@@ -4,21 +4,22 @@ import Data.Text.Lazy import Data.Text.Lazy.Encoding (encodeUtf8) import Language.Jsonnet-import Language.Jsonnet.Core (Core)+import Language.Jsonnet.Pretty (ppJson, prettyError) import Language.Jsonnet.Syntax.Annotated (Expr) import Language.Jsonnet.TH.QQ+import Prettyprinter (Doc) import Test.Tasty.Bench-import Text.PrettyPrint.ANSI.Leijen (Pretty, pretty) -render :: Pretty a => a -> LBS.ByteString-render = encodeUtf8 . pack . show . pretty+render :: (a -> Doc ann) -> a -> LBS.ByteString+render printer = encodeUtf8 . pack . show . printer -conf = Config ""+conf :: Config+conf = Config "" mempty eval :: Expr -> IO LBS.ByteString eval expr = do outp <- runJsonnetM conf (desugar expr >>= evaluate)- pure (either render render outp)+ pure (either (render prettyError) (render (ppJson 4)) outp) main :: IO () main =@@ -35,11 +36,12 @@ ], bgroup "stdlib"- [ bench "bench04" $ nfAppIO eval bench04- , bench "bench06" $ nfAppIO eval bench06+ [ bench "bench04" $ nfAppIO eval bench04,+ bench "bench06" $ nfAppIO eval bench06 ] ] +bench01 :: Expr bench01 = [jsonnet| local sum(x) =@@ -50,6 +52,7 @@ sum(300) |] +bench02 :: Expr bench02 = [jsonnet| local Fib = {@@ -63,6 +66,7 @@ (Fib { n: 25 }).r |] +bench03 :: Expr bench03 = [jsonnet| local fibonacci(n) =@@ -73,11 +77,13 @@ fibonacci(25) |] +bench04 :: Expr bench04 = [jsonnet| std.foldl(function(e, res) e + res, std.makeArray(20000, function(i) 'aaaaa'), '') |] +bench06 :: Expr bench06 = [jsonnet| // A benchmark for builtin sort@@ -93,21 +99,19 @@ && std.assertEqual(std.makeArray(2000, function(i) std.floor((i + 2) / 2)), sort(std.range(1, 1000) + reverse(std.range(1, 1000)))) |] +bench08 :: Expr bench08 = [jsonnet| local fib(n) =- local fibnext = {- a: super.a + super.b,- b: super.a,- };- local go(n) =- if n == 0 then- { a: 1, b: 1 }+ if n <= 1 then+ { ['fib0']: 1, ['fib1']: 1 } else- go(n - 1) + fibnext;+ go(n - 1) {+ ['fib'+n]: super['fib'+(n-1)] + super['fib'+(n-2)]+ }; - go(n).b;+ go(n)['fib'+n]; fib(25) |]
jsonnet.cabal view
@@ -1,6 +1,6 @@-cabal-version: 2.2+cabal-version: 3.0 name: jsonnet-version: 0.3.1.1+version: 0.4.0.0 synopsis: Jsonnet implementaton in pure Haskell description: Please see the README on GitHub at <https://github.com/moleike/jsonnet-haskell#readme> homepage: https://github.com/moleike/haskell-jsonnet#readme@@ -13,16 +13,38 @@ x-license: BSD-3-Clause OR Apache-2.0 license-file: LICENSE build-type: Simple-extra-source-files:+extra-doc-files: README.md CHANGELOG.md+extra-source-files: stdlib/std.jsonnet+ test/golden/*.jsonnet+ test/golden/*.golden+ test/golden/**/*.libsonnet source-repository head type: git location: https://github.com/moleike/haskell-jsonnet +common common-extensions+ default-language: GHC2021+ default-extensions:+ LambdaCase+ DerivingStrategies+ DeriveAnyClass+ DerivingVia+ RecordWildCards++common common-warnings+ ghc-options:+ -Wall -Wcompat -Widentities -Wincomplete-record-updates+ -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints+ -Wno-unused-do-bind -Wunused-packages+ library+ import:+ common-extensions+ , common-warnings exposed-modules: Language.Jsonnet , Language.Jsonnet.Error@@ -51,42 +73,34 @@ hs-source-dirs: src build-depends:- aeson >= 1.5.6 && < 1.6,- base >= 4.14.1 && < 5,- bytestring >= 0.10.12 && < 0.11,- containers >= 0.6.2 && < 0.7,+ aeson >= 2.2 && < 2.3,+ base >= 4.20 && < 4.22,+ bytestring >= 0.11 && < 0.13,+ containers >= 0.6.2 && < 0.8, scientific >= 0.3.7 && < 0.4,- hashable >= 1.3.2 && < 1.4,- text >= 1.2.4 && < 1.3,- template-haskell >= 2.16.0 && < 2.18,+ text >= 2.0 && < 2.2,+ template-haskell >= 2.16.0 && < 2.23, data-fix >= 0.3.1 && < 0.4,- transformers-compat >= 0.6.6 && < 0.7,+ transformers-compat >= 0.6.6 && < 0.8, unordered-containers >= 0.2.14 && < 0.3,- mtl >= 2.2.2 && < 2.3,- vector >= 0.12.3 && < 0.13,- ansi-wl-pprint >= 0.6.9 && < 0.7,- deriving-compat >= 0.5.10 && < 0.6,+ mtl >= 2.2.2 && < 2.4,+ vector >= 0.12.3 && < 0.14,+ prettyprinter >= 1.7.0 && < 1.8, directory >= 1.3.6 && < 1.4,- filepath >= 1.4.2 && < 1.5,+ filepath >= 1.4.2 && < 1.6, exceptions >= 0.10.4 && < 0.11,- lens >= 5.0.1 && < 5.1,- semigroupoids >= 5.3.5 && < 5.4,- megaparsec >= 9.0.1 && < 9.1,- parser-combinators >= 1.3.0 && < 1.4,- unbound-generics >= 0.4.1 && < 0.5,- th-utilities == 0.2.4.2,+ lens >= 5.0.1 && < 5.4,+ megaparsec >= 9.5 && < 9.8,+ parser-combinators >= 1.2.1 && < 1.4,+ unbound-generics >= 0.4.3 && < 0.5,+ th-utilities >= 0.2.4.2 && < 0.3, binary >= 0.8.8 && < 0.9, th-lift-instances >= 0.1.18 && < 0.2 - default-language: Haskell2010- default-extensions:- MultiParamTypeClasses- , FlexibleContexts- , FlexibleInstances- , DeriveGeneric- , LambdaCase- executable hs-jsonnet+ import:+ common-extensions+ , common-warnings main-is: Main.hs autogen-modules: Paths_jsonnet@@ -101,33 +115,43 @@ base, bytestring, text,- mtl,- ansi-wl-pprint,- optparse-applicative >= 0.16.1 && < 0.17- default-language: Haskell2010+ megaparsec,+ optparse-applicative >= 0.16.1 && < 0.19 test-suite jsonnet-test+ import:+ common-extensions+ , common-warnings type: exitcode-stdio-1.0- main-is: Test.hs+ main-is: Main.hs+ autogen-modules:+ Paths_jsonnet other-modules: Paths_jsonnet+ , Language.Jsonnet.Test.Roundtrip+ , Language.Jsonnet.Test.Golden hs-source-dirs:- test/golden+ test+ , test/golden ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base- , ansi-wl-pprint+ , containers+ , prettyprinter , jsonnet- , mtl , text , filepath+ , data-fix , bytestring , tasty , tasty-golden- , tasty-hunit- default-language: Haskell2010+ , hedgehog+ , tasty-hedgehog benchmark jsonnet-bench+ import:+ common-extensions+ , common-warnings main-is: Bench.hs type: exitcode-stdio-1.0 hs-source-dirs:@@ -138,9 +162,5 @@ , jsonnet , bytestring , text- , ansi-wl-pprint- default-language: Haskell2010- if impl(ghc >= 8.10)- ghc-options: "-with-rtsopts=-A32m --nonmoving-gc"- else- ghc-options: "-with-rtsopts=-A32m"+ , prettyprinter+ ghc-options: "-with-rtsopts=-A32m --nonmoving-gc"
src/Language/Jsonnet.hs view
@@ -1,9 +1,4 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} -- |@@ -22,21 +17,25 @@ parse, evaluate, desugar,+ ExtVar (..),+ ExtVarType (..),+ ExtVarContent (..),+ interpretExtVar,+ constructExtVars, ) where +import Control.Monad ((>=>), (<=<))+import Control.Monad.Fix (MonadFix) import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow) import Control.Monad.Except import Control.Monad.Reader import qualified Data.Aeson as JSON-import Data.Functor.Identity-import Data.Functor.Sum+import Data.Binary (decode) import qualified Data.Map.Lazy as M import Data.Map.Strict (singleton) import Data.Text (Text)-import qualified Data.Text.IO as T (readFile)-import Debug.Trace-import Language.Jsonnet.Annotate+import qualified Data.Text.IO as T import qualified Language.Jsonnet.Check as Check import Language.Jsonnet.Common import Language.Jsonnet.Core@@ -45,17 +44,17 @@ import Language.Jsonnet.Eval import Language.Jsonnet.Eval.Monad import qualified Language.Jsonnet.Parser as Parser-import Language.Jsonnet.Pretty ()+import Language.Jsonnet.Pretty (prettyError) import qualified Language.Jsonnet.Std.Lib as Lib import Language.Jsonnet.Std.TH (mkStdlib) import Language.Jsonnet.Syntax.Annotated import Language.Jsonnet.Value-import Data.Binary (decode)+import System.Exit (die) newtype JsonnetM a = JsonnetM { unJsonnetM :: ReaderT Config (ExceptT Error IO) a }- deriving+ deriving newtype ( Functor, Applicative, Monad,@@ -69,8 +68,9 @@ MonadFail ) -newtype Config = Config- { fname :: FilePath+data Config = Config+ { fname :: FilePath,+ extVars :: ExtVars } runJsonnetM :: Config -> JsonnetM a -> IO (Either Error a)@@ -109,8 +109,52 @@ -- | the jsonnet stdlib is written in both jsonnet and Haskell, here we merge -- the native (a small subset) with the interpreted (the splice mkStdlib) std :: JsonnetM Value-std = JsonnetM $ lift $ ExceptT $ runEvalM M.empty stdlib+std = do+ extVars <- asks extVars+ let stdlib = whnf core >>= flip mergeObjects (Lib.std extVars)+ JsonnetM $ lift $ ExceptT $ runEvalM M.empty stdlib where- stdlib = whnf core >>= flip mergeObjects Lib.std core = decode $(mkStdlib) mergeObjects x y = whnfPrim (BinOp Add) [Pos x, Pos y]++data ExtVar = ExtVar+ { extVarType :: !ExtVarType,+ extVarContent :: !ExtVarContent+ }++data ExtVarContent+ = Inline !Text+ | File !FilePath++data ExtVarType+ = ExtStr+ | ExtCode++interpretExtVar :: ExtVar -> IO Value+interpretExtVar = \case+ ExtVar ExtStr s -> VStr <$> readExtVarContent s+ ExtVar ExtCode s -> either dieError pure <=< interpretToValue <=< readExtVarContent $ s+ where+ readExtVarContent :: ExtVarContent -> IO Text+ readExtVarContent = \case+ Inline s -> pure s+ File p -> T.readFile p++ interpretToValue :: Text -> IO (Either Error Value)+ interpretToValue =+ runJsonnetM (Config "External variable" mempty)+ . (parse >=> check >=> desugar >=> evaluateToValue)++ evaluateToValue :: Core -> JsonnetM Value+ evaluateToValue expr = do+ env <- singleton "std" <$> std+ JsonnetM $ lift $ ExceptT $ runEvalM env (whnf expr)++ dieError :: Error -> IO a+ dieError = die . show . prettyError++constructExtVars :: [(Text, ExtVar)] -> IO ExtVars+constructExtVars = fmap (ExtVars . M.fromList) . traverse interpretExtVarPair+ where+ interpretExtVarPair :: (Text, ExtVar) -> IO (Text, Value)+ interpretExtVarPair (s, extVar) = (s,) <$> interpretExtVar extVar
src/Language/Jsonnet/Annotate.hs view
@@ -20,6 +20,8 @@ -- | Annotated fixed-point type. Equivalent to CoFree f a type Ann f a = Fix (AnnF f a) +{-# COMPLETE AnnF #-}+pattern AnnF :: f a -> ann -> AnnF f ann a pattern AnnF f a = Pair (Const a) f annMap :: Functor f => (a -> b) -> Ann f a -> Ann f b@@ -27,8 +29,11 @@ where go (Fix (AnnF f a)) = Fix $ AnnF (fmap go f) (g a) -forget :: Functor f => Ann f a -> Fix f-forget (Fix (AnnF f _)) = Fix $ fmap forget f+forget :: Functor f => Ann f a -> Ann f ()+forget = annMap (const ())++forget' :: Functor f => Ann f a -> Fix f+forget' (Fix (AnnF f _)) = Fix $ fmap forget' f attrib :: Ann f a -> a attrib (Fix (AnnF _ a)) = a
src/Language/Jsonnet/Check.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE LambdaCase #-}- -- | -- Module : Language.Jsonnet.Check -- Copyright : (c) 2020-2021 Alexandre Moreno@@ -12,16 +9,13 @@ import Control.Monad.Except import Data.Fix-import Data.Functor.Identity-import Data.List+import Data.List qualified as List import qualified Data.List.NonEmpty as NE import Language.Jsonnet.Annotate import Language.Jsonnet.Common hiding (span)-import Language.Jsonnet.Core import Language.Jsonnet.Error import Language.Jsonnet.Parser.SrcSpan import Language.Jsonnet.Syntax-import Unbound.Generics.LocallyNameless type Check = ExceptT Error IO @@ -34,18 +28,18 @@ EApply _ (Args as _) -> checkApply as _ -> pure () checkLocal names = case dups names of- [] -> pure ()- (xs : _) -> throwError $ DuplicateBinding (head xs)+ ((x:_) : _) -> throwError $ DuplicateBinding x+ _ -> pure () checkFun names = case dups names of- [] -> pure ()- (xs : _) -> throwError $ DuplicateParam (head xs)+ ((x:_) : _) -> throwError $ DuplicateParam x+ _ -> pure () checkApply args = case f args of [] -> pure ()- (x : _) -> throwError $ PosAfterNamedParam+ _ -> throwError $ PosAfterNamedParam where- f args = filter isPos ns+ f _ = filter isPos ns isPos = \case Pos _ -> True _ -> False- (ps, ns) = span isPos args- dups = filter ((> 1) . length) . group . sort+ (_, ns) = span isPos args+ dups = filter ((> 1) . length) . List.group . List.sort
src/Language/Jsonnet/Common.hs view
@@ -1,14 +1,5 @@ {-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE UndecidableInstances #-} -- | -- Module : Language.Jsonnet.Common@@ -24,39 +15,47 @@ import Data.Functor.Classes import Data.Functor.Classes.Generic import Data.Scientific (Scientific)-import Data.String import Data.Text (Text)+import qualified Data.Text as T (pack) import Data.Typeable (Typeable) import GHC.Generics (Generic, Generic1)-import Language.Jsonnet.Parser.SrcSpan-import Text.Show.Deriving-import Unbound.Generics.LocallyNameless+import Language.Jsonnet.Parser.SrcSpan (SrcSpan)+import Unbound.Generics.LocallyNameless (Alpha (..), Name, name2String) import Unbound.Generics.LocallyNameless.TH (makeClosedAlpha) +n2s :: Name a -> Text+n2s = T.pack . name2String+ data Literal = Null | Bool Bool | String Text | Number Scientific- deriving (Show, Eq, Ord, Generic, Typeable, Data)+ deriving+ ( Show,+ Eq,+ Ord,+ Generic,+ Typeable,+ Data,+ Binary+ ) makeClosedAlpha ''Literal -instance Binary Literal--instance Subst a Literal where- subst _ _ = id- substs _ = id- data Prim = UnyOp UnyOp | BinOp BinOp | Cond- deriving (Show, Eq, Generic, Typeable, Data)--instance Alpha Prim--instance Binary Prim+ deriving+ ( Show,+ Eq,+ Generic,+ Typeable,+ Data,+ Alpha,+ Binary+ ) data BinOp = Add@@ -79,11 +78,17 @@ | LOr | In | Lookup- deriving (Show, Eq, Generic, Typeable, Data)--instance Alpha BinOp--instance Binary BinOp+ deriving+ ( Show,+ Eq,+ Enum,+ Bounded,+ Generic,+ Typeable,+ Data,+ Alpha,+ Binary+ ) data UnyOp = Compl@@ -91,18 +96,30 @@ | Plus | Minus | Err- deriving (Show, Eq, Generic, Typeable, Data)--instance Alpha UnyOp--instance Binary UnyOp+ deriving+ ( Eq,+ Show,+ Read,+ Enum,+ Bounded,+ Generic,+ Typeable,+ Data,+ Alpha,+ Binary+ ) data Strictness = Strict | Lazy- deriving (Eq, Read, Show, Generic, Typeable, Data)--instance Alpha Strictness--instance Binary Strictness+ deriving+ ( Eq,+ Show,+ Read,+ Generic,+ Typeable,+ Data,+ Alpha,+ Binary+ ) data Arg a = Pos a | Named String a deriving@@ -115,14 +132,13 @@ Generic1, Functor, Foldable,- Traversable+ Traversable,+ Alpha,+ Binary )--deriveShow1 ''Arg--instance Alpha a => Alpha (Arg a)--instance Binary a => Binary (Arg a)+ deriving+ (Eq1, Show1)+ via FunctorClassesDefault Arg data Args a = Args { args :: [Arg a],@@ -135,16 +151,16 @@ Typeable, Data, Generic,+ Generic1, Functor, Foldable,- Traversable+ Traversable,+ Alpha,+ Binary )--deriveShow1 ''Args--instance Alpha a => Alpha (Args a)--instance Binary a => Binary (Args a)+ deriving+ (Eq1, Show1)+ via FunctorClassesDefault Args data Assert a = Assert { cond :: a,@@ -158,18 +174,22 @@ Typeable, Data, Generic,+ Generic1, Functor, Foldable,- Traversable+ Traversable,+ Alpha )--instance Alpha a => Alpha (Assert a)--deriveShow1 ''Assert+ deriving+ (Eq1, Show1)+ via FunctorClassesDefault Assert data CompSpec a = CompSpec- { var :: String,+ { -- |+ var :: String,+ -- | forspec :: a,+ -- | ifspec :: Maybe a } deriving@@ -179,14 +199,15 @@ Typeable, Data, Generic,+ Generic1, Functor, Foldable,- Traversable+ Traversable,+ Alpha )--deriveShow1 ''CompSpec--instance Alpha a => Alpha (CompSpec a)+ deriving+ (Eq1, Show1)+ via FunctorClassesDefault CompSpec data StackFrame a = StackFrame { name :: Name a,@@ -202,14 +223,14 @@ ( Eq, Read, Show,+ Enum,+ Bounded, Generic, Typeable,- Data+ Data,+ Alpha,+ Binary )--instance Alpha Visibility--instance Binary Visibility class HasVisibility a where visible :: a -> Bool
src/Language/Jsonnet/Core.hs view
@@ -1,16 +1,4 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DeriveLift #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTSyntax #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE StandaloneDeriving #-}-+{-# OPTIONS_GHC -Wno-orphans #-} -- | -- Module : Language.Jsonnet.Core -- Copyright : (c) 2020-2021 Alexandre Moreno@@ -21,15 +9,19 @@ module Language.Jsonnet.Core where import Data.Binary (Binary)-import Data.Data (Data)-import Data.String ( IsString(..) )+import Data.String (IsString (..)) import Data.Typeable (Typeable) import GHC.Generics (Generic)-import Language.Jsonnet.Common ( Args, Prim, Literal, Visibility )-import Language.Jsonnet.Parser.SrcSpan ( SrcSpan )-import Text.Megaparsec.Pos (Pos, SourcePos)+import Language.Jsonnet.Common (Args, Literal, Prim, Visibility)+import Language.Jsonnet.Parser.SrcSpan (SrcSpan) import Unbound.Generics.LocallyNameless- ( string2Name, Bind, Rec, Embed, Name, Alpha )+ ( Alpha,+ Bind,+ Embed,+ Name,+ Rec,+ string2Name,+ ) type Param a = (Name a, Embed a)
src/Language/Jsonnet/Desugar.hs view
@@ -1,12 +1,5 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeSynonymInstances #-} -- | -- Module : Language.Jsonnet.Desugar@@ -24,15 +17,13 @@ import Data.Maybe (fromMaybe) import Data.Text (Text) import qualified Data.Text as T (pack)-import Debug.Trace import Language.Jsonnet.Annotate import Language.Jsonnet.Common import Language.Jsonnet.Core import Language.Jsonnet.Error import Language.Jsonnet.Parser.SrcSpan-import Language.Jsonnet.Pretty ()+import Language.Jsonnet.Pretty (prettyEvalError) import Language.Jsonnet.Syntax-import Text.PrettyPrint.ANSI.Leijen hiding (encloseSep, (<$>)) import Unbound.Generics.LocallyNameless class Desugarer a where@@ -58,7 +49,10 @@ alg :: Bool -> ExprF Core -> Core alg outermost = \case- ELit l -> CLit l+ ENull -> CLit Null+ EBool b -> CLit (Bool b)+ EStr s -> CLit (String s)+ ENum n -> CLit (Number n) EIdent i -> CVar (s2n i) EFun ps e -> desugarFun ps e EApply e es -> CApp e es@@ -72,7 +66,7 @@ EIf c t -> desugarIfElse c t (CLit Null) EArr e -> CArr e EObj {..} -> desugarObj outermost locals fields- ELookup e1 e2 -> desugarLookup e1 e2+ ELookup e1 i -> desugarLookup e1 (CLit (String (T.pack i))) EIndex e1 e2 -> desugarLookup e1 e2 EErr e -> desugarErr e EAssert e -> desugarAssert e@@ -80,6 +74,7 @@ EArrComp {expr, comp} -> desugarArrComp expr comp EObjComp {field, comp, locals} -> desugarObjComp field comp locals +desugarSlice :: Core -> Maybe Core -> Maybe Core -> Maybe Core -> Core desugarSlice expr start end step = stdFunc "slice"@@ -109,6 +104,7 @@ desugarUnyOp :: UnyOp -> Core -> Core desugarUnyOp op e = CApp (CPrim (UnyOp op)) (Args [Pos e] Lazy) +desugarObj :: Bool -> [(String, Core)] -> [EField Core] -> Core desugarObj outermost locals fields = obj where obj = CObj (desugarField <$> fields')@@ -125,7 +121,7 @@ xs -> desugarLet (NE.fromList xs) v fields' =- (\(EField key val v o) -> EField key (f val) v o) <$> fields+ (\(EField key val comp v o) -> EField key (f val) comp v o) <$> fields desugarAssert :: Assert Core -> Core desugarAssert (Assert c m e) =@@ -157,6 +153,7 @@ else value super = CVar $ s2n "super" +desugarObjComp :: EField Core -> NonEmpty (CompSpec Core) -> [(String, Core)] -> Core desugarObjComp EField {..} comp locals = CComp (ObjC (bind (s2n "arr") (kv', Nothing))) arrComp where@@ -166,7 +163,8 @@ { key = key', value = value', visibility,- override+ override,+ computed } ) bnds = NE.zip (fmap var comp) xs@@ -174,8 +172,8 @@ value' = case locals of [] -> desugarLet bnds value -- we need to nest the let bindings due to the impl.- xs -> desugarLet bnds $ desugarLet (NE.fromList xs) value- xs = desugarLookup (CVar $ s2n "arr") . CLit . Number . fromIntegral <$> [0 ..]+ xs' -> desugarLet bnds $ desugarLet (NE.fromList xs') value+ xs = desugarLookup (CVar $ s2n "arr") . CLit . Number . fromIntegral @Integer <$> [0 ..] arrComp = desugarArrComp arr comp arr = CArr $ NE.toList $ CVar . s2n . var <$> comp @@ -187,13 +185,14 @@ (CLit $ String f) ) +desugarFun :: [(String, Maybe Core)] -> Core -> Core desugarFun ps e = CLam $ bind ( rec $ fmap ( \(n, a) ->- (s2n n, Embed (fromMaybe (errNotBound n) a))+ (s2n n, Embed (fromMaybe (errNotBound (T.pack n)) a)) ) ps )@@ -205,9 +204,10 @@ String ( T.pack $ show $- pretty $ ParamNotBound (pretty n)+ prettyEvalError $ ParamNotBound n ) +desugarLet :: NonEmpty (String, Core) -> Core -> Core desugarLet bnds e = CLet $ bind
src/Language/Jsonnet/Error.hs view
@@ -1,8 +1,3 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}- -- | -- Module : Language.Jsonnet.Error -- Copyright : (c) 2020-2021 Alexandre Moreno@@ -21,34 +16,37 @@ import Language.Jsonnet.Core import Language.Jsonnet.Parser.SrcSpan import Text.Megaparsec (ParseErrorBundle)-import Text.PrettyPrint.ANSI.Leijen (Doc) data Error = ParserError ParserError | CheckError CheckError (Maybe SrcSpan) | EvalError EvalError (Backtrace Core)- deriving (Show)+ deriving (Eq, Show) +instance Exception Error+ data EvalError = TypeMismatch- { expected :: Text,- actual :: Text- }- | InvalidKey Doc- | DuplicateKey Doc- | NoSuchKey Doc- | InvalidIndex Doc+ Text+ -- ^ expected+ Text+ -- ^ actual+ | InvalidKey Text+ | DuplicateKey Text+ | NoSuchKey Text+ | InvalidIndex Text | IndexOutOfBounds Scientific | DivByZero- | VarNotFound Doc- | AssertionFailed Doc+ | VarNotFound Text+ | ExtVarNotFound Text+ | AssertionFailed Text | TooManyArgs Int- | ParamNotBound Doc- | BadParam Doc- | StdError Doc- | RuntimeError Doc- | ManifestError Doc- deriving (Show, Typeable)+ | ParamNotBound Text+ | BadParam Text+ | StdError Text+ | RuntimeError Text+ | ManifestError Text+ deriving (Eq, Show, Typeable) instance Exception EvalError @@ -61,4 +59,4 @@ = DuplicateParam String | PosAfterNamedParam | DuplicateBinding String- deriving (Show)+ deriving (Eq, Show)
src/Language/Jsonnet/Eval.hs view
@@ -1,12 +1,7 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecursiveDo #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_GHC -Wno-orphans #-} -- | -- Module : Language.Jsonnet.Eval@@ -19,11 +14,14 @@ import Control.Applicative import Control.Lens (locally, view)-import Control.Monad.Except+import Control.Monad ((>=>), join, forM, (<=<))+import Control.Monad.IO.Class (liftIO) import qualified Data.Aeson as JSON+import qualified Data.Aeson.KeyMap as KeyMap import Data.Aeson.Text (encodeToLazyText) import Data.Bifunctor (second)-import Data.Bits+import qualified Data.Bits as Bits+import Data.Bits ((.&.), (.|.)) import Data.ByteString (ByteString) import Data.Foldable import Data.HashMap.Lazy (HashMap)@@ -41,14 +39,12 @@ import Data.Traversable (for) import Data.Vector (Vector, (!?)) import qualified Data.Vector as V-import Debug.Trace import Language.Jsonnet.Common import Language.Jsonnet.Core import Language.Jsonnet.Error import Language.Jsonnet.Eval.Monad import Language.Jsonnet.Pretty () import Language.Jsonnet.Value-import Text.PrettyPrint.ANSI.Leijen hiding (equals, (<$>)) import Unbound.Generics.LocallyNameless import Prelude hiding (length) import qualified Prelude as P (length)@@ -82,7 +78,7 @@ lookupVar :: Name Core -> Eval Value lookupVar n = do rho <- view ctx- v <- liftMaybe (VarNotFound (pretty n)) (M.lookup n rho)+ v <- liftMaybe (VarNotFound (n2s n)) (M.lookup n rho) whnfV v whnfLiteral :: Literal -> Value@@ -105,8 +101,8 @@ VPrim op -> whnfPrim op vs v@(VFun _) -> foldlM f v vs where- f (VFun g) (Pos v) = g v- f v _ = throwTypeMismatch "function" v+ f (VFun g) (Pos v') = g v'+ f v' _ = throwTypeMismatch "function" v' v -> throwTypeMismatch "function" v withStackFrame :: Core -> Eval a -> Eval a@@ -135,14 +131,15 @@ bnds <- M.fromList <$> mapM- ( \(n, e) -> do- th <- extendEnv bnds (mkValue e)+ ( \(n, e') -> do+ th <- extendEnv bnds (mkValue e') pure (n, th) ) rs extendEnv bnds (whnf e) -- returns a triple with unapplied binders, positional and named+splitArgs :: [Arg c] -> [(Name a, b)] -> EvalM s ([(Name a, b)], [(Name a, c)], [(Name a, c)]) splitArgs args bnds = do named <- getNamed pos <- getPos@@ -162,14 +159,14 @@ getNamed = traverse f ns where f (a, b) = case g a of- Nothing -> throwE $ BadParam (pretty a)+ Nothing -> throwE $ BadParam (T.pack a) Just n -> pure (n, b) g a = find ((a ==) . name2String) pNames getUnapp named =- pure $ filter ((`notElem` ns) . fst) bnds2+ pure $ filter ((`notElem` ns') . fst) bnds2 where- ns = map fst named+ ns' = map fst named split [] = ([], []) split (Pos p : xs) =@@ -206,10 +203,10 @@ whnfBinOp Ge e1 e2 = liftF2 ((>=) @Double) e1 e2 whnfBinOp And e1 e2 = liftF2 ((.&.) @Int64) e1 e2 whnfBinOp Or e1 e2 = liftF2 ((.|.) @Int64) e1 e2-whnfBinOp Xor e1 e2 = liftF2 (xor @Int64) e1 e2-whnfBinOp ShiftL e1 e2 = liftF2 (shiftL @Int64) e1 e2-whnfBinOp ShiftR e1 e2 = liftF2 (shiftR @Int64) e1 e2-whnfBinOp In s o = liftF2 (\o s -> objectHasEx o s True) o s+whnfBinOp Xor e1 e2 = liftF2 (Bits.xor @Int64) e1 e2+whnfBinOp ShiftL e1 e2 = liftF2 (Bits.shiftL @Int64) e1 e2+whnfBinOp ShiftR e1 e2 = liftF2 (Bits.shiftR @Int64) e1 e2+whnfBinOp In s o = liftF2 (\o' s' -> objectHasEx o' s' True) o s whnfLogical :: HasValue a => (a -> Bool) -> Value -> Value -> Eval Value whnfLogical f e1 e2 = do@@ -222,11 +219,11 @@ append v1 v2 = T.append <$> toString v1 <*> toString v2 whnfUnyOp :: UnyOp -> Value -> Eval Value-whnfUnyOp Compl x = inj <$> fmap (complement @Int64) (proj' x)+whnfUnyOp Compl x = inj <$> fmap (Bits.complement @Int64) (proj' x) whnfUnyOp LNot x = inj <$> fmap not (proj' x) whnfUnyOp Minus x = inj <$> fmap (negate @Double) (proj' x) whnfUnyOp Plus x = inj <$> fmap (id @Double) (proj' x)-whnfUnyOp Err x = (toString >=> throwE . RuntimeError . pretty) x+whnfUnyOp Err x = (toString >=> throwE . RuntimeError) x toString :: Value -> Eval Text toString (VStr s) = pure s@@ -241,7 +238,7 @@ whnfLookup :: Value -> Value -> Eval Value whnfLookup (VObj o) (VStr s) =- whnfV . fieldValWHNF =<< liftMaybe (NoSuchKey (pretty s)) (H.lookup s o)+ whnfV . fieldValWHNF =<< liftMaybe (NoSuchKey s) (H.lookup s o) whnfLookup (VArr a) (VNum i) | isInteger i = whnfV =<< liftMaybe (IndexOutOfBounds i) ((a !?) =<< toBoundedInteger i)@@ -374,13 +371,13 @@ f a b | hidden a && visible b = a | otherwise = b- update name xs f@VField {..} = case fieldVal of+ update name xs' f'@VField {..} = case fieldVal of VThunk c env -> do- let env' = M.insert name xs env- let fieldVal = VThunk c env'- fieldValWHNF <- mkIndirV fieldVal- pure VField {..}- _ -> pure f+ let env' = M.insert name xs' env+ let fieldVal' = VThunk c env'+ fieldValWHNF' <- mkIndirV fieldVal'+ pure VField {fieldVal = fieldVal', fieldValWHNF = fieldValWHNF', ..}+ _ -> pure f' visibleKeys :: Object -> HashMap Text Value visibleKeys = H.mapMaybe f@@ -401,7 +398,7 @@ VBool b -> pure (JSON.Bool b) VStr s -> pure (JSON.String s) VNum n -> pure (JSON.Number n)- VObj vs -> JSON.Object <$> mapM manifest (visibleKeys vs)+ VObj vs -> JSON.Object . KeyMap.fromHashMapText <$> mapM manifest (visibleKeys vs) VArr vs -> JSON.Array <$> mapM manifest vs VClos {} -> throwE (ManifestError "function") VFun _ -> throwE (ManifestError "function")@@ -414,21 +411,16 @@ objectFieldsEx o False = L.sort $ H.keys $ H.filter (not . hidden) o -- only visible (incl. forced) objectHasEx :: Object -> Text -> Bool -> Bool-objectHasEx o f all = f `elem` objectFieldsEx o all+objectHasEx o f all' = f `elem` objectFieldsEx o all' primitiveEquals :: Value -> Value -> Eval Bool primitiveEquals VNull VNull = pure True primitiveEquals (VBool a) (VBool b) = pure (a == b) primitiveEquals (VStr a) (VStr b) = pure (a == b) primitiveEquals (VNum a) (VNum b) = pure (a == b)-primitiveEquals a b =+primitiveEquals _ _ = throwE- ( StdError $- text $- T.unpack $- "primitiveEquals operates on primitive types "- -- <> showTy a- -- <> showTy b+ ( StdError "primitiveEquals operates on primitive types " ) equals :: Value -> Value -> Eval Bool
src/Language/Jsonnet/Eval/Monad.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-} -- |@@ -15,13 +12,10 @@ import Control.Lens (locally, makeLenses, view) import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow) import Control.Monad.Except- ( ExceptT (..),- MonadError (throwError),- MonadFix,- MonadIO,- runExceptT,- )+import Control.Monad.Fix (MonadFix)+import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Reader (MonadReader, ReaderT (..))+import Data.IORef import Data.Map.Lazy (Map) import qualified Data.Map.Lazy as M (union) import Language.Jsonnet.Common (Backtrace (..), StackFrame (..))@@ -29,12 +23,7 @@ import Language.Jsonnet.Error (Error (EvalError), EvalError) import Language.Jsonnet.Parser.SrcSpan (SrcSpan) import Unbound.Generics.LocallyNameless- ( Fresh,- FreshMT,- Name,- runFreshMT,- s2n,- )+import Unbound.Generics.LocallyNameless.Name type Ctx a = Map (Name Core) a @@ -57,15 +46,17 @@ -- | call-stack simulation _callStack :: CallStack, -- | source span of expression being evaluated- _currentPos :: Maybe SrcSpan+ _currentPos :: Maybe SrcSpan,+ -- | fresh names+ _gen :: IORef Integer } makeLenses ''EvalState newtype EvalM a b = EvalM- { unEval :: ExceptT Error (ReaderT (EvalState a) (FreshMT IO)) b+ { unEval :: ExceptT Error (ReaderT (EvalState a) IO) b }- deriving+ deriving newtype ( Functor, Applicative, Monad,@@ -76,14 +67,22 @@ MonadCatch, MonadMask, MonadFail,- MonadFix,- Fresh+ MonadFix ) +instance Fresh (EvalM a) where+ fresh (Fn s _) = EvalM $ do+ ref <- view gen+ n <- liftIO $ readIORef ref+ liftIO $ modifyIORef' ref (+ 1)+ return $ (Fn s n)+ fresh nm@(Bn {}) = return nm+ runEvalM :: Ctx a -> EvalM a b -> IO (Either Error b)-runEvalM ctx e = runFreshMT (runReaderT (runExceptT (unEval e)) st)- where- st = EvalState ctx emptyStack Nothing+runEvalM ctx' e = do+ gen' <- newIORef 0+ let st = EvalState ctx' emptyStack Nothing gen'+ (`runReaderT` st) (runExceptT (unEval e)) throwE :: EvalError -> EvalM a b throwE e = throwError . EvalError e =<< getBacktrace@@ -95,9 +94,9 @@ withEnv = locally ctx . const pushStackFrame :: (Name Core, Maybe SrcSpan) -> EvalM a b -> EvalM a b-pushStackFrame (name, span) =+pushStackFrame (name, span') = locally (callStack . scopes) (name :)- . locally (callStack . spans) (span :)+ . locally (callStack . spans) (span' :) getBacktrace :: EvalM a (Backtrace Core) getBacktrace = do@@ -106,5 +105,5 @@ pure $ Backtrace $ case sequence sp of- Just sp -> zipWith StackFrame sc sp+ Just sp' -> zipWith StackFrame sc sp' Nothing -> []
src/Language/Jsonnet/Parser.hs view
@@ -1,7 +1,4 @@-{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TupleSections #-} -- | -- Module : Language.Jsonnet.Parser@@ -15,6 +12,7 @@ module Language.Jsonnet.Parser ( parse, resolveImports,+ reservedKeywords, ) where @@ -24,18 +22,17 @@ import Control.Monad.Combinators.Expr import qualified Control.Monad.Combinators.NonEmpty as NE import Control.Monad.Except+import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Char import Data.Either import Data.Fix import Data.Functor import Data.Functor.Sum-import Data.List (intercalate) import Data.List.NonEmpty (NonEmpty) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Void-import GHC.IO.Exception hiding (IOError) import Language.Jsonnet.Annotate import Language.Jsonnet.Common import Language.Jsonnet.Error@@ -48,6 +45,8 @@ import Text.Megaparsec hiding (ParseError, parse) import Text.Megaparsec.Char import qualified Text.Megaparsec.Char.Lexer as L+import Data.Word (Word8)+import qualified Data.ByteString as BS type Parser = Parsec Void Text @@ -64,7 +63,7 @@ left (ParserError . ParseError) $ runParser (sc *> exprP <* eof) fp inp -resolveImports ::+resolveImports :: forall m. (MonadError Error m, MonadIO m) => -- | File path (modules are resolved relative to this path) FilePath ->@@ -75,20 +74,42 @@ resolveImports fp = foldFixM go where go (AnnF (InL e) a) = pure $ Fix $ AnnF e a- go (AnnF (InR (Const (Import fp'))) a) =- resolveImports fp'- =<< parse fp'- =<< readImportFile fp' a- readImportFile fp' a = do- inp <- readFile' fp'+ go (AnnF (InR (Const import')) a) =+ case import' of+ Import fp' -> do+ resolveImports fp'+ =<< parse fp'+ =<< readImportFile fp' a+ Importstr fp' -> do+ content <- readImportFile fp' a+ pure $ Fix $ AnnF (EStr content) a+ Importbin fp' -> do+ content <- readBinaryFile fp' a+ pure $ mkBinaryLiteral content a++ readImportFile :: FilePath -> SrcSpan -> m Text+ readImportFile = readFileWith T.readFile++ readBinaryFile :: FilePath -> SrcSpan -> m [Word8]+ readBinaryFile = readFileWith (fmap BS.unpack . BS.readFile)++ readFileWith :: (FilePath -> IO a) -> FilePath -> SrcSpan -> m a+ readFileWith reader fp' a = do+ inp <- (handleIO . reader) fp' liftEither $ left (ParserError . flip ImportError (Just a)) inp where- readFile' =+ handleIO :: IO a -> m (Either IOError a)+ handleIO = liftIO- . tryIOError- . withCurrentDirectory (takeDirectory fp)- . T.readFile+ . tryIOError+ . withCurrentDirectory (takeDirectory fp) + mkBinaryLiteral :: [Word8] -> SrcSpan -> Expr+ mkBinaryLiteral ws a = Fix $ AnnF (EArr $ map wordExpr ws) a+ where+ wordExpr :: Word8 -> Expr+ wordExpr w = Fix $ AnnF (ENum (fromIntegral w)) a+ sc :: Parser () sc = L.space space1 lineComment blockComment where@@ -200,8 +221,8 @@ sc'' :: Pos -> Parser () sc'' x = void $ count' 0 (unPos x - 1) (oneOf [' ', '\t']) -unquoted :: Parser Expr'-unquoted = Fix <$> annotateLoc (mkStrF <$> identifier)+identStringP :: Parser Expr'+identStringP = Fix <$> annotateLoc (mkStrF <$> identifier) stringP :: Parser Expr' stringP =@@ -213,31 +234,36 @@ <|> textBlock ) )+ <?> "string" numberP :: Parser Expr'-numberP = Fix <$> annotateLoc number+numberP = Fix <$> annotateLoc number <?> "number" where number = mkFloatF <$> lexeme L.scientific identP :: Parser Expr'-identP = Fix <$> annotateLoc (mkIdentF <$> (try (T.unpack <$> symbol "$") <|> identifier))+identP =+ Fix+ <$> annotateLoc+ (mkIdentF <$> (try (T.unpack <$> symbol "$") <|> identifier))+ <?> "identifier" booleanP :: Parser Expr'-booleanP = Fix <$> annotateLoc boolean+booleanP = Fix <$> annotateLoc boolean <?> "bool" where boolean = keywordP "true" $> mkBoolF True <|> keywordP "false" $> mkBoolF False nullP :: Parser Expr'-nullP = Fix <$> annotateLoc null+nullP = Fix <$> annotateLoc null' where- null = keywordP "null" $> mkNullF+ null' = keywordP "null" $> mkNullF errorP :: Parser Expr'-errorP = Fix <$> annotateLoc error+errorP = Fix <$> annotateLoc error' where- error = keywordP "error" *> (mkErrorF <$> exprP)+ error' = keywordP "error" *> (mkErrorF <$> exprP) assertP :: Parser Expr' assertP = Fix <$> annotateLoc assert@@ -249,7 +275,7 @@ mkAssertF cond msg <$> exprP ifElseP :: Parser Expr'-ifElseP = Fix <$> annotateLoc ifElseExpr+ifElseP = Fix <$> annotateLoc ifElseExpr <?> "if" where ifElseExpr = do cond <- keywordP "if" *> exprP@@ -267,7 +293,10 @@ Parser [Param Expr'] -> Parser Expr' -> Parser Expr'-function ps expr = Fix <$> annotateLoc (mkFunF <$> ps <*> expr)+function ps expr =+ Fix+ <$> annotateLoc (mkFunF <$> ps <*> expr)+ <?> "function" functionP :: Parser Expr' functionP = keywordP "function" *> function paramsP exprP@@ -302,7 +331,7 @@ (try binding <|> localFunc) `NE.sepBy1` comma localP :: Parser Expr'-localP = Fix <$> annotateLoc localExpr+localP = Fix <$> annotateLoc localExpr <?> "local" where localExpr = do bnds <- localBndsP@@ -310,7 +339,7 @@ mkLocalF bnds <$> exprP arrayP :: Parser Expr'-arrayP = Fix <$> annotateLoc (brackets (try arrayComp <|> array))+arrayP = Fix <$> annotateLoc (brackets (try arrayComp <|> array)) <?> "array" where array = mkArrayF <$> (exprP `sepEndBy` comma) arrayComp = do@@ -319,24 +348,25 @@ return $ mkArrCompF expr comps objectP :: Parser Expr'-objectP = Fix <$> annotateLoc (braces (try objectComp <|> object))+objectP = Fix <$> annotateLoc (braces (try objectComp <|> object)) <?> "object" where object = do- xs <- eitherP localP fieldP `sepEndBy` comma+ xs <- eitherP localP' fieldP `sepEndBy` comma let (ls, fs) = (lefts xs, rights xs) pure $ mkObjectF fs ls fieldP = try methodP <|> pairP pairP = do- key <- keyP+ (key, computed) <- keyP (override, visibility) <- (,) <$> option False (symbol "+" $> True) <*> sepP value <- exprP pure $ EField {..}- keyP = brackets exprP <|> unquoted <|> stringP+ keyP = ((,True) <$> brackets exprP) <|> ((,False) <$> (identStringP <|> stringP)) methodP = do let override = False- key <- unquoted+ computed = False+ key <- identStringP ps <- paramsP visibility <- sepP value <- function (pure ps) exprP@@ -345,21 +375,31 @@ try (symbol ":::" $> Forced) <|> try (symbol "::" $> Hidden) <|> symbol ":" $> Visible- localP = do+ localP' = do _ <- keywordP "local" try binding <|> localFunc objectComp = do- locals1 <- localP `sepEndBy` comma+ locals1 <- localP' `sepEndBy` comma expr <- pairP <* optional comma- locals2 <- localP `sepEndBy` comma+ locals2 <- localP' `sepEndBy` comma comps <- NE.some forspecP return $ mkObjCompF expr (locals1 <> locals2) comps importP :: Parser Expr'-importP = Fix <$> annotateLoc importDecl+importP = Fix <$> annotateLoc importDecl <?> "import" where importDecl = mkImportF <$> (keywordP "import" *> stringLiteral) +importstrP :: Parser Expr'+importstrP = Fix <$> annotateLoc importstrDecl <?> "importstr"+ where+ importstrDecl = mkImportstrF <$> (keywordP "importstr" *> stringLiteral)++importbinP :: Parser Expr'+importbinP = Fix <$> annotateLoc importbinDecl <?> "importbin"+ where+ importbinDecl = mkImportbinF <$> (keywordP "importbin" *> stringLiteral)+ binary :: Text -> (Expr' -> Expr' -> Expr') ->@@ -446,7 +486,15 @@ indexP = flip mkIndex <$> brackets exprP lookupP :: Parser (Expr' -> Expr')-lookupP = flip mkLookup <$> (symbol "." *> unquoted)+lookupP = flip mkLookup <$> (symbol "." *> annotatedIdent) <?> "."+ where+ annotatedIdent :: Parser (Ident, SrcSpan)+ annotatedIdent = do+ begin <- getSourcePos+ res <- identifier+ end <- getSourcePos+ let sourceSpan = SrcSpan begin end+ pure (res, sourceSpan) -- arguments are many postional followed by many named -- just like Python@@ -482,6 +530,8 @@ objectP, arrayP, localP,+ importstrP,+ importbinP, importP, errorP, assertP,@@ -502,6 +552,7 @@ "if", "import", "importstr",+ "importbin", "in", "local", "null",
src/Language/Jsonnet/Parser/SrcSpan.hs view
@@ -1,8 +1,6 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-orphans #-} -- | -- Module : Language.Jsonnet.Parser.SrcSpan@@ -43,7 +41,7 @@ instance Binary SrcSpan -instance Subst b SrcSpan where+instance Subst b SourcePos => Subst b SrcSpan where subst _ _ = id substs _ = id
src/Language/Jsonnet/Pretty.hs view
@@ -1,10 +1,3 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# OPTIONS_GHC -Wno-orphans #-}- -- | -- Module : Language.Jsonnet.Pretty -- Copyright : (c) 2020-2021 Alexandre Moreno@@ -14,171 +7,354 @@ -- Portability : non-portable module Language.Jsonnet.Pretty where +import Control.Applicative (Const (..)) import qualified Data.Aeson as JSON+import qualified Data.Aeson.Key as Key+import qualified Data.Aeson.KeyMap as KeyMap import qualified Data.Aeson.Text as JSON (encodeToLazyText)-import qualified Data.HashMap.Lazy as H+import Data.Bifunctor (bimap, first)+import Data.Bool (bool)+import Data.Fix+import Data.Functor.Sum+import Data.Maybe (fromMaybe) import Data.List (sortOn)+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NE import Data.Scientific (Scientific (..))-import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Lazy as LT import Data.Text.Lazy.Builder import Data.Text.Lazy.Builder.Scientific (scientificBuilder) import qualified Data.Vector as V import GHC.IO.Exception (IOException (..))+import Language.Jsonnet.Annotate import Language.Jsonnet.Common import Language.Jsonnet.Error import Language.Jsonnet.Parser.SrcSpan+import Language.Jsonnet.Syntax+import qualified Language.Jsonnet.Syntax.Annotated as Ann+import Prettyprinter import Text.Megaparsec.Error (errorBundlePretty) import Text.Megaparsec.Pos-import Text.PrettyPrint.ANSI.Leijen hiding (encloseSep, (<$>)) import Unbound.Generics.LocallyNameless (Name, name2String) -instance Pretty (Name a) where- pretty v = pretty (name2String v)+(<$$>) :: Doc ann -> Doc ann -> Doc ann+(<$$>) = \x y -> vcat [x, y] -instance Pretty Text where- pretty v = pretty (T.unpack v)+-- reserved keywords+pnull, ptrue, pfalse, pif, pthen, pelse, pimport, pimportstr, pimportbin,+ perror, plocal, pfunction, passert, pfor, pin :: Doc ann+pnull = pretty "null"+ptrue = pretty "true"+pfalse = pretty "false"+pif = pretty "if"+pthen = pretty "then"+pelse = pretty "else"+pimport = pretty "import"+pimportstr = pretty "importstr"+pimportbin = pretty "importbin"+perror = pretty "error"+plocal = pretty "local"+pfunction = pretty "function"+passert = pretty "assert"+pfor = pretty "for"+pin = pretty "in" +prettyName :: Name a -> Doc ann+prettyName = pretty . name2String++ppNumber :: Scientific -> Doc ann ppNumber s | e < 0 || e > 1024 =- text $+ pretty $ LT.unpack $ toLazyText $ scientificBuilder s- | otherwise = integer (coefficient s * 10 ^ e)+ | otherwise = pretty (coefficient s * 10 ^ e) where e = base10Exponent s -ppJson :: Int -> JSON.Value -> Doc-ppJson i =- \case- JSON.Null -> text "null"- JSON.Number n -> ppNumber n- JSON.Bool True -> text "true"- JSON.Bool False -> text "false"- JSON.String s -> ppString s- JSON.Array a -> ppArray a- JSON.Object o -> ppObject o+ppJson :: Int -> JSON.Value -> Doc ann+ppJson i = \case+ JSON.Null -> pnull+ JSON.Number n -> ppNumber n+ JSON.Bool True -> ptrue+ JSON.Bool False -> pfalse+ JSON.String s -> ppString (Key.fromText s)+ JSON.Array a -> ppArray a+ JSON.Object o -> ppObject' o where- encloseSep l r s ds = case ds of+ encloseSep' l r s ds = case ds of [] -> l <> r _ -> l <$$> indent i (vcat $ punctuate s ds) <$$> r- ppObject o = encloseSep lbrace rbrace comma xs+ ppObject' o = encloseSep' lbrace rbrace comma xs where prop (k, v) = ppString k <> colon <+> ppJson i v- xs = map prop (sortOn fst $ H.toList o)- ppArray a = encloseSep lbracket rbracket comma xs+ xs = map prop (sortOn fst $ KeyMap.toList o)+ ppArray a = encloseSep' lbracket rbracket comma xs where xs = map (ppJson i) (V.toList a)- ppString = text . LT.unpack . JSON.encodeToLazyText+ ppString = pretty . LT.unpack . JSON.encodeToLazyText -instance Pretty JSON.Value where- pretty = ppJson 4+prettySrcSpan :: SrcSpan -> Doc ann+prettySrcSpan SrcSpan {spanBegin, spanEnd} =+ pretty (sourceName spanBegin)+ <> colon+ <> lc spanBegin spanEnd+ where+ lc (SourcePos _ lb cb) (SourcePos _ le ce)+ | lb == le =+ pretty (unPos lb) <> colon+ <> pretty (unPos cb)+ <> dash+ <> pretty (unPos ce)+ | otherwise =+ pretty (unPos lb) <> colon <> pretty (unPos cb) <> dash+ <> pretty (unPos le)+ <> colon+ <> pretty (unPos ce)+ dash = pretty '-' -instance Pretty SrcSpan where- pretty SrcSpan {spanBegin, spanEnd} =- text (sourceName spanBegin)- <> colon- <> lc spanBegin spanEnd- where- lc (SourcePos _ lb cb) (SourcePos _ le ce)- | lb == le =- int (unPos lb) <> colon- <> int (unPos cb)- <> dash- <> int (unPos ce)- | otherwise =- int (unPos lb) <> colon <> int (unPos cb) <> dash- <> int (unPos le)- <> colon- <> int (unPos ce)- dash = char '-'+prettyParserError :: ParserError -> Doc ann+prettyParserError (ParseError e) = pretty (errorBundlePretty e)+prettyParserError (ImportError (IOError _ _ _ desc _ f) sp) =+ pretty "Parse error:"+ <+> pretty f+ <+> parens (pretty desc)+ <$$> indent 4 (maybe mempty prettySrcSpan sp) -instance Pretty ParserError where- pretty (ParseError e) = pretty (errorBundlePretty e)- pretty (ImportError (IOError _ _ _ desc _ f) sp) =- text "Parse error:"- <+> pretty f- <+> parens (text desc)- <$$> indent 4 (pretty sp)+prettyCheckError :: CheckError -> Doc ann+prettyCheckError = \case+ DuplicateParam e ->+ pretty "duplicate parameter"+ <+> squotes (pretty e)+ DuplicateBinding e ->+ pretty "duplicate local var"+ <+> squotes (pretty e)+ PosAfterNamedParam ->+ pretty "positional after named argument" -instance Pretty CheckError where- pretty =- \case- DuplicateParam e ->- text "duplicate parameter"- <+> squotes (text e)- DuplicateBinding e ->- text "duplicate local var"- <+> squotes (text e)- PosAfterNamedParam ->- text "positional after named argument"+prettyEvalError :: EvalError -> Doc ann+prettyEvalError = \case+ TypeMismatch expected actual ->+ pretty "type mismatch:"+ <+> pretty "expected"+ <+> pretty (T.unpack expected)+ <+> pretty "but got"+ <+> pretty (T.unpack actual)+ InvalidKey k ->+ pretty "invalid key:"+ <+> pretty k+ DuplicateKey k ->+ pretty "duplicate key:"+ <+> pretty k+ InvalidIndex k ->+ pretty "invalid index:"+ <+> pretty k+ NoSuchKey k ->+ pretty "no such key:"+ <+> pretty k+ IndexOutOfBounds i ->+ pretty "index out of bounds:"+ <+> ppNumber i+ DivByZero ->+ pretty "divide by zero exception"+ VarNotFound v ->+ pretty "variable"+ <+> squotes (pretty $ show v)+ <+> pretty "is not defined"+ ExtVarNotFound v ->+ pretty "external variable"+ <+> squotes (pretty v)+ <+> pretty "is not defined"+ AssertionFailed e ->+ pretty "assertion failed:" <+> pretty e+ StdError e -> pretty e+ RuntimeError e -> pretty e+ ParamNotBound s ->+ pretty "parameter not bound:"+ <+> pretty (show s)+ BadParam s ->+ pretty "function has no parameter"+ <+> squotes (pretty s)+ ManifestError e ->+ pretty "manifest error:"+ <+> pretty e+ TooManyArgs n ->+ pretty "too many args, function has"+ <+> pretty n+ <+> pretty "parameter(s)" -instance Pretty EvalError where- pretty =- \case- TypeMismatch {..} ->- text "type mismatch:"- <+> text "expected"- <+> text (T.unpack expected)- <+> text "but got"- <+> text (T.unpack actual)- InvalidKey k ->- text "invalid key:"- <+> k- InvalidIndex k ->- text "invalid index:"- <+> k- NoSuchKey k ->- text "no such key:"- <+> k- IndexOutOfBounds i ->- text "index out of bounds:"- <+> ppNumber i- DivByZero ->- text "divide by zero exception"- VarNotFound v ->- text "variable"- <+> squotes (text $ show v)- <+> text "is not defined"- AssertionFailed e ->- text "assertion failed:" <+> e- StdError e -> e- RuntimeError e -> e- ParamNotBound s ->- text "parameter not bound:"- <+> text (show s)- BadParam s ->- text "function has no parameter"- <+> squotes s- ManifestError e ->- text "manifest error:"- <+> e- TooManyArgs n ->- text "too many args, function has"- <+> int n- <+> "parameter(s)"+prettyStackFrame :: StackFrame a -> Doc ann+prettyStackFrame StackFrame {name, span = span'} =+ prettySrcSpan span' <+> f (name2String name)+ where+ f "top-level" = mempty+ f x = pretty "function" <+> angles (pretty x) -instance Pretty (StackFrame a) where- pretty StackFrame {..} =- pretty span <+> pretty (f $ name2String name)- where- f "top-level" = mempty- f x = text "function" <+> (angles $ pretty x)+prettyBacktrace :: Backtrace a -> Doc ann+prettyBacktrace (Backtrace xs) = vcat $ prettyStackFrame <$> xs -instance Pretty (Backtrace a) where- pretty (Backtrace xs) = vcat $ pretty <$> xs+prettyError :: Error -> Doc ann+prettyError = \case+ EvalError e bt ->+ pretty "Runtime error:"+ <+> prettyEvalError e+ <$$> indent 2 (prettyBacktrace bt)+ ParserError e -> prettyParserError e+ CheckError e sp ->+ pretty "Static error:"+ <+> prettyCheckError e+ <$$> indent 2 (maybe mempty prettySrcSpan sp) -instance Pretty Error where- pretty =- \case- EvalError e bt ->- text "Runtime error:"- <+> pretty e- <$$> indent 2 (pretty bt)- ParserError e -> pretty e- CheckError e sp ->- text "Static error:"- <+> pretty e- <$$> indent 2 (pretty sp)+prettyVisibility :: Visibility -> Doc ann+prettyVisibility = \case+ Visible -> pretty ":"+ Hidden -> pretty "::"+ Forced -> pretty ":::"++commaSep :: [Doc ann] -> Doc ann+commaSep = concatWith (surround comma)++bracketSep :: Doc ann -> [Doc ann] -> Doc ann+bracketSep = encloseSep lbracket rbracket++parensSep :: Doc ann -> [Doc ann] -> Doc ann+parensSep = encloseSep lparen rparen++braceSep :: Doc ann -> [Doc ann] -> Doc ann+braceSep = encloseSep lbrace rbrace++ppField :: EField (Doc ann) -> Doc ann+ppField EField {..} =+ surround (ppOverride <> prettyVisibility visibility) key' value+ where+ ppOverride = pretty (bool "" "+" override)+ key' = bool key (brackets key) computed++ppObject :: [(Ident, Doc ann)] -> [Doc ann] -> Doc ann+ppObject l o =+ encloseSep+ lbrace+ rbrace+ comma+ (mcons (ppLocal' l) o)+ where+ mcons ma as = maybe as (: as) ma+ ppLocal' :: [(Ident, Doc ann)] -> Maybe (Doc ann)+ ppLocal' [] = Nothing+ ppLocal' xs =+ Just $ commaSep ((plocal <+>) . uncurry (surround equals) <$> xs')+ where+ xs' = first pretty <$> xs++ppLocal :: [(Ident, Doc ann)] -> Doc ann -> Doc ann+ppLocal xs e =+ plocal+ <+> commaSep (uncurry (surround equals) <$> xs')+ <> semi+ <+> e+ where+ xs' = first pretty <$> xs++prettyUnyOp :: UnyOp -> Doc ann+prettyUnyOp = \case+ Compl -> pretty "~"+ LNot -> pretty "!"+ Plus -> pretty "+"+ Minus -> pretty "-"+ Err -> pretty "<prim:err>"++prettyBinOp :: BinOp -> Doc ann+prettyBinOp = \case+ Add -> pretty "+"+ Sub -> pretty "-"+ Mul -> pretty "*"+ Div -> pretty "/"+ Mod -> pretty "%"+ Lt -> pretty "<"+ Le -> pretty "<="+ Gt -> pretty ">"+ Ge -> pretty ">="+ Eq -> pretty "=="+ Ne -> pretty "!="+ And -> pretty "&"+ Or -> pretty "|"+ Xor -> pretty "^"+ ShiftL -> pretty "<<"+ ShiftR -> pretty ">>"+ LAnd -> pretty "&&"+ LOr -> pretty "||"+ In -> pretty "in"+ Lookup -> pretty "<prim:lookup>"++ppFun :: [Param (Doc ann)] -> Doc ann -> Doc ann+ppFun ps e = pfunction <> parens (commaSep ps') <+> e+ where+ f = maybe mempty (equals <>)+ ps' = uncurry (<>) . bimap pretty f <$> ps++ppMaybeDoc :: Maybe (Doc ann) -> Doc ann+ppMaybeDoc = fromMaybe mempty++prettyExpr' :: Ann.Expr' -> Doc ann+prettyExpr' = prettyFixExprF' . forget'++prettyImport :: Import -> Doc ann+prettyImport = \case+ Import fp -> pimport <+> squotes (pretty fp)+ Importstr fp -> pimportstr <+> squotes (pretty fp)+ Importbin fp -> pimportbin <+> squotes (pretty fp)++prettyFixExprF' :: Fix ExprF' -> Doc ann+prettyFixExprF' = foldFix $ \case+ InR (Const import') -> prettyImport import'+ InL e -> ppExpr e++prettyFixExprF :: Fix ExprF -> Doc ann+prettyFixExprF = foldFix ppExpr++prettyExpr :: Ann.Expr -> Doc ann+prettyExpr = prettyFixExprF . forget'++ppArgs :: Args (Doc ann) -> Doc ann+ppArgs (Args args _) = parensSep comma (ppArg <$> args)++ppArg :: Arg (Doc ann) -> Doc ann+ppArg (Pos a) = a+ppArg (Named n a) = surround equals (pretty n) a++ppCompSpec :: NonEmpty (CompSpec (Doc ann)) -> Doc ann+ppCompSpec cs = hsep $ f <$> NE.toList cs+ where+ f (CompSpec v f' c) =+ pfor+ <+> pretty v+ <+> pin+ <+> parens f'+ <+> ppMaybeDoc ((pif <+>) <$> c)++ppExpr :: ExprF (Doc ann) -> Doc ann+ppExpr = \case+ ENull -> pnull+ EBool True -> ptrue+ EBool False -> pfalse+ EStr s -> squotes (pretty s)+ ENum n -> ppNumber n+ EFun ps e -> ppFun ps e+ EApply a args -> parens a <> ppArgs args+ EIdent i -> pretty i+ EIf c t -> pif <+> c <+> pthen <+> parens t+ EIfElse c t e -> pif <+> c <+> pthen <+> parens t <+> pelse <+> parens e+ EArr a -> bracketSep comma a+ EObj ls o -> ppObject ls (ppField <$> o)+ ELocal xs e -> ppLocal (NE.toList xs) (parens e)+ EErr a -> perror <+> parens a+ EAssert (Assert c m e) -> passert <+> c <> ppMaybeDoc ((colon <>) <$> m) <> semi <+> e+ EIndex a b -> parens a <> brackets b+ ELookup a i -> enclose a (pretty i) dot+ EUnyOp o a -> parens (prettyUnyOp o <> a)+ EBinOp o a b -> parens (parens a <+> prettyBinOp o <+> parens b)+ ESlice a b e s -> parens a <> bracketSep colon (ppMaybeDoc <$> [b, e, s])+ EArrComp e cs -> lbracket <> e <+> ppCompSpec cs <> rbracket+ EObjComp f ls cs -> ppObject ls [ppField f <+> ppCompSpec cs]
src/Language/Jsonnet/Std/Lib.hs view
@@ -1,10 +1,5 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE LiberalTypeSynonyms #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Language.Jsonnet.Std.Lib@@ -19,16 +14,11 @@ ) where -import Control.Lens (view)-import Control.Monad.Except-import Control.Monad.State-import Data.Aeson (FromJSON (..)) import qualified Data.Aeson as JSON import qualified Data.ByteString as B-import Data.Foldable (foldrM)-import Data.HashMap.Lazy (HashMap) import qualified Data.HashMap.Lazy as H-import qualified Data.List as L (intercalate, sort)+import qualified Data.List as L (intercalate)+import qualified Data.Map.Strict as M import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as T@@ -36,22 +26,17 @@ import qualified Data.Vector as V import Data.Word import Language.Jsonnet.Common-import Language.Jsonnet.Core import Language.Jsonnet.Error import Language.Jsonnet.Eval import Language.Jsonnet.Eval.Monad-import Language.Jsonnet.Parser.SrcSpan import Language.Jsonnet.Value-import System.FilePath.Posix (takeFileName)-import Text.Megaparsec.Pos (SourcePos (..))-import Text.PrettyPrint.ANSI.Leijen (text) import Unbound.Generics.LocallyNameless import Prelude hiding (length) import qualified Prelude as P (length) -- | Jsonnet standard library built-in methods-std :: Value-std = VObj $ H.fromList $ map f xs+std :: ExtVars -> Value+std extVars = VObj $ H.fromList $ map f xs where f = \(k, v) -> (k, VField (VStr k) v v Hidden) xs =@@ -79,21 +64,26 @@ ("encodeUTF8", inj (B.unpack . T.encodeUtf8 :: Text -> [Word8])), ("decodeUTF8", inj (T.decodeUtf8 . B.pack :: [Word8] -> Text)), ("makeArray", inj (makeArray @Eval)),- ("filter", inj (filterM @Eval @Value)),+ ("filter", inj (V.filterM @Eval @Value)), ("join", inj intercalate), ("objectHasEx", inj objectHasEx), ("objectFieldsEx", inj objectFieldsEx),- ("parseJson", inj (JSON.decodeStrict @Value))+ ("parseJson", inj (JSON.decodeStrict @Value)),+ ("extVar", inj (lookupExtVar extVars)) ] +lookupExtVar :: ExtVars -> Text -> Eval Value+lookupExtVar (ExtVars extVars) s = liftMaybe (ExtVarNotFound s) (M.lookup s extVars)+ intercalate :: Value -> [Value] -> Eval Value-intercalate sep arr = go sep (filter null arr)+intercalate sep arr = go sep (filter notNull arr) where- null VNull = False- null _ = True- go sep@(VArr _) = app (L.intercalate @Value) sep- go sep@(VStr _) = app T.intercalate sep- app f sep arr = inj <$> (f <$> proj sep <*> traverse proj arr)+ notNull VNull = False+ notNull _ = True+ go sep'@(VArr _) = app (L.intercalate @Value) sep'+ go sep'@(VStr _) = app T.intercalate sep'+ go _ = const $ throwE ( StdError "join's separator must be a string or an array" )+ app f sep' arr' = inj <$> (f <$> proj sep' <*> traverse proj arr') length :: Value -> Eval Int length = \case@@ -103,31 +93,10 @@ VClos f _ -> do (ps, _) <- unbind f pure $ P.length (unrec ps)- v ->+ _ -> throwE- ( StdError $- text $- T.unpack $- "length operates on strings, objects, functions and arrays, got "- -- <> showTy v+ ( StdError "length operates on strings, objects, functions and arrays, got " ) makeArray :: Monad m => Int -> (Int -> m Value) -> m (Vector Value) makeArray n f = traverse f (V.fromList [0 .. n - 1])--instance FromJSON Value where- parseJSON = \case- JSON.Null -> pure VNull- JSON.Bool b -> pure $ VBool b- JSON.Number n -> pure $ VNum n- JSON.String s -> pure $ VStr s- JSON.Array a -> VArr <$> traverse parseJSON a- JSON.Object o -> VObj . f <$> traverse parseJSON o- where- f :: HashMap Text Value -> Object- f o =- H.fromList- [ mkField k v- | (k, v) <- H.toList o- ]- mkField k v = (k, VField (VStr k) v v Visible)
src/Language/Jsonnet/Std/TH.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}- -- | -- Module : Language.Jsonnet.Std.TH -- Copyright : (c) 2020-2021 Alexandre Moreno
src/Language/Jsonnet/Syntax.hs view
@@ -1,10 +1,4 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedLabels #-}-{-# LANGUAGE TemplateHaskell #-}-+{-# OPTIONS_GHC -Wno-partial-fields #-} -- | -- Module : Language.Jsonnet.Syntax -- Copyright : (c) 2020-2021 Alexandre Moreno@@ -16,14 +10,16 @@ import Control.Applicative (Const (..)) import Data.Data (Data)+import Data.Functor.Classes+import Data.Functor.Classes.Generic import Data.Functor.Sum import Data.List.NonEmpty import Data.Scientific (Scientific)+import Data.Text (Text) import qualified Data.Text as T import Data.Typeable (Typeable) import GHC.Generics import Language.Jsonnet.Common-import Text.Show.Deriving import Unbound.Generics.LocallyNameless type Ident = String@@ -33,27 +29,32 @@ data EField a = EField { key :: a, value :: a,+ computed :: Bool, visibility :: Visibility, override :: Bool }- deriving+ deriving stock ( Eq, Read, Show, Typeable, Data, Generic,+ Generic1, Functor, Foldable, Traversable )--instance Alpha a => Alpha (EField a)--deriveShow1 ''EField+ deriving anyclass (Alpha)+ deriving+ (Eq1, Show1)+ via FunctorClassesDefault EField data ExprF a- = ELit Literal+ = ENull+ | EBool Bool+ | ENum Scientific+ | EStr Text | EIdent Ident | EFun [Param a] a | EApply a (Args a)@@ -72,7 +73,7 @@ } | EArr [a] | EErr a- | ELookup a a+ | ELookup a Ident | EIndex a a | EAssert (Assert a) | EIf a a@@ -103,40 +104,55 @@ -- | comp :: NonEmpty (CompSpec a) }- deriving- ( Show,+ deriving stock+ ( Eq,+ Show, Functor, Foldable, Traversable, Generic,+ Generic1, Typeable, Data )--deriveShow1 ''ExprF+ deriving+ (Eq1, Show1)+ via FunctorClassesDefault ExprF -newtype Import = Import FilePath- deriving (Show, Eq)+data Import+ = Import FilePath+ | Importstr FilePath+ | Importbin FilePath+ deriving stock (Show, Eq) type ExprF' = Sum ExprF (Const Import) -mkImportF :: String -> ExprF' a+mkImportF :: FilePath -> ExprF' a mkImportF = InR . Const . Import +mkImportstrF :: FilePath -> ExprF' a+mkImportstrF = InR . Const . Importstr++mkImportbinF :: FilePath -> ExprF' a+mkImportbinF = InR . Const . Importbin+ mkNullF :: ExprF' a-mkNullF = (InL . ELit) Null+mkNullF = InL ENull mkIntF :: Integral b => b -> ExprF' a-mkIntF = InL . ELit . Number . fromIntegral+mkIntF = InL . ENum . fromIntegral mkFloatF :: Scientific -> ExprF' a-mkFloatF = InL . ELit . Number+mkFloatF = InL . ENum +mkTextF :: Text -> ExprF' a+mkTextF = InL . EStr+ mkStrF :: String -> ExprF' a-mkStrF = InL . ELit . String . T.pack+mkStrF s = mkTextF (T.pack s) mkBoolF :: Bool -> ExprF' a-mkBoolF = InL . ELit . Bool+mkBoolF = InL . EBool mkIdentF :: Ident -> ExprF' a mkIdentF = InL . EIdent@@ -156,7 +172,7 @@ mkLocalF :: NonEmpty (Ident, a) -> a -> ExprF' a mkLocalF n = InL . ELocal n -mkLookupF :: a -> a -> ExprF' a+mkLookupF :: a -> Ident -> ExprF' a mkLookupF e = InL . ELookup e mkIndexF :: a -> a -> ExprF' a
src/Language/Jsonnet/Syntax/Annotated.hs view
@@ -9,8 +9,6 @@ import Data.Fix import Data.Functor.Sum-import Data.List.NonEmpty-import Data.Semigroup.Foldable import Language.Jsonnet.Annotate import Language.Jsonnet.Common import Language.Jsonnet.Parser.SrcSpan@@ -29,9 +27,9 @@ --mkApply a@(Fix (AnnF _ ann)) b = -- Fix $ AnnF (InL $ EApply a b) (fold1 (ann <| fmap attrib (fromList b))) -mkLookup :: Expr' -> Expr' -> Expr'-mkLookup a@(Fix (AnnF _ ann1)) b@(Fix (AnnF _ ann2)) =- Fix $ AnnF (InL $ ELookup a b) (ann1 <> ann2)+mkLookup :: Expr' -> (Ident, SrcSpan) -> Expr'+mkLookup a@(Fix (AnnF _ ann1)) (ident, ann2) =+ Fix $ AnnF (InL $ ELookup a ident) (ann1 <> ann2) mkIndex :: Expr' -> Expr' -> Expr' mkIndex a@(Fix (AnnF _ ann1)) b@(Fix (AnnF _ ann2)) =
src/Language/Jsonnet/TH.hs view
@@ -1,12 +1,4 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DeriveLift #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE TemplateHaskellQuotes #-} -- | -- Module : Language.Jsonnet.TH@@ -17,80 +9,21 @@ -- Portability : non-portable module Language.Jsonnet.TH where -import Control.Monad.Except hiding (lift)+import Control.Monad ((>=>))+import Control.Monad.Except+import Data.Binary (encode) import Data.Data-import Data.Functor.Product-import Data.Scientific (Scientific) import Data.Text (Text) import qualified Data.Text as T+import Instances.TH.Lift () import Language.Haskell.TH import Language.Haskell.TH.Syntax-import Language.Jsonnet.Common+import Language.Jsonnet.Annotate (forget) import Language.Jsonnet.Desugar-import Language.Jsonnet.Core import qualified Language.Jsonnet.Parser as Parser-import Language.Jsonnet.Parser.SrcSpan-import Language.Jsonnet.Pretty ()-import Language.Jsonnet.Syntax+import Language.Jsonnet.Pretty (prettyError) import Language.Jsonnet.Syntax.Annotated-import Text.PrettyPrint.ANSI.Leijen (pretty)-import Data.Binary (Binary, encode)-import Language.Jsonnet.Annotate (annMap)-import Instances.TH.Lift () -instance Data a => Lift (Arg a) where- lift = liftData--instance Lift SrcSpan where- lift = liftData--instance Lift Visibility where- lift = liftData--instance Data a => Lift (Args a) where- lift = liftData--instance Lift Strictness where- lift = liftData--instance Lift Literal where- lift = liftData--instance Lift Prim where- lift = liftData--instance Lift BinOp where- lift = liftData--instance Lift UnyOp where- lift = liftData--instance Data a => Lift (EField a) where- lift = liftData--instance Data a => Lift (Assert a) where- lift = liftData--instance Data a => Lift (CompSpec a) where- lift = liftData--instance Data a => Lift (ExprF a) where- lift = liftData--instance- ( Typeable a,- Typeable f,- Typeable g,- Data (f a),- Data (g a)- ) =>- Lift (Product f g a)- where- lift = liftData--instance Lift Expr where- lift = liftData- liftText :: Text -> Q Exp liftText txt = AppE (VarE 'T.pack) <$> lift (T.unpack txt) @@ -98,26 +31,11 @@ liftDataWithText :: Data a => a -> Q Exp liftDataWithText = dataToExpQ (fmap liftText . cast) -parse :: FilePath -> Text -> Q Exp-parse path str = do- res <-- runIO $- parse' str >>= \case- Left err -> fail (show $ pretty err)- Right res -> pure res- liftDataWithText res- where- parse' =- runExceptT- . ( Parser.parse path- >=> Parser.resolveImports path- )- parse0 :: FilePath -> Text -> Q Expr parse0 path str = do- parse' str >>= \case- Left err -> fail (show $ pretty err)- Right res -> pure res+ parse' str >>= \case+ Left err -> fail (show $ prettyError err)+ Right res -> pure res where parse' = runExceptT@@ -125,10 +43,18 @@ >=> Parser.resolveImports path ) --- | compiles a Jsonnet program down to a Core expression stripped of--- annotations+parse :: FilePath -> Text -> Q Exp+parse path str =+ parse0 path str+ >>= liftDataWithText++-- | compiles a Jsonnet program down to a binary-encoded Core expression+-- with source spans stripped out. This is currently useful to preload+-- the std library and have a faster startup compile :: FilePath -> Text -> Q Exp-compile path str = do- ast <- parse0 path str- let core = desugar (annMap (const ()) ast)- lift $ encode core+compile path str =+ parse0 path str+ >>= lift+ . encode+ . desugar+ . forget
src/Language/Jsonnet/Value.hs view
@@ -1,12 +1,3 @@-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE RecordWildCards #-}- -- | -- Module : Language.Jsonnet.Value -- Copyright : (c) 2020-2021 Alexandre Moreno@@ -17,25 +8,30 @@ module Language.Jsonnet.Value where import Control.Lens (view)-import Control.Monad.Except-import Control.Monad.Reader+import Control.Monad.IO.Class (MonadIO, liftIO) import Data.HashMap.Lazy (HashMap) import Data.IORef-import Data.Map.Lazy (Map)+import Data.Map.Strict (Map) import Data.Scientific import Data.Text (Text) import Data.Vector (Vector) import GHC.Generics import Language.Jsonnet.Common-import Language.Jsonnet.Core+import Language.Jsonnet.Core hiding (mkField) import Language.Jsonnet.Eval.Monad import Language.Jsonnet.Pretty ()-import Unbound.Generics.LocallyNameless+import Data.Aeson (FromJSON (..))+import qualified Data.Aeson as JSON+import qualified Data.Aeson.KeyMap as KeyMap+import qualified Data.HashMap.Lazy as H type Eval = EvalM Value type Env = Ctx Value +newtype ExtVars = ExtVars (Map Text Value)+ deriving newtype (Semigroup, Monoid)+ data Value = VNull | VBool !Bool@@ -48,6 +44,23 @@ | VPrim !Prim | VClos !Lam !Env | VFun !Fun++instance FromJSON Value where+ parseJSON = \case+ JSON.Null -> pure VNull+ JSON.Bool b -> pure $ VBool b+ JSON.Number n -> pure $ VNum n+ JSON.String s -> pure $ VStr s+ JSON.Array a -> VArr <$> traverse parseJSON a+ JSON.Object o -> VObj . f <$> traverse parseJSON (KeyMap.toHashMapText o)+ where+ f :: HashMap Text Value -> Object+ f o =+ H.fromList+ [ mkField k v+ | (k, v) <- H.toList o+ ]+ mkField k v = (k, VField (VStr k) v v Visible) data VField = VField { -- |
stdlib/std.jsonnet view
@@ -56,14 +56,14 @@ lstripChars(str, chars):: if std.length(str) > 0 && std.member(chars, str[0]) then- std.lstripChars(str[1:], chars)+ std.lstripChars(str[1:], chars) tailstrict else str, rstripChars(str, chars):: local len = std.length(str); if len > 0 && std.member(chars, str[len - 1]) then- std.rstripChars(str[:len - 1], chars)+ std.rstripChars(str[:len - 1], chars) tailstrict else str, @@ -110,27 +110,39 @@ parse_nat(str, 16), split(str, c)::- assert std.isString(str) : 'std.split first parameter should be a string, got ' + std.type(str);- assert std.isString(c) : 'std.split second parameter should be a string, got ' + std.type(c);- assert std.length(c) == 1 : 'std.split second parameter should have length 1, got ' + std.length(c);+ assert std.isString(str) : 'std.split first parameter must be a String, got ' + std.type(str);+ assert std.isString(c) : 'std.split second parameter must be a String, got ' + std.type(c);+ assert std.length(c) >= 1 : 'std.split second parameter must have length 1 or greater, got ' + std.length(c); std.splitLimit(str, c, -1), splitLimit(str, c, maxsplits)::- assert std.isString(str) : 'std.splitLimit first parameter should be a string, got ' + std.type(str);- assert std.isString(c) : 'std.splitLimit second parameter should be a string, got ' + std.type(c);- assert std.length(c) == 1 : 'std.splitLimit second parameter should have length 1, got ' + std.length(c);- assert std.isNumber(maxsplits) : 'std.splitLimit third parameter should be a number, got ' + std.type(maxsplits);- local aux(str, delim, i, arr, v) =- local c = str[i];- local i2 = i + 1;- if i >= std.length(str) then- arr + [v]- else if c == delim && (maxsplits == -1 || std.length(arr) < maxsplits) then- aux(str, delim, i2, arr + [v], '') tailstrict+ assert std.isString(str) : 'str.splitLimit first parameter must be a String, got ' + std.type(str);+ assert std.isString(c) : 'str.splitLimit second parameter must be a String, got ' + std.type(c);+ assert std.length(c) >= 1 : 'std.splitLimit second parameter must have length 1 or greater, got ' + std.length(c);+ assert std.isNumber(maxsplits) : 'str.splitLimit third parameter must be a Number, got ' + std.type(maxsplits);+ local strLen = std.length(str);+ local cLen = std.length(c);+ local aux(idx, ret, val) =+ if idx >= strLen then+ ret + [val]+ else if str[idx : idx + cLen : 1] == c &&+ (maxsplits == -1 || std.length(ret) < maxsplits) then+ aux(idx + cLen, ret + [val], '') else- aux(str, delim, i2, arr, v + c) tailstrict;- aux(str, c, 0, [], ''),+ aux(idx + 1, ret, val + str[idx]);+ aux(0, [], ''), + splitLimitR(str, c, maxsplits)::+ assert std.isString(str) : 'str.splitLimitR first parameter must be a String, got ' + std.type(str);+ assert std.isString(c) : 'str.splitLimitR second parameter must be a String, got ' + std.type(c);+ assert std.length(c) >= 1 : 'std.splitLimitR second parameter must have length 1 or greater, got ' + std.length(c);+ assert std.isNumber(maxsplits) : 'str.splitLimitR third parameter must be a Number, got ' + std.type(maxsplits);+ if maxsplits == -1 then+ std.splitLimit(str, c, -1)+ else+ local revStr(str) = std.join('', std.reverse(str));+ std.map(function(e) revStr(e), std.reverse(std.splitLimit(revStr(str), revStr(c), maxsplits))),+ strReplace(str, from, to):: assert std.isString(str); assert std.isString(from);@@ -491,7 +503,7 @@ // min_digits must be a whole number >= 0. It's the number of zeroes to pad with. // blank must be a boolean, if true adds an additional ' ' in front of a positive number, so // that it is aligned with negative numbers with the same number of digits.- // plus must be a boolean, if true adds a '+' in front of a postive number, so that it is+ // plus must be a boolean, if true adds a '+' in front of a positive number, so that it is // aligned with negative numbers with the same number of digits. This takes precedence over // blank, if both are true. // radix must be a whole number >1 and <= 10. It is the base of the system of numerals.@@ -881,7 +893,7 @@ escapeKeyToml(key) = local bare_allowed = std.set(std.stringChars("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-")); if std.setUnion(std.set(std.stringChars(key)), bare_allowed) == bare_allowed then key else escapeStringToml(key),- isTableArray(v) = std.isArray(v) && std.length(v) > 0 && std.foldl(function(a, b) a && std.isObject(b), v, true),+ isTableArray(v) = std.isArray(v) && std.length(v) > 0 && std.all(std.map(std.isObject, v)), isSection(v) = std.isObject(v) || isTableArray(v), renderValue(v, indexedPath, inline, cindent) = if v == true then@@ -1041,7 +1053,122 @@ std.join('', lines); aux(value, [], ''), - manifestYamlDoc(value, indent_array_in_object=false)::+ manifestYamlDoc(value, indent_array_in_object=false, quote_keys=true)::+ local onlyChars(charSet, strSet) =+ if std.length(std.setInter(charSet, strSet)) == std.length(strSet) then+ true+ else false;+ local isReserved(key) =+ // NOTE: These values are checked for case insensitively.+ // While this approach results in some false positives, it eliminates+ // the risk of missing a permutation.+ local reserved = [+ // Boolean types taken from https://yaml.org/type/bool.html+ 'true', 'false', 'yes', 'no', 'on', 'off', 'y', 'n',+ // Numerical words taken from https://yaml.org/type/float.html+ '.nan', '-.inf', '+.inf', '.inf', 'null', + // Invalid keys that contain no invalid characters+ '-', '---', '',+ ];+ local bad = [word for word in reserved if word == std.asciiLower(key)];+ if std.length(bad) > 0 then+ true+ else false;+ local typeMatch(m_key, type) =+ // Look for positive or negative numerical types (ex: 0x)+ if std.substr(m_key, 0, 2) == type || std.substr(m_key, 0, 3) == '-' + type then+ true+ else false;+ local bareSafe(key) =+ /*+ For a key to be considered safe to emit without quotes, the following must be true+ - All characters must match [a-zA-Z0-9_/\-]+ - Not match the integer format defined in https://yaml.org/type/int.html+ - Not match the float format defined in https://yaml.org/type/float.html+ - Not match the timestamp format defined in https://yaml.org/type/timestamp.html+ - Not match the boolean format defined in https://yaml.org/type/bool.html+ - Not match the null format defined in https://yaml.org/type/null.html+ - Not match (ignoring case) any reserved words which pass the above tests.+ Reserved words are defined in isReserved() above.++ Since the remaining YAML types require characters outside the set chosen as valid+ for the elimination of quotes from the YAML output, the remaining types listed at+ https://yaml.org/type/ are by default always quoted.+ */+ local letters = std.set(std.stringChars('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-/'));+ local digits = std.set(std.stringChars('0123456789'));+ local intChars = std.set(digits + std.stringChars('_-'));+ local binChars = std.set(intChars + std.stringChars('b'));+ local hexChars = std.set(digits + std.stringChars('abcdefx_-'));+ local floatChars = std.set(digits + std.stringChars('e._-'));+ local dateChars = std.set(digits + std.stringChars('-'));+ local safeChars = std.set(letters + floatChars);+ local keyLc = std.asciiLower(key);+ local keyChars = std.stringChars(key);+ local keySet = std.set(keyChars);+ local keySetLc = std.set(std.stringChars(keyLc));+ // Check for unsafe characters+ if ! onlyChars(safeChars, keySet) then+ false+ // Check for reserved words+ else if isReserved(key) then+ false+ /* Check for timestamp values. Since spaces and colons are already forbidden,+ all that could potentially pass is the standard date format (ex MM-DD-YYYY, YYYY-DD-MM, etc).+ This check is even more conservative: Keys that meet all of the following:+ - all characters match [0-9\-]+ - has exactly 2 dashes+ are considered dates.+ */+ else if onlyChars(dateChars, keySet) + && std.length(std.findSubstr('-', key)) == 2 then+ false+ /* Check for integers. Keys that meet all of the following:+ - all characters match [0-9_\-]+ - has at most 1 dash+ are considered integers.+ */+ else if onlyChars(intChars, keySetLc)+ && std.length(std.findSubstr('-', key)) < 2 then+ false+ /* Check for binary integers. Keys that meet all of the following:+ - all characters match [0-9b_\-]+ - has at least 3 characters+ - starts with (-)0b+ are considered binary integers.+ */+ else if onlyChars(binChars, keySetLc)+ && std.length(key) > 2+ && typeMatch(key, '0b') then+ false+ /* Check for floats. Keys that meet all of the following:+ - all characters match [0-9e._\-]+ - has at most a single period+ - has at most two dashes+ - has at most 1 'e'+ are considered floats.+ */+ else if onlyChars(floatChars, keySetLc)+ && std.length(std.findSubstr('.', key)) == 1+ && std.length(std.findSubstr('-', key)) < 3 + && std.length(std.findSubstr('e', keyLc)) < 2 then+ false+ /* Check for hexadecimals. Keys that meet all of the following:+ - all characters match [0-9a-fx_\-]+ - has at most 1 dash+ - has at least 3 characters+ - starts with (-)0x+ are considered hexadecimals.+ */+ else if onlyChars(hexChars, keySetLc) + && std.length(std.findSubstr('-', key)) < 2+ && std.length(keyChars) > 2+ && typeMatch(key, '0x') then+ false+ // All checks pass. Key is safe for emission without quotes.+ else true;+ local escapeKeyYaml(key) =+ if bareSafe(key) then key else std.escapeStringJson(key); local aux(v, path, cindent) = if v == true then 'true'@@ -1117,19 +1244,19 @@ space: ' ', }; local lines = [- std.escapeStringJson(k) + ':' + param.space + aux(v[k], path + [k], param.new_indent)+ (if quote_keys then std.escapeStringJson(k) else escapeKeyYaml(k)) + ':' + param.space + aux(v[k], path + [k], param.new_indent) for k in std.objectFields(v) for param in [params(v[k])] ]; std.join('\n' + cindent, lines); aux(value, [], ''), - manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::+ manifestYamlStream(value, indent_array_in_object=false, c_document_end=true, quote_keys=true):: if !std.isArray(value) then error 'manifestYamlStream only takes arrays, got ' + std.type(value) else '---\n' + std.join(- '\n---\n', [std.manifestYamlDoc(e, indent_array_in_object) for e in value]+ '\n---\n', [std.manifestYamlDoc(e, indent_array_in_object, quote_keys) for e in value] ) + if c_document_end then '\n...\n' else '\n', @@ -1183,7 +1310,7 @@ base64(input):: local bytes = if std.isString(input) then- std.map(function(c) std.codepoint(c), input)+ std.map(std.codepoint, input) else input; @@ -1220,7 +1347,7 @@ base64_table[(arr[i + 2] & 63)]; aux(arr, i + 3, r + str) tailstrict; - local sanity = std.foldl(function(r, a) r && (a < 256), bytes, true);+ local sanity = std.all([a < 256 for a in bytes]); if !sanity then error 'Can only base64 encode strings / arrays of single bytes.' else@@ -1250,7 +1377,7 @@ base64Decode(str):: local bytes = std.base64DecodeBytes(str);- std.join('', std.map(function(b) std.char(b), bytes)),+ std.join('', std.map(std.char, bytes)), reverse(arr):: local l = std.length(arr);@@ -1379,6 +1506,9 @@ else patch, + get(o, f, default = null, inc_hidden = true)::+ if std.objectHasEx(o, f, inc_hidden) then o[f] else default,+ objectFields(o):: std.objectFieldsEx(o, false), @@ -1476,19 +1606,49 @@ else std.filter(function(i) arr[i] == value, std.range(0, std.length(arr) - 1)), + all(arr)::+ assert std.isArray(arr) : 'all() parameter should be an array, got ' + std.type(arr);+ local arrLen = std.length(arr);+ local aux(idx) =+ if idx >= arrLen then+ true+ else+ local e = arr[idx];+ assert std.isBoolean(e) : std.format('element "%s" of type %s is not a boolean', e, std.type(e));+ if !e then+ false+ else+ aux(idx + 1) tailstrict;+ aux(0),++ any(arr)::+ assert std.isArray(arr) : 'any() parameter should be an array, got ' + std.type(arr);+ local arrLen = std.length(arr);+ local aux(idx) =+ if idx >= arrLen then+ false+ else+ local e = arr[idx];+ assert std.isBoolean(e) : std.format('element "%s" of type %s is not a boolean', e, std.type(e));+ if e then+ true+ else+ aux(idx + 1) tailstrict;+ aux(0),+ // Three way comparison. // TODO(sbarzowski): consider exposing and documenting it properly __compare(v1, v2)::- local t1 = std.type(v1), t2 = std.type(v2);- if t1 != t2 then- error "Comparison requires matching types. Got " + t1 + " and " + t2- else if t1 == "array" then- std.__compare_array(v1, v2)- else if t1 == "function" || t1 == "object" || t1 == "bool" then- error "Values of type " + t1 + " are not comparable."- else if v1 < v2 then -1- else if v1 > v2 then 1- else 0,+ local t1 = std.type(v1), t2 = std.type(v2);+ if t1 != t2 then+ error 'Comparison requires matching types. Got ' + t1 + ' and ' + t2+ else if t1 == 'array' then+ std.__compare_array(v1, v2)+ else if t1 == 'function' || t1 == 'object' || t1 == 'boolean' then+ error 'Values of type ' + t1 + ' are not comparable.'+ else if v1 < v2 then -1+ else if v1 > v2 then 1+ else 0, __compare_array(arr1, arr2):: local len1 = std.length(arr1), len2 = std.length(arr2);
+ test/Language/Jsonnet/Test/Golden.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++-- |+module Language.Jsonnet.Test.Golden where++import qualified Data.ByteString.Lazy as LBS+import qualified Data.Map.Strict as M+import qualified Data.Text.IO as T (readFile)+import Data.Text.Lazy+import Data.Text.Lazy.Encoding (encodeUtf8)+import Language.Jsonnet hiding (extVars)+import Language.Jsonnet.Pretty (ppJson, prettyError)+import Language.Jsonnet.Value+import Prettyprinter (Doc)+import System.FilePath (replaceExtension, takeBaseName)+import System.IO.Unsafe (unsafePerformIO)+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Golden (findByExtension, goldenVsString)++render :: (a -> Doc ann) -> a -> LBS.ByteString+render printer = encodeUtf8 . pack . show . printer++run :: Config -> IO LBS.ByteString+run conf = do+ prog <- T.readFile (fname conf)+ outp <- interpret conf prog+ pure (either (render prettyError) (render (ppJson 4)) outp)++-- Adapted from the C++ test suite+extVars :: ExtVars+extVars =+ ExtVars $+ M.fromList+ [ ("var1", VStr "test"),+ ("var2", unsafePerformIO $ interpretExtVar (ExtVar ExtCode (Inline "{x:1,y:2}")))+ ]++goldenTests :: IO TestTree+goldenTests = do+ jsonnetFiles <- findByExtension [".jsonnet"] "./test/golden"+ return $+ testGroup+ "Jsonnet golden tests"+ [ goldenVsString+ (takeBaseName jsonnetFile) -- test name+ goldenFile -- golden file path+ (run $ Config jsonnetFile extVars)+ | jsonnetFile <- jsonnetFiles,+ let goldenFile = replaceExtension jsonnetFile ".golden"+ ]
+ test/Language/Jsonnet/Test/Roundtrip.hs view
@@ -0,0 +1,201 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+module Language.Jsonnet.Test.Roundtrip where++import Data.Fix+import Data.Functor.Sum+import Data.Text (Text)+import Hedgehog+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range+import Language.Jsonnet.Annotate+import Language.Jsonnet.Common+import Language.Jsonnet.Error (Error)+import qualified Language.Jsonnet.Parser as Parser+import Language.Jsonnet.Pretty+import Language.Jsonnet.Syntax+import Prettyprinter+import Prettyprinter.Render.Text+import Test.Tasty+import Test.Tasty.Hedgehog++genString :: Gen String+genString =+ Gen.filterT+ (`notElem` Parser.reservedKeywords)+ (Gen.string (Range.constant 2 4) Gen.alpha)++genText :: Gen Text+genText = Gen.text (Range.constant 2 4) Gen.alpha++genField :: Gen a -> Gen (EField a)+genField a = EField <$> a <*> a <*> pure True <*> Gen.enumBounded <*> Gen.bool++genObject :: Gen (Fix ExprF')+genObject =+ Fix+ <$> ( mkObjectF+ <$> Gen.list (Range.linear 0 10) (genField genExpr)+ <*> Gen.list (Range.linear 0 10) ((,) <$> genString <*> genExpr)+ )++genArray :: Gen (Fix ExprF')+genArray = Fix <$> (mkArrayF <$> Gen.list (Range.linear 0 10) genExpr)++genIf :: Gen (Fix ExprF')+genIf = Gen.subterm2 genExpr genExpr (\a b -> Fix (mkIfF a b))++genIfElse :: Gen (Fix ExprF')+genIfElse = Gen.subterm3 genExpr genExpr genExpr (\a b c -> Fix (mkIfElseF a b c))++genSlice :: Gen (Fix ExprF')+genSlice =+ Fix+ <$> ( mkSliceF+ <$> genExpr+ <*> Gen.maybe genExpr+ <*> Gen.maybe genExpr+ <*> Gen.maybe genExpr+ )++genError :: Gen (Fix ExprF')+genError = Fix . mkErrorF <$> genExpr++genImport :: Gen (Fix ExprF')+genImport = Fix . mkImportF <$> genString++genIndex :: Gen (Fix ExprF')+genIndex = Gen.subterm2 genExpr genExpr (\a b -> Fix (mkIndexF a b))++genLookup :: Gen (Fix ExprF')+genLookup = Fix <$> (mkLookupF <$> genIdent <*> genString)++genAssert :: Gen (Fix ExprF')+genAssert =+ Fix+ <$> ( mkAssertF+ <$> genExpr+ <*> Gen.maybe genExpr+ <*> genExpr+ )++genLocal :: Gen (Fix ExprF')+genLocal =+ Fix+ <$> ( mkLocalF+ <$> Gen.nonEmpty (Range.linear 0 10) ((,) <$> genString <*> genExpr)+ <*> genExpr+ )++genLitStr :: Gen (Fix ExprF')+genLitStr = Fix <$> (mkTextF <$> genText)++genLiteral :: Gen (Fix ExprF')+genLiteral =+ Gen.choice+ [ pure (Fix mkNullF),+ Fix . mkBoolF <$> Gen.bool,+ Fix . mkIntF @Int <$> Gen.integral (Range.linear 0 10),+ genLitStr+ ]++genArg :: Gen a -> Gen (Arg a)+genArg a = Gen.choice [Pos <$> a, Named <$> genString <*> a]++genArgs :: Gen a -> Gen (Args a)+genArgs a =+ Args+ <$> Gen.list (Range.linear 0 10) (genArg a) <*> pure Lazy++genApply :: Gen (Fix ExprF')+genApply = Fix <$> (mkApplyF <$> genExpr <*> genArgs genExpr)++genFunc :: Gen (Fix ExprF')+genFunc =+ Fix+ <$> ( mkFunF+ <$> Gen.list (Range.linear 0 10) ((,) <$> genString <*> Gen.maybe genExpr)+ <*> genExpr+ )++genIdent :: Gen (Fix ExprF')+genIdent = Fix . mkIdentF <$> genString++genUnyOp :: Gen (Fix ExprF')+genUnyOp = Fix <$> (InL <$> (EUnyOp <$> Gen.enum Compl Minus <*> genExpr))++genBinOp :: Gen (Fix ExprF')+genBinOp = Fix <$> (InL <$> (EBinOp <$> Gen.enum Add In <*> genExpr <*> genExpr))++genCompSpec :: Gen a -> Gen (CompSpec a)+genCompSpec a = CompSpec <$> genString <*> a <*> Gen.maybe a++genArrComp :: Gen (Fix ExprF')+genArrComp =+ Fix+ <$> ( mkArrCompF+ <$> genExpr+ <*> Gen.nonEmpty (Range.linear 0 10) (genCompSpec genExpr)+ )++genObjComp :: Gen (Fix ExprF')+genObjComp =+ Fix+ <$> ( mkObjCompF+ <$> genField genExpr+ <*> Gen.list (Range.linear 0 10) ((,) <$> genString <*> genExpr)+ <*> Gen.nonEmpty (Range.linear 0 10) (genCompSpec genExpr)+ )++genExpr :: Gen (Fix ExprF')+genExpr =+ Gen.recursive+ Gen.choice+ [ -- non-recursive generators+ genLiteral,+ genIdent,+ genImport+ ]+ [ -- recursive generators+ genIf,+ genIfElse,+ genObject,+ genArray,+ genSlice,+ genLocal,+ genError,+ genAssert,+ genLookup,+ genIndex,+ genApply,+ genFunc,+ genBinOp,+ genUnyOp,+ genObjComp,+ genArrComp+ ]++printExpr :: Fix ExprF' -> Text+printExpr =+ renderStrict+ . layoutPretty defaultLayoutOptions+ . prettyFixExprF'++parseExpr :: Text -> Either Error (Fix ExprF')+parseExpr = fmap forget' . Parser.parse ""++prop_roundtrip :: Property+prop_roundtrip =+ property $ do+ x <- forAll genExpr+ tripping x printExpr parseExpr++testRoundtripGroup :: TestTree+testRoundtripGroup =+ testGroup+ "Property tests"+ [ testProperty+ "roundtrip"+ prop_roundtrip+ ]
+ test/Main.hs view
@@ -0,0 +1,16 @@+-- |+module Main where++import Language.Jsonnet.Test.Golden+import Language.Jsonnet.Test.Roundtrip+import Test.Tasty++main :: IO ()+main = do+ goldenTests' <- goldenTests+ defaultMain $+ testGroup+ "tests"+ [ testRoundtripGroup,+ goldenTests'+ ]
− test/golden/Test.hs
@@ -1,56 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-}---- |-module Main where--import Control.Monad-import Control.Monad.Except-import qualified Data.ByteString.Lazy as LBS-import Data.IORef-import qualified Data.Text as T-import qualified Data.Text.IO as T (readFile)-import Data.Text.Lazy-import Data.Text.Lazy.Encoding (encodeUtf8)-import Language.Jsonnet-import Language.Jsonnet.Annotate-import Language.Jsonnet.Desugar-import Language.Jsonnet.Error-import Language.Jsonnet.Eval-import Language.Jsonnet.Eval.Monad-import Language.Jsonnet.Pretty ()-import qualified Language.Jsonnet.Std.Lib as Lib-import Language.Jsonnet.Std.TH (mkStdlib)-import Language.Jsonnet.Value-import System.FilePath (replaceExtension, takeBaseName)-import Test.Tasty (TestTree, defaultMain, testGroup)-import Test.Tasty.Golden (findByExtension, goldenVsString)-import Test.Tasty.HUnit (assertEqual, assertFailure, testCase)-import Text.PrettyPrint.ANSI.Leijen (Pretty, pretty)--main :: IO ()-main = goldenTests >>= defaultMain--render :: Pretty a => a -> LBS.ByteString-render = encodeUtf8 . pack . show . pretty--run :: Config -> IO LBS.ByteString-run conf = do- prog <- T.readFile (fname conf)- outp <- interpret conf prog- pure (either render render outp)--goldenTests :: IO TestTree-goldenTests = do- jsonnetFiles <- findByExtension [".jsonnet"] "./test/golden"- return $- testGroup- "Jsonnet golden tests"- [ goldenVsString- (takeBaseName jsonnetFile) -- test name- goldenFile -- golden file path- (run $ Config jsonnetFile)- | jsonnetFile <- jsonnetFiles,- let goldenFile = replaceExtension jsonnetFile ".golden"- ]
+ test/golden/arith-float.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/arith-float.jsonnet view
@@ -0,0 +1,136 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++//Test lexing:+std.assertEqual(0, 0.0) &&+std.assertEqual(0e0, 0.0) &&+std.assertEqual(0e+0, 0.0) &&+std.assertEqual(0e-0, 0.0) &&+std.assertEqual(0e00, 0.0) &&+std.assertEqual(0E0, 0.0) &&+std.assertEqual(0E+0, 0.0) &&+std.assertEqual(0E-0, 0.0) &&+std.assertEqual(0E00, 0.0) &&+std.assertEqual(0.0e0, 0.0) &&+std.assertEqual(0.0e+0, 0.0) &&+std.assertEqual(0.0e-0, 0.0) &&+std.assertEqual(0.0e00, 0.0) &&+std.assertEqual(0.0E0, 0.0) &&+std.assertEqual(0.0E+0, 0.0) &&+std.assertEqual(0.0E-0, 0.0) &&+std.assertEqual(0.0E00, 0.0) &&+std.assertEqual(0.00e0, 0.0) &&+std.assertEqual(0.00e+0, 0.0) &&+std.assertEqual(0.00e-0, 0.0) &&+std.assertEqual(0.00e00, 0.0) &&+std.assertEqual(0.00E0, 0.0) &&+std.assertEqual(0.00E+0, 0.0) &&+std.assertEqual(0.00E-0, 0.0) &&+std.assertEqual(0.00E00, 0.0) &&++std.assertEqual(1, 1.0) &&+std.assertEqual(1e0, 1.0) &&+std.assertEqual(1e+0, 1.0) &&+std.assertEqual(1e-0, 1.0) &&+std.assertEqual(1e00, 1.0) &&+std.assertEqual(1E0, 1.0) &&+std.assertEqual(1E+0, 1.0) &&+std.assertEqual(1E-0, 1.0) &&+std.assertEqual(1E00, 1.0) &&+std.assertEqual(1.0e0, 1.0) &&+std.assertEqual(1.0e+0, 1.0) &&+std.assertEqual(1.0e-0, 1.0) &&+std.assertEqual(1.0e00, 1.0) &&+std.assertEqual(1.0E0, 1.0) &&+std.assertEqual(1.0E+0, 1.0) &&+std.assertEqual(1.0E-0, 1.0) &&+std.assertEqual(1.0E00, 1.0) &&+std.assertEqual(1.00e0, 1.0) &&+std.assertEqual(1.00e+0, 1.0) &&+std.assertEqual(1.00e-0, 1.0) &&+std.assertEqual(1.00e00, 1.0) &&+std.assertEqual(1.00E0, 1.0) &&+std.assertEqual(1.00E+0, 1.0) &&+std.assertEqual(1.00E-0, 1.0) &&+std.assertEqual(1.00E00, 1.0) &&++std.assertEqual(11, 11.0) &&+std.assertEqual(11e0, 11.0) &&+std.assertEqual(11e+0, 11.0) &&+std.assertEqual(11e-0, 11.0) &&+std.assertEqual(11e00, 11.0) &&+std.assertEqual(11E0, 11.0) &&+std.assertEqual(11E+0, 11.0) &&+std.assertEqual(11E-0, 11.0) &&+std.assertEqual(11E00, 11.0) &&+std.assertEqual(11.0e0, 11.0) &&+std.assertEqual(11.0e+0, 11.0) &&+std.assertEqual(11.0e-0, 11.0) &&+std.assertEqual(11.0e00, 11.0) &&+std.assertEqual(11.0E0, 11.0) &&+std.assertEqual(11.0E+0, 11.0) &&+std.assertEqual(11.0E-0, 11.0) &&+std.assertEqual(11.0E00, 11.0) &&+std.assertEqual(11.00e0, 11.0) &&+std.assertEqual(11.00e+0, 11.0) &&+std.assertEqual(11.00e-0, 11.0) &&+std.assertEqual(11.00e00, 11.0) &&+std.assertEqual(11.00E0, 11.0) &&+std.assertEqual(11.00E+0, 11.0) &&+std.assertEqual(11.00E-0, 11.0) &&+std.assertEqual(11.00E00, 11.0) &&++// Test arithmetic+//std.assertEqual(4.5 + 3, 7.5) &&+//std.assertEqual(4.5 - 2, 2.5) &&+//std.assertEqual(4.5 * 3, 13.5) &&+//std.assertEqual(4.5 / 3, 1.5) &&+//std.assertEqual(4.5 % 2, 0.5) &&+//+//std.assertEqual(4.5 << 2, 16) &&+//std.assertEqual(4.5 << 66, 16) &&+//std.assertEqual(4.5 >> 2, 1) &&+//std.assertEqual(4.5 >> 66, 1) &&+//std.assertEqual(4.5 ^ 3.6, 7) &&+//std.assertEqual(5.5 & 3.3, 1) &&+//std.assertEqual(4.5 | 1.9, 5) &&+//+//std.assertEqual(~4.5, -#+//5) &&+//+//std.assertEqual(~4.5, -5) &&+//std.assertEqual(-4.5, 0-4.5) &&+//+//std.assertEqual(4.5 >= 4.5, true) &&+//std.assertEqual(4.5 > 1, true) &&+//std.assertEqual(4.5 <= 4.5, true) &&+//std.assertEqual(4.5 < 5, true) &&+//std.assertEqual(4.5 > 4.5, false) &&+//std.assertEqual(4.5 <= 1, false) &&+//std.assertEqual(4.5 < 4.5, false) &&+//std.assertEqual(4.5 >= 5, false) &&+//+//std.assertEqual(4.5 == 4.5, true) &&+//std.assertEqual(4.5 != 3.5, true) &&++std.assertEqual(std.toString(1e20), "100000000000000000000") &&++// Check formatter handles - $ correctly.+local obj = {+ f: { f: { f: 1 } },+ g: - $.f.f.f+};++std.assertEqual(obj.g, -1) &&++true
+ test/golden/array.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/array.jsonnet view
@@ -0,0 +1,58 @@+/*+Copyright 2015 Google Inc. All rights reserved.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual([], []) &&+std.assertEqual([1], [1]) &&+std.assertEqual([1,], [1]) &&+std.assertEqual([1, 4, 9, 16][2], 9) &&+std.assertEqual([1, 4, 9, 16] == [1, 4, 9, 16], true) &&+std.assertEqual([1, 4, 9, 16] != [1, 4, 9, 15], true) &&+std.assertEqual([1, 4, 9, 16] == [1, 4, 9, 15], false) &&+std.assertEqual([1, 4, 9, 16] != [1, 4, 9, 16], false) &&++std.assertEqual([1, 4, 9, 16] == [1, 4, 9, 16, 17], false) &&+std.assertEqual([1, 4, 9, 16] != [1, 4, 9, 16, 17], true) &&+std.assertEqual([1, 4, 9, 16, 17] == [1, 4, 9, 16], false) &&+std.assertEqual([1, 4, 9, 16, 17] != [1, 4, 9, 16], true) &&++std.assertEqual([1, 4, 9, error 'foo'][2], 9) &&+std.assertEqual([] + [1, 2, 3] + [4, 5, 6] + [], [1, 2, 3, 4, 5, 6]) &&+std.assertEqual([] + [], []) &&++std.assertEqual([x * x for x in [1, 2, 3, 4]], [1, 4, 9, 16]) &&+std.assertEqual([x * x for x in [-3, -2, -1, 0, 1, 2, 3] if x >= 0], [0, 1, 4, 9]) &&++std.assertEqual([x * x for x in [-3, -2, -1, 0, 1, 2, 3] if x >= 0], [0, 1, 4, 9]) &&+std.assertEqual([x * x for x in []], []) &&++std.assertEqual([[x] for x in [1, 2, 3, 4]], [[1], [2], [3], [4]]) &&++std.assertEqual(local x = 10; [x for x in [x, x, x]], [10, 10, 10]) &&++local arr = [+ { x: 1, y: 4, z: true },+ { x: 1, y: 4, z: false },+ { x: 1, y: 6, z: true },+ { x: 1, y: 6, z: false },+ { x: 2, y: 6, z: true },+ { x: 2, y: 6, z: false },+ { x: 3, y: 6, z: true },+ { x: 3, y: 6, z: false },+];++std.assertEqual(arr, [{ x: x, y: y, z: z } for x in [1, 2, 3] for y in [1, 4, 6] if x + 2 < y for z in [true, false]]) &&++true
+ test/golden/comments.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/comments.jsonnet view
@@ -0,0 +1,43 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++// This kind of comment++# this kind of comment++/* ignore this */ true &&++// Test lexing:+local x = 1-/*foo*/1;++local x = 1-/*foo*/1;++local x = 1-#foo+1;++local x = 1*/*foo*/1;++local x = 1*#foo+1;++local x = 1-/*+foo*/1;++local x = 1-#+foo+1;++local x = 1*/*+foo*/1;++local x = 1*#+foo+1;++true
+ test/golden/condition.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/condition.jsonnet view
@@ -0,0 +1,41 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local f(x) =+ if x < 0 then+ 'negative'+ else if x == 0 then+ 'zero'+ else if x <= 5 then+ 'small'+ else if x <= 10 then+ 'large'+ else+ 'huge';++std.assertEqual(f(-10), 'negative') &&+std.assertEqual(f(0), 'zero') &&+std.assertEqual(f(3), 'small') &&+std.assertEqual(f(8), 'large') &&+std.assertEqual(f(100), 'huge') &&+std.assertEqual(if 1 > 0 then 1, 1) &&+std.assertEqual(if 1 > 2 then 1, null) &&++std.assertEqual(if true then if false then 'f' else 'y', 'y') &&+std.assertEqual(if true then (if false then 'f') else 'y', null) &&++std.assertEqual(if true then if true then 'f' + 'y', 'fy') &&+std.assertEqual(if false then (if true then 'f') + 'y', null) &&+std.assertEqual((if false then (if true then 'f')) + 'y', 'nully') &&++true
+ test/golden/empty.golden view
@@ -0,0 +1,1 @@+{}
+ test/golden/empty.jsonnet view
@@ -0,0 +1,1 @@+{ foo :: "bar" }
+ test/golden/error.01.golden view
@@ -0,0 +1,5 @@+Runtime error: foo+ ./test/golden/error.01.jsonnet:17:29-40 function <bananas>+ ./test/golden/error.01.jsonnet:18:29-36 function <oranges>+ ./test/golden/error.01.jsonnet:19:28-35 function <apples>+ ./test/golden/error.01.jsonnet:20:1-7
+ test/golden/error.01.jsonnet view
@@ -0,0 +1,20 @@+/*+Copyright 2015 Google Inc. All rights reserved.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local bananas = function(x) error 'foo';+local oranges = function(y) bananas(1);+local apples = function(z) oranges(0);+apples(2)
+ test/golden/error.array_index_string.golden view
@@ -0,0 +1,2 @@+Runtime error: invalid index: array index was not integer+ ./test/golden/error.array_index_string.jsonnet:14:1-15:1
+ test/golden/error.array_index_string.jsonnet view
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++[0, 1, 2].foo
+ test/golden/error.array_large_index.golden view
@@ -0,0 +1,2 @@+Runtime error: index out of bounds: 18446744073709552000+ ./test/golden/error.array_large_index.jsonnet:14:1-18
+ test/golden/error.array_large_index.jsonnet view
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++[0, 1, 2][std.pow(2, 64)]
+ test/golden/error.assert.fail1.golden view
@@ -0,0 +1,2 @@+Runtime error: x should be equal to y+ ./test/golden/error.assert.fail1.jsonnet:17:1-20:1
+ test/golden/error.assert.fail1.jsonnet view
@@ -0,0 +1,19 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local x = 'foo';+local y = 'bar';++assert x == y : "x should be equal to y";++true
+ test/golden/error.assert.fail2.golden view
@@ -0,0 +1,3 @@+Runtime error: a must be bigger than b+ ./test/golden/error.assert.fail2.jsonnet:2:3-3:8 function <subtract>+ ./test/golden/error.assert.fail2.jsonnet:5:1-10
+ test/golden/error.assert.fail2.jsonnet view
@@ -0,0 +1,5 @@+local subtract(a, b) =+ assert a > b : 'a must be bigger than b';+ a - b;++subtract (0, 1)
+ test/golden/error.divide-zero.golden view
@@ -0,0 +1,2 @@+Runtime error: divide by zero exception+ ./test/golden/error.divide-zero.jsonnet:14:1-15:1
+ test/golden/error.divide-zero.jsonnet view
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++100 / 0
+ test/golden/error.duplicate_local_var.golden view
@@ -0,0 +1,2 @@+Static error: duplicate local var 'a'+ ./test/golden/error.duplicate_local_var.jsonnet:1:1-3:1
+ test/golden/error.duplicate_local_var.jsonnet view
@@ -0,0 +1,2 @@+local a = 1, a = 2;+a
+ test/golden/error.field-not-exist.golden view
@@ -0,0 +1,2 @@+Runtime error: no such key: y+ ./test/golden/error.field-not-exist.jsonnet:14:1-15:1
+ test/golden/error.field-not-exist.jsonnet view
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++{ x: 3 }.y
+ test/golden/error.function_arg_positional_after_named.golden view
@@ -0,0 +1,2 @@+Static error: positional after named argument+ ./test/golden/error.function_arg_positional_after_named.jsonnet:16:1-4
+ test/golden/error.function_arg_positional_after_named.jsonnet view
@@ -0,0 +1,16 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local foo(a, b) = [a, b];++foo(b=3, 4)
+ test/golden/error.function_duplicate_param.golden view
@@ -0,0 +1,2 @@+Parse error: foo.bar (No such file or directory)+ ./test/golden/error.function_duplicate_param.jsonnet:14:13-29
+ test/golden/error.function_duplicate_param.jsonnet view
@@ -0,0 +1,16 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local foo = import 'foo.bar';++function(x, x) x
+ test/golden/error.function_no_such_param.golden view
@@ -0,0 +1,3 @@+Runtime error: function has no parameter 'b'+ ./test/golden/error.function_no_such_param.jsonnet:2:1-3 function <id>+ ./test/golden/error.function_no_such_param.jsonnet:2:1-3
+ test/golden/error.function_no_such_param.jsonnet view
@@ -0,0 +1,2 @@+local id(a) = a;+id("foo", b = 2)
+ test/golden/error.function_too_many_args.golden view
@@ -0,0 +1,3 @@+Runtime error: too many args, function has 2 parameter(s)+ ./test/golden/error.function_too_many_args.jsonnet:16:1-4 function <foo>+ ./test/golden/error.function_too_many_args.jsonnet:16:1-4
+ test/golden/error.function_too_many_args.jsonnet view
@@ -0,0 +1,16 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local foo(a, b) = [a, b];++foo(3, 4, 5)
+ test/golden/error.index-out-of-bounds.golden view
@@ -0,0 +1,2 @@+Runtime error: index out of bounds: 100+ ./test/golden/error.index-out-of-bounds.jsonnet:3:10-17
+ test/golden/error.index-out-of-bounds.jsonnet view
@@ -0,0 +1,3 @@+local arr = [1, 2, 3];++arr[0] + arr[100]
+ test/golden/error.invalid-key.golden view
@@ -0,0 +1,2 @@+Runtime error: type mismatch: expected string but got number+ ./test/golden/error.invalid-key.jsonnet:1:1-6:1
+ test/golden/error.invalid-key.jsonnet view
@@ -0,0 +1,5 @@+{+ [null] : "null",+ [3 + "3"] : "3 + 3",+ [3 % 3]: function(x) x+}
+ test/golden/error.sanity.golden view
@@ -0,0 +1,3 @@+Runtime error: Assertion failed. 1 != 2+ ./test/golden/error.sanity.jsonnet:14:1-16 function <anonymous>+ ./test/golden/error.sanity.jsonnet:14:1-16
+ test/golden/error.sanity.jsonnet view
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual(1, 2)
+ test/golden/format.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/format.jsonnet view
@@ -0,0 +1,310 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++// #+std.assertEqual(std.format('No format chars\n', []), 'No format chars\n') &&+std.assertEqual(std.format('', []), '') &&+std.assertEqual(std.format('%#%', []), '%') &&+std.assertEqual(std.format('%# +05.3%', []), ' %') &&+std.assertEqual(std.format('%# -+05.3%', []), '% ') &&++// %+std.assertEqual(std.format('%%', []), '%') &&+std.assertEqual(std.format('%%%%', []), '%%') &&+std.assertEqual(std.format('%s%%', ['foo']), 'foo%') &&+std.assertEqual(std.format('%%%s', ['foo']), '%foo') &&++// s+std.assertEqual(std.format('%s', ['test']), 'test') &&+std.assertEqual(std.format('%s', [true]), 'true') &&+std.assertEqual(std.format('%5s', ['test']), ' test') &&++// c+std.assertEqual(std.format('%c', ['a']), 'a') &&+std.assertEqual(std.format('%# +05.3c', ['a']), ' a') &&+std.assertEqual(std.format('%c', [std.codepoint('a')]), 'a') &&++// d (also a quick test of i and u)+std.assertEqual(std.format('thing-%d', [10]), 'thing-10') &&+std.assertEqual(std.format('thing-%#ld', [10]), 'thing-10') &&+std.assertEqual(std.format('thing-%d', [-10]), 'thing--10') &&+std.assertEqual(std.format('thing-%4d', [10]), 'thing- 10') &&+std.assertEqual(std.format('thing-%04d', [10]), 'thing-0010') &&+std.assertEqual(std.format('thing-% d', [10]), 'thing- 10') &&+std.assertEqual(std.format('thing-%- 4d', [10]), 'thing- 10 ') &&+std.assertEqual(std.format('thing-% d', [-10]), 'thing--10') &&+std.assertEqual(std.format('thing-%5.3d', [10.3]), 'thing- 010') &&+std.assertEqual(std.format('thing-%+5.3d', [10.3]), 'thing- +010') &&+std.assertEqual(std.format('thing-%+-5.3d', [10.3]), 'thing-+010 ') &&+std.assertEqual(std.format('thing-%-5.3d', [10.3]), 'thing-010 ') &&+std.assertEqual(std.format('thing-%#-5.3d', [10.3]), 'thing-010 ') &&+std.assertEqual(std.format('thing-%#-5.3i', [10.3]), 'thing-010 ') &&+std.assertEqual(std.format('thing-%#-5.3u', [10.3]), 'thing-010 ') &&+std.assertEqual(std.format('thing-%5.3d', [-10.3]), 'thing- -010') &&+std.assertEqual(std.format('thing-%+5.3d', [-10.3]), 'thing- -010') &&+std.assertEqual(std.format('thing-%+-5.3d', [-10.3]), 'thing--010 ') &&+std.assertEqual(std.format('thing-%-5.3d', [-10.3]), 'thing--010 ') &&+std.assertEqual(std.format('thing-%#-5.3d', [-10.3]), 'thing--010 ') &&+std.assertEqual(std.format('thing-%#-5.3i', [-10.3]), 'thing--010 ') &&+std.assertEqual(std.format('thing-%#-5.3u', [-10.3]), 'thing--010 ') &&+std.assertEqual(std.format('thing-%5.3d', [0.3]), 'thing- 000') &&+std.assertEqual(std.format('thing-%+5.3d', [0.3]), 'thing- +000') &&+std.assertEqual(std.format('thing-%+-5.3d', [0.3]), 'thing-+000 ') &&+std.assertEqual(std.format('thing-%-5.3d', [0.3]), 'thing-000 ') &&+std.assertEqual(std.format('thing-%#-5.3d', [0.3]), 'thing-000 ') &&+std.assertEqual(std.format('thing-%#-5.3i', [0.3]), 'thing-000 ') &&+std.assertEqual(std.format('thing-%#-5.3u', [0.3]), 'thing-000 ') &&+std.assertEqual(std.format('thing-%5.3d', [-0.3]), 'thing- 000') &&+std.assertEqual(std.format('thing-%+5.3d', [-0.3]), 'thing- +000') &&+std.assertEqual(std.format('thing-%+-5.3d', [-0.3]), 'thing-+000 ') &&+std.assertEqual(std.format('thing-%-5.3d', [-0.3]), 'thing-000 ') &&+std.assertEqual(std.format('thing-%#-5.3d', [-0.3]), 'thing-000 ') &&+std.assertEqual(std.format('thing-%#-5.3i', [-0.3]), 'thing-000 ') &&+std.assertEqual(std.format('thing-%#-5.3u', [-0.3]), 'thing-000 ') &&++// o+std.assertEqual(std.format('thing-%o', [10]), 'thing-12') &&+std.assertEqual(std.format('thing-%lo', [10]), 'thing-12') &&+std.assertEqual(std.format('thing-%o', [-10]), 'thing--12') &&+std.assertEqual(std.format('thing-%4o', [10]), 'thing- 12') &&+std.assertEqual(std.format('thing-%04o', [10]), 'thing-0012') &&+std.assertEqual(std.format('thing-% o', [10]), 'thing- 12') &&+std.assertEqual(std.format('thing-%- 4o', [10]), 'thing- 12 ') &&+std.assertEqual(std.format('thing-% o', [-10]), 'thing--12') &&+std.assertEqual(std.format('thing-%5.3o', [10.3]), 'thing- 012') &&+std.assertEqual(std.format('thing-%+5.3o', [10.3]), 'thing- +012') &&+std.assertEqual(std.format('thing-%+-5.3o', [10.3]), 'thing-+012 ') &&+std.assertEqual(std.format('thing-%-5.3o', [10.3]), 'thing-012 ') &&+std.assertEqual(std.format('thing-%#o', [10]), 'thing-012') &&+std.assertEqual(std.format('thing-%#lo', [10]), 'thing-012') &&+std.assertEqual(std.format('thing-%#o', [-10]), 'thing--012') &&+std.assertEqual(std.format('thing-%#4o', [10]), 'thing- 012') &&+std.assertEqual(std.format('thing-%#04o', [10]), 'thing-0012') &&+std.assertEqual(std.format('thing-%# o', [10]), 'thing- 012') &&+std.assertEqual(std.format('thing-%#- 4o', [10]), 'thing- 012') &&+std.assertEqual(std.format('thing-%# o', [-10]), 'thing--012') &&+std.assertEqual(std.format('thing-%#5.3o', [10.3]), 'thing- 012') &&+std.assertEqual(std.format('thing-%#+5.3o', [10.3]), 'thing- +012') &&+std.assertEqual(std.format('thing-%#+-5.3o', [10.3]), 'thing-+012 ') &&+std.assertEqual(std.format('thing-%#-5.3o', [10.3]), 'thing-012 ') &&++// x+std.assertEqual(std.format('thing-%x', [910]), 'thing-38e') &&+std.assertEqual(std.format('thing-%lx', [910]), 'thing-38e') &&+std.assertEqual(std.format('thing-%x', [-910]), 'thing--38e') &&+std.assertEqual(std.format('thing-%5x', [910]), 'thing- 38e') &&+std.assertEqual(std.format('thing-%05x', [910]), 'thing-0038e') &&+std.assertEqual(std.format('thing-% x', [910]), 'thing- 38e') &&+std.assertEqual(std.format('thing-%- 5x', [910]), 'thing- 38e ') &&+std.assertEqual(std.format('thing-% x', [-910]), 'thing--38e') &&+std.assertEqual(std.format('thing-%6.4x', [910.3]), 'thing- 038e') &&+std.assertEqual(std.format('thing-%+6.4x', [910.3]), 'thing- +038e') &&+std.assertEqual(std.format('thing-%+-6.4x', [910.3]), 'thing-+038e ') &&+std.assertEqual(std.format('thing-%-6.4x', [910.3]), 'thing-038e ') &&+std.assertEqual(std.format('thing-%#x', [910]), 'thing-0x38e') &&+std.assertEqual(std.format('thing-%#lx', [910]), 'thing-0x38e') &&+std.assertEqual(std.format('thing-%#x', [-910]), 'thing--0x38e') &&+std.assertEqual(std.format('thing-%#7x', [910]), 'thing- 0x38e') &&+std.assertEqual(std.format('thing-%#07x', [910]), 'thing-0x0038e') &&+std.assertEqual(std.format('thing-%# x', [910]), 'thing- 0x38e') &&+std.assertEqual(std.format('thing-%#- 7x', [910]), 'thing- 0x38e ') &&+std.assertEqual(std.format('thing-%# x', [-910]), 'thing--0x38e') &&+std.assertEqual(std.format('thing-%#8.4x', [910.3]), 'thing- 0x038e') &&+std.assertEqual(std.format('thing-%#+8.4x', [910.3]), 'thing- +0x038e') &&+std.assertEqual(std.format('thing-%#+-8.4x', [910.3]), 'thing-+0x038e ') &&+std.assertEqual(std.format('thing-%#-8.4x', [910.3]), 'thing-0x038e ') &&++// X+std.assertEqual(std.format('thing-%X', [910]), 'thing-38E') &&+std.assertEqual(std.format('thing-%lX', [910]), 'thing-38E') &&+std.assertEqual(std.format('thing-%X', [-910]), 'thing--38E') &&+std.assertEqual(std.format('thing-%5X', [910]), 'thing- 38E') &&+std.assertEqual(std.format('thing-%05X', [910]), 'thing-0038E') &&+std.assertEqual(std.format('thing-% X', [910]), 'thing- 38E') &&+std.assertEqual(std.format('thing-%- 5X', [910]), 'thing- 38E ') &&+std.assertEqual(std.format('thing-% X', [-910]), 'thing--38E') &&+std.assertEqual(std.format('thing-%6.4X', [910.3]), 'thing- 038E') &&+std.assertEqual(std.format('thing-%+6.4X', [910.3]), 'thing- +038E') &&+std.assertEqual(std.format('thing-%+-6.4X', [910.3]), 'thing-+038E ') &&+std.assertEqual(std.format('thing-%-6.4X', [910.3]), 'thing-038E ') &&+std.assertEqual(std.format('thing-%#X', [910]), 'thing-0X38E') &&+std.assertEqual(std.format('thing-%#lX', [910]), 'thing-0X38E') &&+std.assertEqual(std.format('thing-%#X', [-910]), 'thing--0X38E') &&+std.assertEqual(std.format('thing-%#7X', [910]), 'thing- 0X38E') &&+std.assertEqual(std.format('thing-%#07X', [910]), 'thing-0X0038E') &&+std.assertEqual(std.format('thing-%# X', [910]), 'thing- 0X38E') &&+std.assertEqual(std.format('thing-%#- 7X', [910]), 'thing- 0X38E ') &&+std.assertEqual(std.format('thing-%# X', [-910]), 'thing--0X38E') &&+std.assertEqual(std.format('thing-%#8.4X', [910.3]), 'thing- 0X038E') &&+std.assertEqual(std.format('thing-%#+8.4X', [910.3]), 'thing- +0X038E') &&+std.assertEqual(std.format('thing-%#+-8.4X', [910.3]), 'thing-+0X038E ') &&+std.assertEqual(std.format('thing-%#-8.4X', [910.3]), 'thing-0X038E ') &&++// e+std.assertEqual(std.format('%e', [910]), '9.100000e+02') &&+std.assertEqual(std.format('%e', [0]), '0.000000e+00') &&+std.assertEqual(std.format('%.0le', [910]), '9e+02') &&+std.assertEqual(std.format('%.0le', [0]), '0e+00') &&+std.assertEqual(std.format('%#e', [-910]), '-9.100000e+02') &&+std.assertEqual(std.format('%16e', [910]), ' 9.100000e+02') &&+std.assertEqual(std.format('%016e', [910]), '00009.100000e+02') &&+std.assertEqual(std.format('% e', [910]), ' 9.100000e+02') &&+std.assertEqual(std.format('%- 16e', [910]), ' 9.100000e+02 ') &&+std.assertEqual(std.format('% e', [-910]), '-9.100000e+02') &&+std.assertEqual(std.format('%16.4e', [910.3]), ' 9.1030e+02') &&+std.assertEqual(std.format('%+16.4e', [910.3]), ' +9.1030e+02') &&+std.assertEqual(std.format('%+-16.4e', [910.3]), '+9.1030e+02 ') &&+std.assertEqual(std.format('%-16.4e', [910.3]), '9.1030e+02 ') &&+std.assertEqual(std.format('%#.0e', [910.3]), '9.e+02') &&+std.assertEqual(std.format('%#.0e', [900]), '9.e+02') &&+std.assertEqual(std.format('%.3e', [1000000001]), '1.000e+09') &&+// For very small numbers, Go and C++ differ in the accuracy of log().+// The following test makes sure that we don't at least have a runtime error+// while calculating it.+//std.assertEqual(std.type(std.format('%e', [3.94066e-324])), 'string') &&++// E+std.assertEqual(std.format('%E', [910]), '9.100000E+02') &&+std.assertEqual(std.format('%.0lE', [910]), '9E+02') &&+std.assertEqual(std.format('%#E', [-910]), '-9.100000E+02') &&+std.assertEqual(std.format('%16E', [910]), ' 9.100000E+02') &&+std.assertEqual(std.format('%016E', [910]), '00009.100000E+02') &&+std.assertEqual(std.format('% E', [910]), ' 9.100000E+02') &&+std.assertEqual(std.format('%- 16E', [910]), ' 9.100000E+02 ') &&+std.assertEqual(std.format('% E', [-910]), '-9.100000E+02') &&+std.assertEqual(std.format('%16.4E', [910.3]), ' 9.1030E+02') &&+std.assertEqual(std.format('%+16.4E', [910.3]), ' +9.1030E+02') &&+std.assertEqual(std.format('%+-16.4E', [910.3]), '+9.1030E+02 ') &&+std.assertEqual(std.format('%-16.4E', [910.3]), '9.1030E+02 ') &&+std.assertEqual(std.format('%#.0E', [910.3]), '9.E+02') &&+std.assertEqual(std.format('%#.0E', [900]), '9.E+02') &&+std.assertEqual(std.format('%.3E', [1000000001]), '1.000E+09') &&++// f+std.assertEqual(std.format('%f', [910]), '910.000000') &&+std.assertEqual(std.format('%f', 0), '0.000000') &&+std.assertEqual(std.format('%.0lf', [910]), '910') &&+std.assertEqual(std.format('%#f', [-910]), '-910.000000') &&+std.assertEqual(std.format('%12f', [910]), ' 910.000000') &&+std.assertEqual(std.format('%012f', [910]), '00910.000000') &&+std.assertEqual(std.format('% f', [910]), ' 910.000000') &&+std.assertEqual(std.format('%- 12f', [910]), ' 910.000000 ') &&+std.assertEqual(std.format('% f', [-910]), '-910.000000') &&+std.assertEqual(std.format('%12.4f', [910.3]), ' 910.3000') &&+std.assertEqual(std.format('%+12.4f', [910.3]), ' +910.3000') &&+std.assertEqual(std.format('%+-12.4f', [910.3]), '+910.3000 ') &&+std.assertEqual(std.format('%-12.4f', [910.3]), '910.3000 ') &&+std.assertEqual(std.format('%#.0f', [910.3]), '910.') &&+std.assertEqual(std.format('%#.0f', [910]), '910.') &&+std.assertEqual(std.format('%.3f', [1000000001]), '1000000001.000') &&+std.assertEqual(std.format('%f', [-0.1]), '-0.100000') &&+std.assertEqual(std.format('%.1f', [0.99]), '1.0') &&+std.assertEqual(std.format('%.1f', [1.95555]), '2.0') &&+std.assertEqual(std.format('%.4f', [0.99995]), '1.0000') &&++// g+std.assertEqual(std.format('%#.3g', [1000000001]), '1.00e+09') &&+std.assertEqual(std.format('%#.3g', [1100]), '1.10e+03') &&+std.assertEqual(std.format('%#.3g', [1.1]), '1.10') &&+std.assertEqual(std.format('%#.5g', [1000000001]), '1.0000e+09') &&+std.assertEqual(std.format('%#.5g', [1100]), '1100.0') &&+std.assertEqual(std.format('%#.5g', [110]), '110.00') &&+std.assertEqual(std.format('%#.5g', [1.1]), '1.1000') &&+std.assertEqual(std.format('%#.1g', [0.99]), '1.') &&+std.assertEqual(std.format('%#.1g', [1.95555]), '2.') &&+std.assertEqual(std.format('%#.4g', [0.99995]), '1.000') &&+std.assertEqual(std.format('%#10.3g', [1000000001]), ' 1.00e+09') &&+std.assertEqual(std.format('%#10.3g', [1100]), ' 1.10e+03') &&+std.assertEqual(std.format('%#10.3g', [1.1]), ' 1.10') &&+std.assertEqual(std.format('%#10.5g', [1000000001]), '1.0000e+09') &&+std.assertEqual(std.format('%#10.5g', [1100]), ' 1100.0') &&+std.assertEqual(std.format('%#10.5g', [110]), ' 110.00') &&+std.assertEqual(std.format('%#10.5g', [1.1]), ' 1.1000') &&+std.assertEqual(std.format('%.3g', [1000000001]), '1e+09') &&+std.assertEqual(std.format('%.3g', [1100]), '1.1e+03') &&+std.assertEqual(std.format('%.3g', [1.1]), '1.1') &&+std.assertEqual(std.format('%.5g', [1000000001]), '1e+09') &&+std.assertEqual(std.format('%.5g', [1100]), '1100') &&+std.assertEqual(std.format('%.5g', [110]), '110') &&+std.assertEqual(std.format('%.5g', [1.1]), '1.1') &&+std.assertEqual(std.format('%10.3g', [1000000001]), ' 1e+09') &&+std.assertEqual(std.format('%10.3g', [1100]), ' 1.1e+03') &&+std.assertEqual(std.format('%10.3g', [1.1]), ' 1.1') &&+std.assertEqual(std.format('%10.5g', [1000000001]), ' 1e+09') &&+std.assertEqual(std.format('%10.5g', [1100]), ' 1100') &&+std.assertEqual(std.format('%10.5g', [110]), ' 110') &&+std.assertEqual(std.format('%10.5g', [1.1]), ' 1.1') &&++// G+std.assertEqual(std.format('%#.3G', [1000000001]), '1.00E+09') &&+std.assertEqual(std.format('%#.3G', [1100]), '1.10E+03') &&+std.assertEqual(std.format('%#.3G', [1.1]), '1.10') &&+std.assertEqual(std.format('%#.5G', [1000000001]), '1.0000E+09') &&+std.assertEqual(std.format('%#.5G', [1100]), '1100.0') &&+std.assertEqual(std.format('%#.5G', [110]), '110.00') &&+std.assertEqual(std.format('%#.5G', [1.1]), '1.1000') &&+std.assertEqual(std.format('%#10.3G', [1000000001]), ' 1.00E+09') &&+std.assertEqual(std.format('%#10.3G', [1100]), ' 1.10E+03') &&+std.assertEqual(std.format('%#10.3G', [1.1]), ' 1.10') &&+std.assertEqual(std.format('%#10.5G', [1000000001]), '1.0000E+09') &&+std.assertEqual(std.format('%#10.5G', [1100]), ' 1100.0') &&+std.assertEqual(std.format('%#10.5G', [110]), ' 110.00') &&+std.assertEqual(std.format('%#10.5G', [1.1]), ' 1.1000') &&+std.assertEqual(std.format('%.3G', [1000000001]), '1E+09') &&+std.assertEqual(std.format('%.3G', [1100]), '1.1E+03') &&+std.assertEqual(std.format('%.3G', [1.1]), '1.1') &&+std.assertEqual(std.format('%.5G', [1000000001]), '1E+09') &&+std.assertEqual(std.format('%.5G', [1100]), '1100') &&+std.assertEqual(std.format('%.5G', [110]), '110') &&+std.assertEqual(std.format('%.5G', [1.1]), '1.1') &&+std.assertEqual(std.format('%10.3G', [1000000001]), ' 1E+09') &&+std.assertEqual(std.format('%10.3G', [1100]), ' 1.1E+03') &&+std.assertEqual(std.format('%10.3G', [1.1]), ' 1.1') &&+std.assertEqual(std.format('%10.5G', [1000000001]), ' 1E+09') &&+std.assertEqual(std.format('%10.5G', [1100]), ' 1100') &&+std.assertEqual(std.format('%10.5G', [110]), ' 110') &&+std.assertEqual(std.format('%10.5G', [1.1]), ' 1.1') &&++// lots together, also test % operator+std.assertEqual('%s[%05d]-%2x%2x%2x%c' % ['foo', 3991, 17, 18, 17, 100], 'foo[03991]-111211d') &&++// use of *+std.assertEqual('%*d' % [10, 8], '%10d' % [8]) &&+std.assertEqual('%*.*f' % [10, 3, 1 / 3], '%10.3f' % [1 / 3]) &&++// Test mappings+std.assertEqual('%(name)s[%(id)05d]-%(a)2x%(b)2x%(c)2x%(x)c' % { name: 'foo', id: 3991, a: 17, b: 18, c: 17, x: 100 },+ 'foo[03991]-111211d') &&++local text = |||+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. In pellentesque felis mi, et iaculis+ tellus consectetur pretium. Integer ultricies ullamcorper arcu quis bibendum. Vivamus luctus nec+ nulla id egestas. Vestibulum lectus nibh, lobortis sed gravida ac, pellentesque sit amet eros.+ Nulla urna purus, ornare at iaculis eget, pharetra sed libero. Fusce a neque malesuada,+ hendrerit ex nec, suscipit lorem. Aenean orci quam, placerat sed mollis ut, faucibus nec turpis.+ Vivamus consectetur auctor vehicula. Nam eu risus sit amet eros mattis finibus nec ac enim.+ Quisque velit metus, tristique in urna in, dictum gravida elit.%(a)s++ Aenean laoreet libero nunc. Cras molestie condimentum mollis. Nam quis leo sed enim vestibulum+ dapibus faucibus eget elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Class+ aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent+ cursus magna at urna viverra, eget venenatis ante sodales. In vitae magna sed lacus iaculis+ porttitor eu vel nisl. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vitae sapien+ vel eros ultricies iaculis. Pellentesque et metus libero. Proin nec rhoncus est. Vivamus a+ aliquam ipsum, ut vehicula nibh. Sed ac posuere dolor.+||| % { a: 'a' };++std.assertEqual(std.length(text), 1244) &&+++true
+ test/golden/functions.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/functions.jsonnet view
@@ -0,0 +1,58 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++// Correct self binding from within a function.+std.assertEqual({ x: 1, y: { x: 0, y: function() self.x }.y() }, { x: 1, y: 0 }) &&++local max = function(a, b) if a > b then a else b;+local inv(d) = if d != 0 then 1 / d else 0;++std.assertEqual(max(4, 8), 8) &&+std.assertEqual(inv(0.5), 2) &&++local is_even = function(n) if n == 0 then true else is_odd(n - 1),+ is_odd = function(n) if n == 1 then true else is_even(n - 1);++std.assertEqual(is_even(42), true) &&+std.assertEqual(is_odd(41), true) &&++std.assertEqual((function(a, b) [a, b])(b=1, a=2), [2, 1]) &&+std.assertEqual((function(a, b=2) [a, b])(3), [3, 2]) &&++// Mutually recursive default arguments.+std.assertEqual((function(a=[1, b[1]], b=[a[0], 2]) [a, b])(), [[1, 2], [1, 2]]) &&+std.assertEqual((local a = 'no1', b = 'no2'; function(a=[1, b[1]], b=[a[0], 2]) [a, b])(),+ [[1, 2], [1, 2]]) &&+std.assertEqual((local x = 3; function(a=[x, b[1]], b=[a[0], 2]) [a, b])(),+ [[3, 2], [3, 2]]) &&+std.assertEqual({ g: 3, f(a=[self.g, b[1]], b=[a[0], 2]): [a, b] }.f(),+ [[3, 2], [3, 2]]) &&++local url(host, port=80, protocol='http', url='%s://%s:%d/' % [protocol, host, port]) = url;++std.assertEqual(url('myhost'), 'http://myhost:80/') &&+std.assertEqual(url('mybucket', 8080, protocol='gs'), 'gs://mybucket:8080/') &&+std.assertEqual(url(null, url='wat'), 'wat') &&++local test(a=error 'Need a', alt="'" + a + "'") = alt;+std.assertEqual(test(a='Q'), "'Q'") &&+std.assertEqual(test(alt='|Q|'), '|Q|') &&++local X = 3;+std.assertEqual((function(X=4) X)(), 4) &&+std.assertEqual((function(X=4, Y=X) Y)(), 4) &&+std.assertEqual((function(X=Z, Y=X+1, Z=5) Y)(), 6) &&+std.assertEqual((function(X=Z, Y=X+1, Z=5) Y)(3), 4) &&+std.assertEqual((function(X=Z, Y=X+1, Z=5) Y)(null, 5), 5) &&++true
+ test/golden/hello-world.golden view
@@ -0,0 +1,3 @@+{+ "message": "Hello, world"+}
+ test/golden/hello-world.jsonnet view
@@ -0,0 +1,4 @@+{+ message: self.hello("world"),+ hello(world):: "Hello, " + world+}
@@ -0,0 +1,5 @@+{+ "voice": {+ "last_name": "Bon Jovi"+ }+}
@@ -0,0 +1,10 @@+{+ voice: {+ name :: "John",+ last_name: "Bon Jovi"+ },+ guitar :: {+ name: "Richie",+ last_name: "Sambora"+ }+}
+ test/golden/importbin_nonutf8.golden view
@@ -0,0 +1,5 @@+[+ 255,+ 0,+ 254+]
+ test/golden/importbin_nonutf8.jsonnet view
@@ -0,0 +1,1 @@+importbin "nonutf8.bin"
+ test/golden/imports.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/imports.jsonnet view
@@ -0,0 +1,6 @@++local imports = import "imports.libsonnet";++std.assertEqual(imports.foo, 'bar') &&++true
+ test/golden/imports.libsonnet view
@@ -0,0 +1,4 @@+{+ foo: 'bar',+ bar: 'baz'+}
+ test/golden/importstr-nonexistent.golden view
@@ -0,0 +1,2 @@+Parse error: this file does not exist (No such file or directory)+ ./test/golden/importstr-nonexistent.jsonnet:1:1-37
+ test/golden/importstr-nonexistent.jsonnet view
@@ -0,0 +1,1 @@+importstr "this file does not exist"
+ test/golden/importstr-twice.golden view
@@ -0,0 +1,1 @@+"{ foo :: \"bar\" }\n{ foo :: \"bar\" }\n"
+ test/golden/importstr-twice.jsonnet view
@@ -0,0 +1,3 @@+local x = importstr "empty.jsonnet";+local y = importstr "empty.jsonnet";+x + y
+ test/golden/importstr.golden view
@@ -0,0 +1,1 @@+"{ foo :: \"bar\" }\n"
+ test/golden/importstr.jsonnet view
@@ -0,0 +1,1 @@+importstr "empty.jsonnet"
+ test/golden/lazy.golden view
@@ -0,0 +1,1 @@+0
+ test/golden/lazy.jsonnet view
@@ -0,0 +1,4 @@+local x = error "foo", c = false;+if c+ then x+ else 0
+ test/golden/lib/capture_std.libsonnet view
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std
+ test/golden/lib/capture_std_func.libsonnet view
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++function () std
+ test/golden/local.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/local.jsonnet view
@@ -0,0 +1,33 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual(local x = 3; x, 3) &&+std.assertEqual(local x = error 'foo'; 3, 3) &&+std.assertEqual(local x = y, y = 3; x, 3) &&++local x = [x, y, 'foo'], y = [y, 'bar', x];+std.assertEqual(x[1][1], 'bar') &&+std.assertEqual(y[0][2][2], 'foo') &&++local x = { '0': 'zero', '1': y['1'] },+ y = { '0': x['0'], '1': 'one' };++std.assertEqual(x, y) &&++local x = ['zero', y[1]],+ y = [x[0], 'one'];++std.assertEqual(x, y) &&+++true
+ test/golden/manifest-json-ex.golden view
@@ -0,0 +1,1 @@+"{\n \"x\": [\n 1,\n 2,\n 3,\n true,\n false,\n null,\n \"string\\\\n\\\"string\\\"\\\\n\"\n ],\n \"y\": {\n \"a\": 1,\n \"b\": 2,\n \"c\": [\n 1,\n 2\n ]\n }\n}"
+ test/golden/manifest-json-ex.jsonnet view
@@ -0,0 +1,8 @@+std.manifestJsonEx(+ {+ x: [1, 2, 3, true, false, null,+ @"string\n""string""\n"],+ y: { a: 1, b: 2, c: [1, 2] }+ },+ " "+)
+ test/golden/manifest-yaml-doc.golden view
@@ -0,0 +1,1 @@+"\"x\":\n- 1\n- 2\n- 3\n- true\n- false\n- null\n- |\n string\n string\n\"y\":\n \"a\": 1\n \"b\": 2\n \"c\":\n - 1\n - 2"
+ test/golden/manifest-yaml-doc.jsonnet view
@@ -0,0 +1,7 @@+std.manifestYamlDoc(+ {+ x: [1, 2, 3, true, false, null,+ "string\nstring\n"],+ y: { a: 1, b: 2, c: [1, 2] }+ }+)
+ test/golden/null.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/null.jsonnet view
@@ -0,0 +1,21 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual(null, null) &&+std.assertEqual(null == null, true) &&+std.assertEqual(null != false, true) &&+std.assertEqual(null != 0, true) &&+std.assertEqual(null != 3, true) &&+std.assertEqual(null != 'null', true) &&+std.assertEqual(null != 'false', true) &&+true
+ test/golden/object-methods.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/object-methods.jsonnet view
@@ -0,0 +1,7 @@+local test = {+ foo(x): x + 1+};++std.assertEqual(test.foo(5), 6) &&++true
+ test/golden/object.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/object.jsonnet view
@@ -0,0 +1,114 @@+/*+Copyright 2015 Google Inc. All rights reserved.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual({}, {}) &&+std.assertEqual({ 'x': 1 }, { 'x': 1 }) &&+std.assertEqual({ '': 1 }, { '': 1 }) &&+std.assertEqual({ x: 1, y: 2 }.y, 2) &&+std.assertEqual({ x: 1, y: 2 }["y"], 2) &&+std.assertEqual({ x: error "foo", y: 2 }.y, 2) &&+std.assertEqual({ x: 1, y: 2 } + { z: 3 }, { x: 1, y: 2, z: 3 }) &&+std.assertEqual({ x: 1, y: 2 } + { y: 3 }, { x: 1, y: 3 }) &&+std.assertEqual({ x: 1, y: 2 } + { y+: 3 }, { x: 1, y: 5 }) &&+std.assertEqual({ a: { x: 1 }, b: { x: 2 } } + { a+: { y: 3 }, b: { x: 3 } }, { a: { x: 1, y: 3 }, b: { x: 3 } }) &&++std.assertEqual({ x: 1, y: 2 } == { x: 1, y: 2 }, true) &&+std.assertEqual({ x: 1, y: 2 } == { x: 1, y: 2, z: 3 }, false) &&+std.assertEqual({ x: 1, y: 2 } != { x: 1, y: 2 }, false) &&+std.assertEqual({ x: 1, y: 2 } != { x: 1, y: 2, z: 3 }, true) &&++std.assertEqual({ f(x, y, z): x, y: self.f(1, 2, 3) }.y, 1) &&+//std.assertEqual({ ["f"](x, y, z):: x, "y"(x): self.f(x, 2, 3), z: self.y(4) }.z, 4) &&++// Object Local++std.assertEqual({ local foo = 3, local bar(n) = foo + n, x: foo, y: foo + bar(1) }, { x: 3, y: 7 }) &&+std.assertEqual({ local foo = bar(3)[1], local bar(n) = [foo, n], x: foo, y: [foo] + bar(3) }, { x: 3, y: [3, 3, 3] }) &&+++// Computed field name+std.assertEqual(local f = "x"; { [f]: 1 }, { x: 1 }) &&+std.assertEqual({ [pair[0]]: pair[1] for pair in [["x", 1], ["y", 2]] }, { x: 1, y: 2 }) &&+std.assertEqual(local t = { a: 1, b: 2, c: null, d: 4 }; { [x]: t[x] for x in [y for y in ["a", "b", "c", "d"] if t[y] != null] }, { a: 1, b: 2, d: 4 }) &&++// Comprehension object+std.assertEqual(local x = 10; { ["" + x]: x for x in [x, x + 1, x + 2] }, { "10": 10, "11": 11, "12": 12 }) &&+//std.assertEqual(local x = "foo"; { local x = "bar", [x]: 1 }, { foo: 1 }) &&+//std.assertEqual({ [x + ""]: if x == 1 then 1 else x + $["1"] for x in [1, 2, 3] }, { "1": 1, "2": 3, "3": 4 }) &&++std.assertEqual(local x = "baz"; { local x = "bar", [x]: x for x in ["foo"] }, { foo: "bar" }) &&+++std.assertEqual({ f: "foo", g: { [self.f]: 7 } }, { f: "foo", g: { foo: 7 } }) &&++std.assertEqual({ [k]: null for k in [] }, {}) &&+std.assertEqual({ [null]: "test" for k in [1] }, {}) &&+std.assertEqual({ [null]: "test" }, {}) &&++std.assertEqual({ ["" + k]: k for k in [1, 2, 3] }, { "1": 1, "2": 2, "3": 3 }) &&+std.assertEqual({ ["" + (k + 1)]: (k + 1) for k in [0, 1, 2] }, { ["" + k]: k for k in [1, 2, 3] }) &&+std.assertEqual({ ["" + k]: k for k in [1, 2, 3] }, { "1": 1, "2": 2, "3": 3 }) &&+std.assertEqual({ [x + ""]: x + foo, local foo = 3 for x in [1, 2, 3] }, { "1": 4, "2": 5, "3": 6 }) &&++// Test for #791+std.assertEqual({ [x]: true for x in ['\\k'] }, { '\\k': true }) &&+++local obj = {+ f14true: { x: 1, y: 4, z: true },+ f14false: { x: 1, y: 4, z: false },+ f16true: { x: 1, y: 6, z: true },+ f16false: { x: 1, y: 6, z: false },+ f26true: { x: 2, y: 6, z: true },+ f26false: { x: 2, y: 6, z: false },+ f36true: { x: 3, y: 6, z: true },+ f36false: { x: 3, y: 6, z: false },+};++std.assertEqual(obj, { ["f" + x + y + z]: { x: x, y: y, z: z } for x in [1, 2, 3] for y in [1, 4, 6] if x + 2 < y for z in [true, false] }) &&++//std.assertEqual({ f: { foo: 7, bar: 1 } { [self.name]+: 3, name:: "foo" }, name:: "bar" },+// { f: { foo: 7, bar: 4 } }) &&++//std.assertEqual({ name:: "supername" } { name:: "selfname", f: { wrongname: 7, supername: 1, name:: "wrongname" } { [super.name]+: 3 } },+// { f: { wrongname: 7, supername: 4 } }) &&++std.assertEqual({} + { f+: 3 }, { f: 3 }) &&+std.assertEqual({} + { f+: { g+: "foo" } }, { f: { g: "foo" } }) &&+std.assertEqual({} + { f+: [3] }, { f: [3] }) &&++//std.assertEqual({ f+: 3 }, { f: 3 }) &&+//std.assertEqual({ f+: { g+: "foo" } }, { f: { g: "foo" } }) &&+//std.assertEqual({ f+: [3] }, { f: [3] }) &&++// Ensure that e in super is handled correctly during the +: desugaring, because it moves+// into a different object scope:+//std.assertEqual(+// { opt:: true, f: { y: 5 } } + { f+: { [if "opt" in super then "x" else "y"]+: 3 } },+// { f: { x: 3, y: 5 } }+//) &&++std.assertEqual({ x: 1 } + { a: "x" in super, b: "y" in super }, { x: 1, a: true, b: false }) &&+std.assertEqual({ x:: 1 } + { a: "x" in super, b: "y" in super }, { a: true, b: false }) &&++std.assertEqual("x" in { x: 3 }, true) &&+std.assertEqual("x" in { y: 3 }, false) &&++std.assertEqual({ x: 1, a: "x" in self, b: "y" in self }, { x: 1, a: true, b: false }) &&+std.assertEqual({ x:: 1, a: "x" in self, b: "y" in self }, { a: true, b: false }) &&+std.assertEqual({ f: "f" in self }, { f: true }) &&++true
+ test/golden/oop.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/oop.jsonnet view
@@ -0,0 +1,88 @@+/*+Copyright 2015 Google Inc. All rights reserved.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++// basic self+std.assertEqual({ x: 1, y: self.x }.y, 1) &&+std.assertEqual(({ x: 1, y: self.x } + {}).y, 1) &&++// self as object+std.assertEqual({ x: 1, y: self }.y.y.x, 1) &&+std.assertEqual(({ x: 1, y: self } + {}).y.y.x, 1) &&++// self bound correctly in superobjects+std.assertEqual(({ x: 1, y: self.x } + { x: 2 }).y, 2) &&++// basic super+std.assertEqual(({ x: 1, y: self.x } + { x: 2, y: super.y + 1, z: super.y }), { x: 2, y: 3, z: 2 }) &&++// self bound correctly in super objects when calling via super+std.assertEqual(({ x: self.x } + { x: 2, y: super.x }).y, 2) &&++// super up 2 levels (note + is left associative)+std.assertEqual({ x: 1 } + { x: 2, y: super.x } + { x: 3, z: super.x }, { x: 3, y: 1, z: 2 }) &&++// variables bound statically in superobjects+std.assertEqual((local z = 3; { x: z }) + (local z = 4; { x: z, y: super.x }), { x: 4, y: 3 }) &&++// static binding in superobject+std.assertEqual(local A = { a1: 1, a2: self.a1, a3: A.a2 };+ A { a1: 2, a2: super.a2 + 1, b1: super.a2, b2: super.a3 }, { a1: 2, a2: 3, a3: 1, b1: 2, b2: 1 }) &&+++// multiple inheritance+std.assertEqual({ x: 1, y: self.x } + ({ x: 2 } + { x: 3, y: 5, z: super.y }), { x: 3, y: 5, z: 3 }) &&++// multiple inheritance of empty middle+std.assertEqual({ x: 1 } + ({} + { y: super.x }), { x: 1, y: 1 }) &&+++// grandparents with super visiting all leaves+local A = { name: 'A' },+ B = { name: 'B', sB: super.name },+ C = { name: 'C', sC: super.name },+ D = { name: 'D', sD: super.name };++//std.assertEqual((A + B) + (C + D), { name: 'D', sB: 'A', sC: 'B', sD: 'C' }) &&++// Outer variable+local a = {+ d: 0,+ local outer = self,+ b: { c: outer.d + 1, c2: a.d + 1 },+};++local e = a.b { d: 4 };+local e2 = (a { d: 4 }).b;++//std.assertEqual(e.c, 1) &&+std.assertEqual(e.c2, 1) &&+//std.assertEqual(e2.c, 5) &&+std.assertEqual(e2.c2, 1) &&++// $ is late-bound+std.assertEqual(({ x: 1, y: { a: $.x } } + { x: 2 }).y.a, 2) &&++// DAG+//std.assertEqual({ x: 1, y: 2 } + (local A = { x: super.y, y: super.x }; A + A), { x: 1, y: 2 }) &&+++// Object composition: inheritance+std.assertEqual(local f = 'x'; { x: 2 } + { [f]: 1 }, { x: 1 }) &&+// more object composition+std.assertEqual({ x: 3, z: 4 } + { [pair[0]]: pair[1] for pair in [['x', 1], ['y', 2]] }, { x: 1, y: 2, z: 4 }) &&+// more inheritance and object composition+std.assertEqual({} + { [k]: k + '_' for k in ['x', 'y', 'foo', 'foobar'] }, { x: 'x_', y: 'y_', foo: 'foo_', foobar: 'foobar_' }) &&+true
+ test/golden/oop_extra.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/oop_extra.jsonnet view
@@ -0,0 +1,57 @@+/*+Copyright 2015 Google Inc. All rights reserved.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++// Simple super+std.assertEqual(({ x: 0, y: self.x } + { x: 1, y: super.y }).y, 1) &&++// returning self+std.assertEqual(({ x: 0, y: self.x } + { x: 1, z: self }).z.y, 1) &&++// extending self on the right+std.assertEqual(({ x: 0, y: self.x } + { x: 1, z: (self + {}).y }).z, 1) &&+std.assertEqual(({ x: 0, y: self.x } + { x: 1, z: (self + {}) }).z.y, 1) &&++// extending self on the right has dynamic binding+std.assertEqual(({ x: 0, y: self.x } + { x: 2, z: (self + { x: 1 }).y }).z, 1) &&+std.assertEqual(({ x: 0, y: self.x } + { x: 2, z: (self + { x: 1 }) }).z.y, 1) &&++std.assertEqual(({ x: 0, y: self.x } + { x: 2, w: 1, z: (self + { x: super.w }).y }).z, 1) &&+std.assertEqual(({ x: 0, y: self.x } + { x: 2, w: 1, z: (self + { x: super.w }) }).z.y, 1) &&+std.assertEqual(({ x: 0, y: self.x } + { x: 2, w: 1, z: (self + { x: self.w }).y }).z, 1) &&+std.assertEqual(({ x: 0, y: self.x } + { x: 2, w: 1, z: (self + { x: self.w }) }).z.y, 1) &&++// self + self+std.assertEqual(({ x: 1, y: super.x } + { z: (self + self).y }).z, 1) &&++// extending self on the left+std.assertEqual(({ x: 0, y: self.x } + { x: 1, z: ({} + self).y }).z, 1) &&+std.assertEqual(({ x: 0, y: self.x } + { x: 1, z: ({} + self) }).z.y, 1) &&++std.assertEqual(({ x: 1, z: ({ x: 0, y: self.x } + self).y }).z, 1) &&+std.assertEqual(({ x: 1, z: ({ x: 0, y: self.x } + self) }).z.y, 1) &&++std.assertEqual(({ x: 0, z: ({ w: 1, x: 2, y: self.w } + self).y }).z, 1) &&+std.assertEqual(({ x: 0, z: ({ w: 1, x: 2, y: self.w } + self) }).z.y, 1) &&++std.assertEqual(({ x: 1, w: self.x, z: ({ x: 2, y: self.w } + self).y }).z, 1) &&+std.assertEqual(({ x: 1, w: self.x, z: ({ x: 2, y: self.w } + self) }).z.y, 1) &&+++// extra++std.assertEqual({ a: ({ b: self.c, c: 1 } + self).b }.a, 1) &&++true
+ test/golden/outermost.golden view
@@ -0,0 +1,22 @@+[+ {+ "bar": {+ "baz": {+ "qux": {+ "quux": "test1"+ }+ }+ },+ "foo": "test1"+ },+ {+ "bar": {+ "baz": {+ "qux": {+ "quux": "test2"+ }+ }+ },+ "foo": "test2"+ }+]
+ test/golden/outermost.jsonnet view
@@ -0,0 +1,24 @@+local test = [+ {+ foo: "test1",+ bar: {+ baz: {+ qux: {+ quux: $.foo+ }+ }+ }+ },+ {+ foo: "test2",+ bar: {+ baz: {+ qux: {+ quux: $["foo"]+ }+ }+ }+ }+];++test
+ test/golden/precedence.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/precedence.jsonnet view
@@ -0,0 +1,41 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual(1 + 2 * 3, 7) &&+std.assertEqual(1 + 2 * 3, 7) &&+std.assertEqual(+1 + 2 * 3, 7) &&+std.assertEqual(-1 + 2 * 3, 5) &&+std.assertEqual(3 + 2 / 2, 4) &&+std.assertEqual(6 - 3 + 2, 5) &&+std.assertEqual(6 / 3 / 2, 1) &&+std.assertEqual(6 / 3 / 2, 1) &&+std.assertEqual(6 / 3 * 2, 4) &&+std.assertEqual(6 / 3 % 2, 0) &&+std.assertEqual(1 + 2 << 1, 6) &&+std.assertEqual(42 << 1 >> 1, 42) &&+std.assertEqual(42 > 40 + 2, false) &&+std.assertEqual(42 >= 40 + 2, true) &&+std.assertEqual(42 < 40 + 2, false) &&+std.assertEqual(42 <= 40 + 2, true) &&+std.assertEqual(1 + 1 == 2, true) &&+std.assertEqual(1 | 3 & 6, 3) &&+std.assertEqual(1 | 3 & 6 ^ 2, 1) &&+std.assertEqual(false && true || false, false) &&+std.assertEqual(!false && true || false, true) &&+std.assertEqual(true && false || false, false) &&+std.assertEqual(if true then 1 else 2 + 3, 1) &&+std.assertEqual(':' + if true then 'foo' else error 'bar' + 'baz', ':foo') &&+std.assertEqual(':' + if true then 'foo' else function() 'bar' + 'baz', ':foo') &&+std.assertEqual(20 * local x = 6; x + 4, 200) &&++true
+ test/golden/recursive-functions.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/recursive-functions.jsonnet view
@@ -0,0 +1,49 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local fibonacci(n) =+ if n <= 1 then+ 1+ else+ fibonacci(n - 1) + fibonacci(n - 2);++std.assertEqual(fibonacci(15), 987) &&++# mutual recursion+local is_even = function(n) if n == 0 then true else is_odd(n - 1),+ is_odd = function(n) if n == 1 then true else is_even(n - 1);++std.assertEqual(is_even(42), true) &&+std.assertEqual(is_odd(41), true) &&++# lazy version of fix+local fix(f) = local x = f(x); x;++local factorial(n) =+ local step(rec) =+ function(n) if n == 0 then 1 else n * rec(n-1);+ fix(step)(n);++std.assertEqual(factorial(5), 120) &&++# strict version of fix+local fix_(f) = f(fix_(f));++local factorial_(n) =+ local step(rec) =+ function(n) if n == 0 then 1 else n * rec(n-1);+ fix_(step)(n);++std.assertEqual(factorial_(5), 120) &&++true
+ test/golden/slice.sugar.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/slice.sugar.jsonnet view
@@ -0,0 +1,178 @@+/*+Copyright 2015 Google Inc. All rights reserved.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++local arr = std.range(0, 5);+local str = '012345';+local a = 2;+local b = 4;++local arrCases = [+ {+ input: arr[2:4],+ output: [2, 3],+ },+ {+ input: arr[2:4:1],+ output: [2, 3],+ },+ {+ input: arr[:4],+ output: [0, 1, 2, 3],+ },+ {+ input: arr[2:],+ output: [2, 3, 4, 5],+ },+ {+ input: arr[:],+ output: arr,+ },+ {+ input: arr[:1000],+ output: arr,+ },+ {+ input: arr[::],+ output: arr,+ },+ {+ input: arr[1::],+ output: [1, 2, 3, 4, 5],+ },+ {+ input: arr[(1)::],+ output: [1, 2, 3, 4, 5],+ },+ {+ input: arr[1::2],+ output: [1, 3, 5],+ },+ {+ input: arr[(1)::2],+ output: [1, 3, 5],+ },+ {+ input: arr[::2],+ output: [0, 2, 4],+ },+ {+ input: arr[:0:],+ output: [],+ },+ {+ input: arr[:(0):],+ output: [],+ },+ {+ input: arr[a:b],+ output: [2, 3 ],+ },+ {+ input: arr[a:b + 1],+ output: [2, 3, 4],+ },+ {+ input: arr[2:1000],+ output: [2, 3, 4, 5],+ },+ {+ input: (arr)[2:1000],+ output: [2, 3, 4, 5],+ },++];++local strCases = [+ {+ input: str[2:4],+ output: '23',+ },+ {+ input: str[2:4:1],+ output: '23',+ },+ {+ input: str[:4],+ output: '0123',+ },+ {+ input: str[2:],+ output: '2345',+ },+ {+ input: str[:],+ output: str,+ },+ {+ input: str[:1000],+ output: str,+ },+ {+ input: str[::],+ output: str,+ },+ {+ input: str[1::],+ output: '12345',+ },+ {+ input: str[(1)::],+ output: '12345',+ },+ {+ input: str[1::2],+ output: '135',+ },+ {+ input: str[(1)::2],+ output: '135',+ },+ {+ input: str[::2],+ output: '024',+ },+ {+ input: str[:0:],+ output: '',+ },+ {+ input: str[:(0):],+ output: '',+ },+ {+ input: str[a:b],+ output: '23',+ },+ {+ input: str[a:b + 1],+ output: '234',+ },+ {+ input: str[2:1000],+ output: '2345',+ },+ {+ input: (str)[2:1000],+ output: '2345',+ },+];++std.foldl(+ function(bool, case)+ bool && std.assertEqual(case.input, case.output),+ arrCases + strCases,+ true,+)
@@ -0,0 +1,1 @@+true
@@ -0,0 +1,14 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual(std, {})
+ test/golden/stdlib.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/stdlib.jsonnet view
@@ -0,0 +1,1011 @@+/*+Copyright 2015 Google Inc. All rights reserved.++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++// This file tests functions from the standard library (std.jsonnet and builtins).++// Can capture std from another file.+std.assertEqual((import 'lib/capture_std_func.libsonnet')().sqrt(4), 2) &&++// Each import has its own std. (not implemented in haskell-jsonnet)+std.assertEqual(+ //local std = { sqrt: function(x) x };+ local lib = import 'lib/capture_std.libsonnet';+ lib.sqrt(4),+ 2+) &&++// Now, test each std library function in turn.++std.assertEqual(std.makeArray(10, function(i) i + 1), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) &&+std.assertEqual(std.makeArray(0, function(i) null), []) &&++local assertClose(a, b) =+ local err =+ if b == 0 then+ a - b+ else+ if a / b - 1 > 0 then a / b - 1 else 1 - a / b;+ if err > 0.000005 then+ error 'Assertion failed (error ' + err + '). ' + a + ' !~ ' + b+ else+ true;++std.assertEqual(std.pow(3, 2), 9) &&+std.assertEqual(std.floor(10), 10) &&+std.assertEqual(std.floor(10.99999), 10) &&+std.assertEqual(std.ceil(10), 10) &&+std.assertEqual(std.ceil(10.99999), 11) &&+std.assertEqual(std.sqrt(0), 0) &&+std.assertEqual(std.sqrt(1), 1) &&+std.assertEqual(std.sqrt(9), 3) &&+std.assertEqual(std.sqrt(16), 4) &&+std.assertEqual(std.abs(33), 33) &&+std.assertEqual(std.abs(-33), 33) &&+std.assertEqual(std.abs(0), 0) &&++// Ordinary (non-test) code can define pi as 2*std.acos(0)+local pi = 3.14159265359;++assertClose(std.sin(0.0 * pi), 0) &&+assertClose(std.sin(0.5 * pi), 1) &&+assertClose(std.sin(1.0 * pi), 0) &&+assertClose(std.sin(1.5 * pi), -1) &&+assertClose(std.sin(2.0 * pi), 0) &&+assertClose(std.cos(0.0 * pi), 1) &&+assertClose(std.cos(0.5 * pi), 0) &&+assertClose(std.cos(1.0 * pi), -1) &&+assertClose(std.cos(1.5 * pi), 0) &&+assertClose(std.cos(2.0 * pi), 1) &&+assertClose(std.tan(0), 0) &&+assertClose(std.tan(0.25 * pi), 1) &&+assertClose(std.asin(0), 0) &&+assertClose(std.acos(1), 0) &&+assertClose(std.asin(1), 0.5 * pi) &&+assertClose(std.acos(0), 0.5 * pi) &&+assertClose(std.atan(0), 0) &&+assertClose(std.log(std.exp(5)), 5) &&+assertClose(std.mantissa(1), 0.5) &&+assertClose(std.exponent(1), 1) &&+assertClose(std.mantissa(128), 0.5) &&+assertClose(std.exponent(128), 8) &&++std.assertEqual(std.clamp(-3, 0, 5), 0) &&+std.assertEqual(std.clamp(4, 0, 5), 4) &&+std.assertEqual(std.clamp(7, 0, 5), 5) &&++std.assertEqual(std.type(null), 'null') &&+std.assertEqual(std.type(true), 'boolean') &&+std.assertEqual(std.type(false), 'boolean') &&+std.assertEqual(std.type(0), 'number') &&+std.assertEqual(std.type(-1e10), 'number') &&+std.assertEqual(std.type([1, 2, 3]), 'array') &&+std.assertEqual(std.type([]), 'array') &&+std.assertEqual(std.type(function(x) x), 'function') &&+std.assertEqual(std.type({ x: 1, y: 2 }), 'object') &&+std.assertEqual(std.type({}), 'object') &&+std.assertEqual(std.type('fail'), 'string') &&+std.assertEqual(std.type('' + {}), 'string') &&++std.assertEqual(std.isString(''), true) &&+std.assertEqual(std.isBoolean(true), true) &&+std.assertEqual(std.isNumber(0), true) &&+std.assertEqual(std.isObject({}), true) &&+std.assertEqual(std.isArray([]), true) &&+std.assertEqual(std.isFunction(function() 0), true) &&++std.assertEqual(std.isString(null), false) &&+std.assertEqual(std.isBoolean(null), false) &&+std.assertEqual(std.isNumber(null), false) &&+std.assertEqual(std.isObject(null), false) &&+std.assertEqual(std.isArray(null), false) &&+std.assertEqual(std.isFunction(null), false) &&++std.assertEqual(std.member('foo', 'o'), true) &&+std.assertEqual(std.member('foo', 'f'), true) &&+std.assertEqual(std.member('foo', 'x'), false) &&+std.assertEqual(std.member([], 'o'), false) &&+std.assertEqual(std.member(['f'], 'o'), false) &&+std.assertEqual(std.member(['f', 'o', 'o'], 'o'), true) &&+std.assertEqual(std.member(['f', 'o', 'o'], 'f'), true) &&+std.assertEqual(std.member(['f', 'o', 'o'], 'g'), false) &&++std.assertEqual(std.count([true, false, false, true, true, true, false], true), 4) &&+std.assertEqual(std.count([true, false, false, true, true, true, false], false), 3) &&++std.assertEqual(std.filter(function(x) x % 2 == 0, [1, 2, 3, 4]), [2, 4]) &&+std.assertEqual(std.filter(function(x) false, [1, 2, 3, 4]), []) &&+std.assertEqual(std.filter(function(x) x, []), []) &&++std.assertEqual(std.objectHas({ x: 1, y: 2 }, 'x'), true) &&+std.assertEqual(std.objectHas({ x: 1, y: 2 }, 'z'), false) &&+std.assertEqual(std.objectHas({}, 'z'), false) &&++std.assertEqual(std.length('asdfasdf'), 8) &&+std.assertEqual(std.length([1, 4, 9, error 'foo']), 4) &&+std.assertEqual(std.length(function(x, y, z) error 'foo'), 3) &&+std.assertEqual(std.length({ x: 1, y: 2 }), 2) &&+std.assertEqual(std.length({ a: 1, b: 2, c: 0 } + { c: 3, d: error 'foo' }), 4) &&+std.assertEqual(std.length(''), 0) &&+std.assertEqual(std.length([]), 0) &&+std.assertEqual(std.length(function() error 'foo'), 0) &&+std.assertEqual(std.length({}), 0) &&++std.assertEqual(std.objectFields({}), []) &&+std.assertEqual(std.objectFields({ x: 1, y: 2 }), ['x', 'y']) &&+std.assertEqual(std.objectFields({ a: 1, b: 2, c: null, d: error 'foo' }), ['a', 'b', 'c', 'd']) &&+std.assertEqual(std.objectFields({ x: 1 } { x: 1 }), ['x']) &&+std.assertEqual(std.objectFields({ x: 1 } { x:: 1 }), []) &&+std.assertEqual(std.objectFields({ x: 1 } { x::: 1 }), ['x']) &&+std.assertEqual(std.objectFields({ x:: 1 } { x: 1 }), []) &&+std.assertEqual(std.objectFields({ x:: 1 } { x:: 1 }), []) &&+std.assertEqual(std.objectFields({ x:: 1 } { x::: 1 }), ['x']) &&+std.assertEqual(std.objectFields({ x::: 1 } { x: 1 }), ['x']) &&+std.assertEqual(std.objectFields({ x::: 1 } { x:: 1 }), []) &&+std.assertEqual(std.objectFields({ x::: 1 } { x::: 1 }), ['x']) &&++std.assertEqual(std.objectValues({}), []) &&+std.assertEqual(std.objectValues({ x: 1, y: 2 }), [1, 2]) &&+std.assertEqual(std.objectValues({ x: 1 } { x: 1 }), [1]) &&+std.assertEqual(std.objectValues({ x: 1 } { x:: 1 }), []) &&+std.assertEqual(std.objectValues({ x: 1 } { x::: 1 }), [1]) &&+std.assertEqual(std.objectValues({ x:: 1 } { x: 1 }), []) &&+std.assertEqual(std.objectValues({ x:: 1 } { x:: 1 }), []) &&+std.assertEqual(std.objectValues({ x:: 1 } { x::: 1 }), [1]) &&+std.assertEqual(std.objectValues({ x::: 1 } { x: 1 }), [1]) &&+std.assertEqual(std.objectValues({ x::: 1 } { x:: 1 }), []) &&+std.assertEqual(std.objectValues({ x::: 1 } { x::: 1 }), [1]) &&+++//std.assertEqual(std.toString({ a: 1, b: 2 }), '{"b":2,"a":1}') &&+std.assertEqual(std.toString({}), '{}') &&+std.assertEqual(std.toString([1, 2]), '[1,2]') &&+std.assertEqual(std.toString([]), '[]') &&+std.assertEqual(std.toString(null), 'null') &&+std.assertEqual(std.toString(true), 'true') &&+std.assertEqual(std.toString(false), 'false') &&+std.assertEqual(std.toString('str'), 'str') &&+std.assertEqual(std.toString(''), '') &&+std.assertEqual(std.toString([1, 2, 'foo']), '[1,2,"foo"]') &&++std.assertEqual(std.substr('cookie', 1, 3), 'ook') &&+std.assertEqual(std.substr('cookie', 1, 0), '') &&+std.assertEqual(std.substr('cookie', 1, 15), 'ookie') &&+std.assertEqual(std.substr('cookie', 0, 5), 'cooki') &&+std.assertEqual(std.substr('cookie', 0, 6), 'cookie') &&+std.assertEqual(std.substr('cookie', 0, 15), 'cookie') &&+std.assertEqual(std.substr('cookie', 3, 1), 'k') &&+std.assertEqual(std.substr('cookie', 20, 1), '') &&+std.assertEqual(std.substr('cookie', 6, 1), '') &&+++std.assertEqual(std.substr('ąę', 1, 1), 'ę') &&++std.assertEqual(std.startsWith('food', 'foo'), true) &&+std.assertEqual(std.startsWith('food', 'food'), true) &&+std.assertEqual(std.startsWith('food', 'foody'), false) &&+std.assertEqual(std.startsWith('food', 'wat'), false) &&++std.assertEqual(std.endsWith('food', 'ood'), true) &&+std.assertEqual(std.endsWith('food', 'food'), true) &&+std.assertEqual(std.endsWith('food', 'omgfood'), false) &&+std.assertEqual(std.endsWith('food', 'wat'), false) &&++std.assertEqual(std.stripChars(' test test test ', ' '), 'test test test') &&+std.assertEqual(std.stripChars('aaabbbbcccc', 'ac'), 'bbbb') &&+std.assertEqual(std.stripChars('cacabbbbaacc', 'ac'), 'bbbb') &&+std.assertEqual(std.stripChars('', 'ac'), '') &&++std.assertEqual(std.lstripChars(' test test test ', ' '), 'test test test ') &&+std.assertEqual(std.lstripChars('aaabbbbcccc', 'ac'), 'bbbbcccc') &&+std.assertEqual(std.lstripChars('cacabbbbaacc', 'ac'), 'bbbbaacc') &&+std.assertEqual(std.lstripChars('', 'ac'), '') &&++std.assertEqual(std.rstripChars(' test test test ', ' '), ' test test test') &&+std.assertEqual(std.rstripChars('aaabbbbcccc', 'ac'), 'aaabbbb') &&+std.assertEqual(std.rstripChars('cacabbbbaacc', 'ac'), 'cacabbbb') &&+std.assertEqual(std.rstripChars('', 'ac'), '') &&++std.assertEqual(std.codepoint('a'), 97) &&+std.assertEqual(std.char(97), 'a') &&+std.assertEqual(std.codepoint('\u0000'), 0) &&+std.assertEqual(std.char(0), '\u0000') &&++std.assertEqual(std.strReplace('A cat walked by', 'cat', 'dog'), 'A dog walked by') &&+std.assertEqual(std.strReplace('cat', 'cat', 'dog'), 'dog') &&+std.assertEqual(std.strReplace('', 'cat', ''), '') &&+std.assertEqual(std.strReplace('xoxoxoxox', 'xoxox3xox', 'A'), 'xoxoxoxox') &&+std.assertEqual(std.strReplace('xoxoxox3xox', 'xoxox3xox', 'A'), 'xoA') &&+std.assertEqual(std.strReplace('A cat is a cat', 'cat', 'dog'), 'A dog is a dog') &&+std.assertEqual(std.strReplace('wishyfishyisishy', 'ish', 'and'), 'wandyfandyisandy') &&++std.assertEqual(std.map(function(x) x * x, []), []) &&+std.assertEqual(std.map(function(x) x * x, [1, 2, 3, 4]), [1, 4, 9, 16]) &&+std.assertEqual(std.map(function(x) x * x, std.filter(function(x) x > 5, std.range(1, 10))), [36, 49, 64, 81, 100]) &&++std.assertEqual(std.mapWithIndex(function(i, x) x * i, []), []) &&+std.assertEqual(std.mapWithIndex(function(i, x) x * i, [1, 2, 3, 4]), [0, 2, 6, 12]) &&+std.assertEqual(std.mapWithIndex(function(i, x) x * i, std.filter(function(x) x > 5, std.range(1, 10))), [0, 7, 16, 27, 40]) &&++std.assertEqual(std.mapWithKey(function(k, o) k + o, {}), {}) &&+std.assertEqual(std.mapWithKey(function(k, o) k + o, { a: 1, b: 2 }), { a: 'a1', b: 'b2' }) &&++std.assertEqual(std.flatMap(function(x) [x, x], [1, 2, 3]), [1, 1, 2, 2, 3, 3]) &&+std.assertEqual(std.flatMap(function(x) if x == 2 then [] else [x], [1, 2, 3]), [1, 3]) &&+std.assertEqual(std.flatMap(function(x) if x == 2 then [] else [x * 3, x * 2], [1, 2, 3]), [3, 2, 9, 6]) &&++std.assertEqual(std.filterMap(function(x) x >= 0, function(x) x * x, [-3, -2, -1, 0, 1, 2, 3]), [0, 1, 4, 9]) &&++std.assertEqual(std.foldl(function(x, y) [x, y], [], 'foo'), 'foo') &&+std.assertEqual(std.foldl(function(x, y) [x, y], [1, 2, 3, 4], []), [[[[[], 1], 2], 3], 4]) &&++std.assertEqual(std.foldr(function(x, y) [x, y], [], 'bar'), 'bar') &&+std.assertEqual(std.foldr(function(x, y) [x, y], [1, 2, 3, 4], []), [1, [2, [3, [4, []]]]]) &&++std.assertEqual(std.range(2, 6), [2, 3, 4, 5, 6]) &&+std.assertEqual(std.range(2, 2), [2]) &&+std.assertEqual(std.range(2, 1), []) &&++std.assertEqual(std.repeat([], 0), []) &&+std.assertEqual(std.repeat([1], 1), [1]) &&+std.assertEqual(std.repeat([1, 2], 1), [1, 2]) &&+std.assertEqual(std.repeat([1], 2), [1, 1]) &&+std.assertEqual(std.repeat([1, 2], 2), [1, 2, 1, 2]) &&+std.assertEqual(std.repeat('a', 1), 'a') &&+std.assertEqual(std.repeat('a', 4), 'aaaa') &&+std.assertEqual(std.repeat('ab', 4), 'abababab') &&+std.assertEqual(std.repeat('a', 0), '') &&++std.assertEqual(std.join([], [[1, 2], [3, 4, 5], [6]]), [1, 2, 3, 4, 5, 6]) &&+std.assertEqual(std.join(['a', 'b'], [[]]), []) &&+std.assertEqual(std.join(['a', 'b'], []), []) &&+std.assertEqual(std.join(['a', 'b'], [null, [1, 2], null, [3, 4, 5], [6], null]), [1, 2, 'a', 'b', 3, 4, 5, 'a', 'b', 6]) &&+std.assertEqual(std.join(['a', 'b'], [[], [1, 2]]), ['a', 'b', 1, 2]) &&+std.assertEqual(std.join('', [null, '12', null, '345', '6', null]), '123456') &&+std.assertEqual(std.join('ab', ['']), '') &&+std.assertEqual(std.join('ab', []), '') &&+std.assertEqual(std.join('ab', [null, '12', null, '345', '6', null]), '12ab345ab6') &&+std.assertEqual(std.join('ab', ['', '12']), 'ab12') &&+std.assertEqual(std.lines(['a', null, 'b']), 'a\nb\n') &&++std.assertEqual(std.flattenArrays([[1, 2, 3], [4, 5, 6], []]), [1, 2, 3, 4, 5, 6]) &&++std.assertEqual(+ std.manifestIni({+ main: { a: '1', b: '2' },+ sections: {+ s1: { x: '11', y: '22', z: '33' },+ s2: { p: 'yes', q: '' },+ empty: {},+ },+ }),+ 'a = 1\nb = 2\n[empty]\n[s1]\nx = 11\ny = 22\nz = 33\n[s2]\np = yes\nq = \n'+) &&++std.assertEqual(+ std.manifestIni({+ sections: {+ s1: { x: '11', y: '22', z: '33' },+ s2: { p: 'yes', q: '' },+ empty: {},+ },+ }),+ '[empty]\n[s1]\nx = 11\ny = 22\nz = 33\n[s2]\np = yes\nq = \n'+) &&++std.assertEqual(+ std.manifestIni({+ main: { a: ['1', '2'] },+ sections: {+ s2: { p: ['yes', ''] },+ },+ }), 'a = 1\na = 2\n[s2]\np = yes\np = \n'+) &&+++std.assertEqual(std.escapeStringJson('hello'), '"hello"') &&+std.assertEqual(std.escapeStringJson('he"llo'), '"he\\"llo"') &&+std.assertEqual(std.escapeStringJson('he"llo'), '"he\\"llo"') &&+std.assertEqual(std.escapeStringBash("he\"l'lo"), "'he\"l'\"'\"'lo'") &&+std.assertEqual(std.escapeStringDollars('The path is ${PATH}.'), 'The path is $${PATH}.') &&+std.assertEqual(std.escapeStringJson('!~'), '"!~"') &&++std.assertEqual(std.manifestPython({+ x: 'test',+ y: [],+ z: ['foo', 'bar'],+ n: 1,+ a: true,+ b: false,+ c: null,+ o: { f1: 'foo', f2: 'bar' },+}), '{%s}' % std.join(', ', [+ '"a": True',+ '"b": False',+ '"c": None',+ '"n": 1',+ '"o": {"f1": "foo", "f2": "bar"}',+ '"x": "test"',+ '"y": []',+ '"z": ["foo", "bar"]',+])) &&++std.assertEqual(std.manifestPythonVars({+ x: 'test',+ y: [],+ z: ['foo', 'bar'],+ n: 1,+ a: true,+ b: false,+ c: null,+ o: { f1: 'foo', f2: 'bar' },+}), std.join('\n', [+ 'a = True',+ 'b = False',+ 'c = None',+ 'n = 1',+ 'o = {"f1": "foo", "f2": "bar"}',+ 'x = "test"',+ 'y = []',+ 'z = ["foo", "bar"]',+ '',+])) &&++std.assertEqual(+ std.manifestXmlJsonml(+ ['f', {}, ' ', ['g', 'inside'], 'nope', ['h', { attr: 'yolo' }, ['x', { attr: 'otter' }]]]+ ),+ '<f> <g>inside</g>nope<h attr="yolo"><x attr="otter"></x></h></f>'+) &&+++std.assertEqual(std.base64('Hello World!'), 'SGVsbG8gV29ybGQh') &&+std.assertEqual(std.base64('Hello World'), 'SGVsbG8gV29ybGQ=') &&+std.assertEqual(std.base64('Hello Worl'), 'SGVsbG8gV29ybA==') &&+std.assertEqual(std.base64(''), '') &&++std.assertEqual(std.base64Decode('SGVsbG8gV29ybGQh'), 'Hello World!') &&+std.assertEqual(std.base64Decode('SGVsbG8gV29ybGQ='), 'Hello World') &&+std.assertEqual(std.base64Decode('SGVsbG8gV29ybA=='), 'Hello Worl') &&+std.assertEqual(std.base64Decode(''), '') &&++std.assertEqual(std.reverse([]), []) &&+std.assertEqual(std.reverse([1]), [1]) &&+std.assertEqual(std.reverse([1, 2]), [2, 1]) &&+std.assertEqual(std.reverse([1, 2, 3]), [3, 2, 1]) &&+std.assertEqual(std.reverse([[1, 2, 3]]), [[1, 2, 3]]) &&++//(+// if std.thisFile == '<stdin>' then+// // This happens when testing the unparser.+// true+// else+// std.assertEqual(std.thisFile, 'stdlib.jsonnet')+//) &&++std.assertEqual(std.extVar('var1'), 'test') &&++std.assertEqual(std.extVar('var2'), { x: 1, y: 2 }) &&+// std.assertEqual(std.toString(std.extVar('var2')), '{"x": 1, "y": 2}') &&+// std.assertEqual(std.extVar('var2') { x+: 2 }.x, 3) &&++local some_toml = {+ key: 'value',+ simple: { t: 5 },+ section: {+ a: 1,+ nested: { b: 2 },+ 'e$caped': { q: 't' },+ array: [+ { c: 3 },+ { d: 4 },+ ],+ nestedArray: [{+ k: 'v',+ nested: { e: 5 },+ }],+ },+ arraySection: [+ { q: 1 },+ { w: 2 },+ ],+ 'escaped"Section': { z: 'q' },+ emptySection: {},+ emptyArraySection: [{}],+ bool: true,+ notBool: false,+ number: 7,+ array: ['s', 1, [2, 3], { r: 6, a: ['0', 'z'] }],+ emptyArray: [],+ '"': 4,+};++//std.assertEqual(+// std.manifestTomlEx(some_toml, ' ') + '\n',+// |||+// "\"" = 4+// array = [+// "s",+// 1,+// [ 2, 3 ],+// { a = [ "0", "z" ], r = 6 }+// ]+// bool = true+// emptyArray = []+// key = "value"+// notBool = false+// number = 7+//+// [[arraySection]]+// q = 1+//+// [[arraySection]]+// w = 2+//+// [[emptyArraySection]]+//+// [emptySection]+//+// ["escaped\"Section"]+// z = "q"+//+// [section]+// a = 1+//+// [[section.array]]+// c = 3+//+// [[section.array]]+// d = 4+//+// [section."e$caped"]+// q = "t"+//+// [section.nested]+// b = 2+//+// [[section.nestedArray]]+// k = "v"+//+// [section.nestedArray.nested]+// e = 5+//+// [simple]+// t = 5+// |||+//) &&++local some_json = {+ x: [1, 2, 3, true, false, null, 'string\nstring\n'],+ arr: [[[]]],+ y: { a: 1, b: 2, c: [1, 2] },+ emptyArray: [],+ emptyObject: {},+ objectInArray: [{ f: 3 }],+ '"': null,+};++std.assertEqual(+ std.manifestJsonEx(some_json, ' ') + '\n',+ |||+ {+ "\"": null,+ "arr": [+ [+ [++ ]+ ]+ ],+ "emptyArray": [++ ],+ "emptyObject": {++ },+ "objectInArray": [+ {+ "f": 3+ }+ ],+ "x": [+ 1,+ 2,+ 3,+ true,+ false,+ null,+ "string\nstring\n"+ ],+ "y": {+ "a": 1,+ "b": 2,+ "c": [+ 1,+ 2+ ]+ }+ }+ |||+) &&++std.assertEqual(+ std.manifestJsonEx(some_json, '', ' ', ' : '),+ '{ "\\"" : null, "arr" : [ [ [ ] ] ], "emptyArray" : [ ], '+ + '"emptyObject" : { }, "objectInArray" : [ { "f" : 3 } ], '+ + '"x" : [ 1, 2, 3, true, false, null, "string\\nstring\\n" ], '+ + '"y" : { "a" : 1, "b" : 2, "c" : [ 1, 2 ] } }'+) &&++std.assertEqual(+ std.manifestJsonMinified(some_json),+ '{"\\"":null,"arr":[[[]]],"emptyArray":[],"emptyObject":{},"objectInArray":[{"f":3}],'+ + '"x":[1,2,3,true,false,null,"string\\nstring\\n"],"y":{"a":1,"b":2,"c":[1,2]}}'+) &&++std.assertEqual(+ std.manifestYamlDoc([{ x: [1, 2, 3] }]) + '\n',+ |||+ - "x":+ - 1+ - 2+ - 3+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ x: [1, 2, 3] }) + '\n',+ |||+ "x":+ - 1+ - 2+ - 3+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc([[[1, 2], [3, 4]]]) + '\n',+ |||+ -+ -+ - 1+ - 2+ -+ - 3+ - 4+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ x: [[[1, [1], 1]]] }) + '\n',+ |||+ "x":+ -+ -+ - 1+ -+ - 1+ - 1+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ x: [[[1, { f: 3, g: [1, 2] }, 1]]] }) + '\n',+ |||+ "x":+ -+ -+ - 1+ - "f": 3+ "g":+ - 1+ - 2+ - 1+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc('hello\nworld\n') + '\n',+ |||+ |+ hello+ world+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc(['hello\nworld\n']) + '\n',+ |||+ - |+ hello+ world+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ f: 'hello\nworld\n' }) + '\n',+ |||+ "f": |+ hello+ world+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc(some_json) + '\n',+ |||+ "\"": null+ "arr":+ -+ - []+ "emptyArray": []+ "emptyObject": {}+ "objectInArray":+ - "f": 3+ "x":+ - 1+ - 2+ - 3+ - true+ - false+ - null+ - |+ string+ string+ "y":+ "a": 1+ "b": 2+ "c":+ - 1+ - 2+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc([{ x: [1, 2, 3] }], indent_array_in_object=true) + '\n',+ |||+ - "x":+ - 1+ - 2+ - 3+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ x: [1, 2, 3] }, indent_array_in_object=true) + '\n',+ |||+ "x":+ - 1+ - 2+ - 3+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc([[[1, 2], [3, 4]]], indent_array_in_object=true) + '\n',+ |||+ -+ -+ - 1+ - 2+ -+ - 3+ - 4+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ x: [[[1, [1], 1]]] }, indent_array_in_object=true) + '\n',+ |||+ "x":+ -+ -+ - 1+ -+ - 1+ - 1+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ x: [[[1, { f: 3, g: [1, 2] }, 1]]] }, indent_array_in_object=true) + '\n',+ |||+ "x":+ -+ -+ - 1+ - "f": 3+ "g":+ - 1+ - 2+ - 1+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc('hello\nworld\n', indent_array_in_object=true) + '\n',+ |||+ |+ hello+ world+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc(['hello\nworld\n'], indent_array_in_object=true) + '\n',+ |||+ - |+ hello+ world+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc({ f: 'hello\nworld\n' }, indent_array_in_object=true) + '\n',+ |||+ "f": |+ hello+ world+ |||+) &&++std.assertEqual(+ std.manifestYamlDoc(some_json, indent_array_in_object=true) + '\n',+ |||+ "\"": null+ "arr":+ -+ - []+ "emptyArray": []+ "emptyObject": {}+ "objectInArray":+ - "f": 3+ "x":+ - 1+ - 2+ - 3+ - true+ - false+ - null+ - |+ string+ string+ "y":+ "a": 1+ "b": 2+ "c":+ - 1+ - 2+ |||+) &&++std.assertEqual(+ std.manifestYamlStream([some_json, some_json, {}, [], 3, '"']),+ |||+ ---+ "\"": null+ "arr":+ -+ - []+ "emptyArray": []+ "emptyObject": {}+ "objectInArray":+ - "f": 3+ "x":+ - 1+ - 2+ - 3+ - true+ - false+ - null+ - |+ string+ string+ "y":+ "a": 1+ "b": 2+ "c":+ - 1+ - 2+ ---+ "\"": null+ "arr":+ -+ - []+ "emptyArray": []+ "emptyObject": {}+ "objectInArray":+ - "f": 3+ "x":+ - 1+ - 2+ - 3+ - true+ - false+ - null+ - |+ string+ string+ "y":+ "a": 1+ "b": 2+ "c":+ - 1+ - 2+ ---+ {}+ ---+ []+ ---+ 3+ ---+ "\""+ ...+ |||+) &&++std.assertEqual(+ std.manifestYamlStream([some_json, some_json, {}, [], 3, '"'], indent_array_in_object=true),+ |||+ ---+ "\"": null+ "arr":+ -+ - []+ "emptyArray": []+ "emptyObject": {}+ "objectInArray":+ - "f": 3+ "x":+ - 1+ - 2+ - 3+ - true+ - false+ - null+ - |+ string+ string+ "y":+ "a": 1+ "b": 2+ "c":+ - 1+ - 2+ ---+ "\"": null+ "arr":+ -+ - []+ "emptyArray": []+ "emptyObject": {}+ "objectInArray":+ - "f": 3+ "x":+ - 1+ - 2+ - 3+ - true+ - false+ - null+ - |+ string+ string+ "y":+ "a": 1+ "b": 2+ "c":+ - 1+ - 2+ ---+ {}+ ---+ []+ ---+ 3+ ---+ "\""+ ...+ |||+) &&++std.assertEqual(+ std.manifestYamlStream([{}, [], 3, '"'], c_document_end=false),+ |||+ ---+ {}+ ---+ []+ ---+ 3+ ---+ "\""+ |||+) &&++std.assertEqual(std.parseInt('01234567890'), 1234567890) &&+std.assertEqual(std.parseInt('-01234567890'), -1234567890) &&+std.assertEqual(std.parseOctal('755'), 493) &&+std.assertEqual(std.parseOctal('0755'), 493) &&+std.assertEqual(std.parseHex('ff'), 255) &&+std.assertEqual(std.parseHex('FF'), 255) &&+std.assertEqual(std.parseHex('0ff'), 255) &&+std.assertEqual(std.parseHex('0FF'), 255) &&+std.assertEqual(std.parseHex('a'), 10) &&+std.assertEqual(std.parseHex('A'), 10) &&+std.assertEqual(std.parseHex('4a'), 74) &&++// verified by running md5 -s value+//std.assertEqual(std.md5(''), 'd41d8cd98f00b204e9800998ecf8427e') &&+//std.assertEqual(std.md5('grape'), 'b781cbb29054db12f88f08c6e161c199') &&+//std.assertEqual(std.md5("{}[]01234567890\"'+=-_/<>?,.!@#$%^&*|\\:;`~"), 'a680db28332f0c9647376e5b2aeb4b3d') &&+//std.assertEqual(std.md5('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum si.'), '3496bb633e830e7679ce53700d42de1e') &&+std.assertEqual(std.parseInt('-01234567890'), -1234567890) &&++std.assertEqual(std.prune({}), {}) &&+std.assertEqual(std.prune([]), []) &&+std.assertEqual(std.prune(null), null) &&+std.assertEqual(std.prune({ a: [], b: {}, c: null }), {}) &&+std.assertEqual(std.prune([[], {}, null]), []) &&+std.assertEqual(std.prune({ a: [[], {}, null], b: { a: [], b: {}, c: null } }), {}) &&+std.assertEqual(std.prune([[[], {}, null], { a: [], b: {}, c: null }]), []) &&+std.assertEqual(std.prune({ a: [{ b: true }] }), { a: [{ b: true }] }) &&++std.assertEqual(std.parseJson('"foo"'), 'foo') &&+std.assertEqual(std.parseJson('{}'), {}) &&+std.assertEqual(std.parseJson('[]'), []) &&+std.assertEqual(std.parseJson('null'), null) &&+std.assertEqual(std.parseJson('12'), 12) &&+std.assertEqual(std.parseJson('12.123'), 12.123) &&+std.assertEqual(std.parseJson('{"a": {"b": ["c", 42]}}'), { a: { b: ['c', 42] } }) &&++std.assertEqual(std.asciiUpper('!@#$%&*()asdfghFGHJKL09876 '), '!@#$%&*()ASDFGHFGHJKL09876 ') &&+std.assertEqual(std.asciiLower('!@#$%&*()asdfghFGHJKL09876 '), '!@#$%&*()asdfghfghjkl09876 ') &&++std.assertEqual(std.deepJoin(['a', ['b', 'c', [[], 'd', ['e'], 'f', 'g'], [], []], 'h']),+ 'abcdefgh') &&++std.assertEqual(std.findSubstr('', 'a'), []) &&+std.assertEqual(std.findSubstr('aa', ''), []) &&+std.assertEqual(std.findSubstr('aa', 'bb'), []) &&+std.assertEqual(std.findSubstr('aa', 'a'), []) &&+std.assertEqual(std.findSubstr('aa', 'aa'), [0]) &&+std.assertEqual(std.findSubstr('aa', 'bbaabaaa'), [2, 5, 6]) &&++std.assertEqual(std.find(null, [null]), [0]) &&+std.assertEqual(std.find([], [[]]), [0]) &&+std.assertEqual(std.find({}, [{}]), [0]) &&+std.assertEqual(std.find('a', []), []) &&+std.assertEqual(std.find('a', ['b']), []) &&+std.assertEqual(std.find('a', ['a']), [0]) &&+std.assertEqual(std.find('a', ['a', ['a'], 'b', 'a']), [0, 3]) &&+std.assertEqual(std.find(['a'], [['a']]), [0]) &&++std.assertEqual(std.encodeUTF8(''), []) &&+std.assertEqual(std.encodeUTF8('A'), [65]) &&+std.assertEqual(std.encodeUTF8('AAA'), [65, 65, 65]) &&+std.assertEqual(std.encodeUTF8('§'), [194, 167]) &&+std.assertEqual(std.encodeUTF8('Zażółć gęślą jaźń'), [90, 97, 197, 188, 195, 179, 197, 130, 196, 135, 32, 103, 196, 153, 197, 155, 108, 196, 133, 32, 106, 97, 197, 186, 197, 132]) &&+std.assertEqual(std.encodeUTF8('😃'), [240, 159, 152, 131]) &&++std.assertEqual(std.decodeUTF8([]), '') &&+std.assertEqual(std.decodeUTF8([65]), 'A') &&+std.assertEqual(std.decodeUTF8([65, 65, 65]), 'AAA') &&+std.assertEqual(std.decodeUTF8([(function(x) 65)(42)]), 'A') &&+std.assertEqual(std.decodeUTF8([65 + 1 - 1]), 'A') &&+std.assertEqual(std.decodeUTF8([90, 97, 197, 188, 195, 179, 197, 130, 196, 135, 32, 103, 196, 153, 197, 155, 108, 196, 133, 32, 106, 97, 197, 186, 197, 132]), 'Zażółć gęślą jaźń') &&+std.assertEqual(std.decodeUTF8([240, 159, 152, 131]), '😃') &&++true
+ test/golden/unicode.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/unicode.jsonnet view
@@ -0,0 +1,36 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++std.assertEqual('Ā', 'Ā') &&++std.assertEqual(std.length('Ā'), 1) &&+std.assertEqual('Ā' + 'Ā', 'ĀĀ') &&++std.assertEqual('£7'[0], '£') &&+std.assertEqual('£7'[1], '7') &&++local test_korean = '안녕 세상아!'; // Hello world!+std.assertEqual(std.length(test_korean), 7) &&++local test_russian = 'ЁЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ ёйцукенгшщзхъфывапролджэячсмитьбю';+std.assertEqual(std.length(test_russian), 67) &&++local test_chinese = '肉'; // Meat.+std.assertEqual(std.length(test_chinese), 1) &&++std.assertEqual('Ā', 'Ā') &&++std.assertEqual(@"\u0100", '\\u0100') &&+std.assertEqual(@"Ā", 'Ā') &&++true
+ test/golden/unparsed.golden view
@@ -0,0 +1,17 @@+{+ "false": false,+ "lit_field1": 1,+ "lit_field2": 1,+ "neg_integer": -1029301293,+ "null": null,+ "number": 0.3333333333333333,+ "pos_integer": 13212381932,+ "small_number": 1.0e-14,+ "string": "'foo\n bar\n\n\"bar\u0005\"'\t P\u0008\u000c\r\\",+ "string2": "\"foo\n bar\n\n'bar\u0005\"'\t P\u0008\u000c\r\\",+ "string3": "\"foo\\n bar\\n\\n'bar\\u0005\\\"'\\t \\u0050\\b\\f\\r\\\\",+ "string4": "'foo\\n bar\\n\\n'bar\\u0005\"'\\t \\u0050\\b\\f\\r\\\\",+ "true": true,+ "with\"quote": "\"",+ "zero": 0+}
+ test/golden/unparsed.jsonnet view
@@ -0,0 +1,32 @@+/*+Copyright 2015 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/++{+ neg_integer: -1029301293,+ pos_integer: 13212381932,+ number: 1 / 3,+ small_number: 0.00000000000001,+ zero: 0,+ string: "'foo\n bar\n\n\"bar\u0005\"\'\t \u0050\b\f\r\\",+ string2: '"foo\n bar\n\n\'bar\u0005\"\'\t \u0050\b\f\r\\',+ string3: @'"foo\n bar\n\n''bar\u0005\"''\t \u0050\b\f\r\\',+ string4: @"'foo\n bar\n\n'bar\u0005""'\t \u0050\b\f\r\\",+ "lit_field1": 1,+ 'lit_field2': 1,+ "false": false,+ "true": true,+ "null": null,+ 'with"quote': '"',+ hidden_field:: null,+ ["hidden_field2"]:: null,+}
+ test/golden/verbatim-strings.golden view
@@ -0,0 +1,1 @@+true
+ test/golden/verbatim-strings.jsonnet view
@@ -0,0 +1,21 @@+/*+Copyright 2017 Google Inc. All rights reserved.+Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at+ http://www.apache.org/licenses/LICENSE-2.0+Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.+*/+++std.assertEqual(@"c:\negative""", 'c:\\negative"') &&+std.assertEqual(@'c:\negative''', "c:\\negative'") &&++std.assertEqual({ @"c:\negative""": 1 }, { 'c:\\negative"': 1 }) &&+std.assertEqual({ @'c:\negative''': 1 }, { "c:\\negative'": 1 }) &&++true