diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
 # haskell-jsonnet
 
 [![Actions Status](https://github.com/moleike/haskell-jsonnet/workflows/build/badge.svg)](https://github.com/moleike/haskell-jsonnet/actions)
-[![Hackage](https://img.shields.io/hackage/v/jsonnet?style=flat)](https://hackage.haskell.org/package/jsonnet)
-
+[![Hackage](https://img.shields.io/hackage/v/jsonnet.svg?logo=haskell)](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
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -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
diff --git a/benchmarks/Bench.hs b/benchmarks/Bench.hs
--- a/benchmarks/Bench.hs
+++ b/benchmarks/Bench.hs
@@ -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)
   |]
diff --git a/jsonnet.cabal b/jsonnet.cabal
--- a/jsonnet.cabal
+++ b/jsonnet.cabal
@@ -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"
diff --git a/src/Language/Jsonnet.hs b/src/Language/Jsonnet.hs
--- a/src/Language/Jsonnet.hs
+++ b/src/Language/Jsonnet.hs
@@ -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
diff --git a/src/Language/Jsonnet/Annotate.hs b/src/Language/Jsonnet/Annotate.hs
--- a/src/Language/Jsonnet/Annotate.hs
+++ b/src/Language/Jsonnet/Annotate.hs
@@ -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
diff --git a/src/Language/Jsonnet/Check.hs b/src/Language/Jsonnet/Check.hs
--- a/src/Language/Jsonnet/Check.hs
+++ b/src/Language/Jsonnet/Check.hs
@@ -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
diff --git a/src/Language/Jsonnet/Common.hs b/src/Language/Jsonnet/Common.hs
--- a/src/Language/Jsonnet/Common.hs
+++ b/src/Language/Jsonnet/Common.hs
@@ -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
diff --git a/src/Language/Jsonnet/Core.hs b/src/Language/Jsonnet/Core.hs
--- a/src/Language/Jsonnet/Core.hs
+++ b/src/Language/Jsonnet/Core.hs
@@ -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)
 
diff --git a/src/Language/Jsonnet/Desugar.hs b/src/Language/Jsonnet/Desugar.hs
--- a/src/Language/Jsonnet/Desugar.hs
+++ b/src/Language/Jsonnet/Desugar.hs
@@ -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
diff --git a/src/Language/Jsonnet/Error.hs b/src/Language/Jsonnet/Error.hs
--- a/src/Language/Jsonnet/Error.hs
+++ b/src/Language/Jsonnet/Error.hs
@@ -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)
diff --git a/src/Language/Jsonnet/Eval.hs b/src/Language/Jsonnet/Eval.hs
--- a/src/Language/Jsonnet/Eval.hs
+++ b/src/Language/Jsonnet/Eval.hs
@@ -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
diff --git a/src/Language/Jsonnet/Eval/Monad.hs b/src/Language/Jsonnet/Eval/Monad.hs
--- a/src/Language/Jsonnet/Eval/Monad.hs
+++ b/src/Language/Jsonnet/Eval/Monad.hs
@@ -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 -> []
diff --git a/src/Language/Jsonnet/Parser.hs b/src/Language/Jsonnet/Parser.hs
--- a/src/Language/Jsonnet/Parser.hs
+++ b/src/Language/Jsonnet/Parser.hs
@@ -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",
diff --git a/src/Language/Jsonnet/Parser/SrcSpan.hs b/src/Language/Jsonnet/Parser/SrcSpan.hs
--- a/src/Language/Jsonnet/Parser/SrcSpan.hs
+++ b/src/Language/Jsonnet/Parser/SrcSpan.hs
@@ -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
 
diff --git a/src/Language/Jsonnet/Pretty.hs b/src/Language/Jsonnet/Pretty.hs
--- a/src/Language/Jsonnet/Pretty.hs
+++ b/src/Language/Jsonnet/Pretty.hs
@@ -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]
diff --git a/src/Language/Jsonnet/Std/Lib.hs b/src/Language/Jsonnet/Std/Lib.hs
--- a/src/Language/Jsonnet/Std/Lib.hs
+++ b/src/Language/Jsonnet/Std/Lib.hs
@@ -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)
diff --git a/src/Language/Jsonnet/Std/TH.hs b/src/Language/Jsonnet/Std/TH.hs
--- a/src/Language/Jsonnet/Std/TH.hs
+++ b/src/Language/Jsonnet/Std/TH.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE QuasiQuotes #-}
-{-# LANGUAGE TemplateHaskell #-}
-
 -- |
 -- Module                  : Language.Jsonnet.Std.TH
 -- Copyright               : (c) 2020-2021 Alexandre Moreno
diff --git a/src/Language/Jsonnet/Syntax.hs b/src/Language/Jsonnet/Syntax.hs
--- a/src/Language/Jsonnet/Syntax.hs
+++ b/src/Language/Jsonnet/Syntax.hs
@@ -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
diff --git a/src/Language/Jsonnet/Syntax/Annotated.hs b/src/Language/Jsonnet/Syntax/Annotated.hs
--- a/src/Language/Jsonnet/Syntax/Annotated.hs
+++ b/src/Language/Jsonnet/Syntax/Annotated.hs
@@ -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)) =
diff --git a/src/Language/Jsonnet/TH.hs b/src/Language/Jsonnet/TH.hs
--- a/src/Language/Jsonnet/TH.hs
+++ b/src/Language/Jsonnet/TH.hs
@@ -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
diff --git a/src/Language/Jsonnet/Value.hs b/src/Language/Jsonnet/Value.hs
--- a/src/Language/Jsonnet/Value.hs
+++ b/src/Language/Jsonnet/Value.hs
@@ -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
   { -- |
diff --git a/stdlib/std.jsonnet b/stdlib/std.jsonnet
--- a/stdlib/std.jsonnet
+++ b/stdlib/std.jsonnet
@@ -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);
diff --git a/test/Language/Jsonnet/Test/Golden.hs b/test/Language/Jsonnet/Test/Golden.hs
new file mode 100644
--- /dev/null
+++ b/test/Language/Jsonnet/Test/Golden.hs
@@ -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"
+      ]
diff --git a/test/Language/Jsonnet/Test/Roundtrip.hs b/test/Language/Jsonnet/Test/Roundtrip.hs
new file mode 100644
--- /dev/null
+++ b/test/Language/Jsonnet/Test/Roundtrip.hs
@@ -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
+    ]
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -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'
+      ]
diff --git a/test/golden/Test.hs b/test/golden/Test.hs
deleted file mode 100644
--- a/test/golden/Test.hs
+++ /dev/null
@@ -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"
-      ]
diff --git a/test/golden/arith-float.golden b/test/golden/arith-float.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/arith-float.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/arith-float.jsonnet b/test/golden/arith-float.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/arith-float.jsonnet
@@ -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
diff --git a/test/golden/array.golden b/test/golden/array.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/array.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/array.jsonnet b/test/golden/array.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/array.jsonnet
@@ -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
diff --git a/test/golden/comments.golden b/test/golden/comments.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/comments.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/comments.jsonnet b/test/golden/comments.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/comments.jsonnet
@@ -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
diff --git a/test/golden/condition.golden b/test/golden/condition.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/condition.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/condition.jsonnet b/test/golden/condition.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/condition.jsonnet
@@ -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
diff --git a/test/golden/empty.golden b/test/golden/empty.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/empty.golden
@@ -0,0 +1,1 @@
+{}
diff --git a/test/golden/empty.jsonnet b/test/golden/empty.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/empty.jsonnet
@@ -0,0 +1,1 @@
+{ foo :: "bar" }
diff --git a/test/golden/error.01.golden b/test/golden/error.01.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.01.golden
@@ -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 
diff --git a/test/golden/error.01.jsonnet b/test/golden/error.01.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.01.jsonnet
@@ -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)
diff --git a/test/golden/error.array_index_string.golden b/test/golden/error.array_index_string.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.array_index_string.golden
@@ -0,0 +1,2 @@
+Runtime error: invalid index: array index was not integer
+  ./test/golden/error.array_index_string.jsonnet:14:1-15:1 
diff --git a/test/golden/error.array_index_string.jsonnet b/test/golden/error.array_index_string.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.array_index_string.jsonnet
@@ -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
diff --git a/test/golden/error.array_large_index.golden b/test/golden/error.array_large_index.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.array_large_index.golden
@@ -0,0 +1,2 @@
+Runtime error: index out of bounds: 18446744073709552000
+  ./test/golden/error.array_large_index.jsonnet:14:1-18 
diff --git a/test/golden/error.array_large_index.jsonnet b/test/golden/error.array_large_index.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.array_large_index.jsonnet
@@ -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)]
diff --git a/test/golden/error.assert.fail1.golden b/test/golden/error.assert.fail1.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.assert.fail1.golden
@@ -0,0 +1,2 @@
+Runtime error: x should be equal to y
+  ./test/golden/error.assert.fail1.jsonnet:17:1-20:1 
diff --git a/test/golden/error.assert.fail1.jsonnet b/test/golden/error.assert.fail1.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.assert.fail1.jsonnet
@@ -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
diff --git a/test/golden/error.assert.fail2.golden b/test/golden/error.assert.fail2.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.assert.fail2.golden
@@ -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 
diff --git a/test/golden/error.assert.fail2.jsonnet b/test/golden/error.assert.fail2.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.assert.fail2.jsonnet
@@ -0,0 +1,5 @@
+local subtract(a, b) =
+  assert a > b : 'a must be bigger than b';
+  a - b;
+
+subtract (0, 1)
diff --git a/test/golden/error.divide-zero.golden b/test/golden/error.divide-zero.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.divide-zero.golden
@@ -0,0 +1,2 @@
+Runtime error: divide by zero exception
+  ./test/golden/error.divide-zero.jsonnet:14:1-15:1 
diff --git a/test/golden/error.divide-zero.jsonnet b/test/golden/error.divide-zero.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.divide-zero.jsonnet
@@ -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
diff --git a/test/golden/error.duplicate_local_var.golden b/test/golden/error.duplicate_local_var.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.duplicate_local_var.golden
@@ -0,0 +1,2 @@
+Static error: duplicate local var 'a'
+  ./test/golden/error.duplicate_local_var.jsonnet:1:1-3:1
diff --git a/test/golden/error.duplicate_local_var.jsonnet b/test/golden/error.duplicate_local_var.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.duplicate_local_var.jsonnet
@@ -0,0 +1,2 @@
+local a = 1, a = 2;
+a
diff --git a/test/golden/error.field-not-exist.golden b/test/golden/error.field-not-exist.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.field-not-exist.golden
@@ -0,0 +1,2 @@
+Runtime error: no such key: y
+  ./test/golden/error.field-not-exist.jsonnet:14:1-15:1 
diff --git a/test/golden/error.field-not-exist.jsonnet b/test/golden/error.field-not-exist.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.field-not-exist.jsonnet
@@ -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
diff --git a/test/golden/error.function_arg_positional_after_named.golden b/test/golden/error.function_arg_positional_after_named.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_arg_positional_after_named.golden
@@ -0,0 +1,2 @@
+Static error: positional after named argument
+  ./test/golden/error.function_arg_positional_after_named.jsonnet:16:1-4
diff --git a/test/golden/error.function_arg_positional_after_named.jsonnet b/test/golden/error.function_arg_positional_after_named.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_arg_positional_after_named.jsonnet
@@ -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)
diff --git a/test/golden/error.function_duplicate_param.golden b/test/golden/error.function_duplicate_param.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_duplicate_param.golden
@@ -0,0 +1,2 @@
+Parse error: foo.bar (No such file or directory)
+    ./test/golden/error.function_duplicate_param.jsonnet:14:13-29
diff --git a/test/golden/error.function_duplicate_param.jsonnet b/test/golden/error.function_duplicate_param.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_duplicate_param.jsonnet
@@ -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
diff --git a/test/golden/error.function_no_such_param.golden b/test/golden/error.function_no_such_param.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_no_such_param.golden
@@ -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 
diff --git a/test/golden/error.function_no_such_param.jsonnet b/test/golden/error.function_no_such_param.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_no_such_param.jsonnet
@@ -0,0 +1,2 @@
+local id(a) = a;
+id("foo", b = 2)
diff --git a/test/golden/error.function_too_many_args.golden b/test/golden/error.function_too_many_args.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_too_many_args.golden
@@ -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 
diff --git a/test/golden/error.function_too_many_args.jsonnet b/test/golden/error.function_too_many_args.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.function_too_many_args.jsonnet
@@ -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)
diff --git a/test/golden/error.index-out-of-bounds.golden b/test/golden/error.index-out-of-bounds.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.index-out-of-bounds.golden
@@ -0,0 +1,2 @@
+Runtime error: index out of bounds: 100
+  ./test/golden/error.index-out-of-bounds.jsonnet:3:10-17 
diff --git a/test/golden/error.index-out-of-bounds.jsonnet b/test/golden/error.index-out-of-bounds.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.index-out-of-bounds.jsonnet
@@ -0,0 +1,3 @@
+local arr = [1, 2, 3];
+
+arr[0] + arr[100]
diff --git a/test/golden/error.invalid-key.golden b/test/golden/error.invalid-key.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.invalid-key.golden
@@ -0,0 +1,2 @@
+Runtime error: type mismatch: expected string but got number
+  ./test/golden/error.invalid-key.jsonnet:1:1-6:1 
diff --git a/test/golden/error.invalid-key.jsonnet b/test/golden/error.invalid-key.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.invalid-key.jsonnet
@@ -0,0 +1,5 @@
+{
+  [null] : "null",
+  [3 + "3"] : "3 + 3",
+  [3 % 3]: function(x) x
+}
diff --git a/test/golden/error.sanity.golden b/test/golden/error.sanity.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/error.sanity.golden
@@ -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 
diff --git a/test/golden/error.sanity.jsonnet b/test/golden/error.sanity.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/error.sanity.jsonnet
@@ -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)
diff --git a/test/golden/format.golden b/test/golden/format.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/format.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/format.jsonnet b/test/golden/format.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/format.jsonnet
@@ -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
diff --git a/test/golden/functions.golden b/test/golden/functions.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/functions.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/functions.jsonnet b/test/golden/functions.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/functions.jsonnet
@@ -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
diff --git a/test/golden/hello-world.golden b/test/golden/hello-world.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/hello-world.golden
@@ -0,0 +1,3 @@
+{
+    "message": "Hello, world"
+}
diff --git a/test/golden/hello-world.jsonnet b/test/golden/hello-world.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/hello-world.jsonnet
@@ -0,0 +1,4 @@
+{
+  message: self.hello("world"),
+  hello(world):: "Hello, " + world
+}
diff --git a/test/golden/hidden.golden b/test/golden/hidden.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/hidden.golden
@@ -0,0 +1,5 @@
+{
+    "voice": {
+        "last_name": "Bon Jovi"
+    }
+}
diff --git a/test/golden/hidden.jsonnet b/test/golden/hidden.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/hidden.jsonnet
@@ -0,0 +1,10 @@
+{
+  voice: {
+    name :: "John",
+    last_name: "Bon Jovi"
+  },
+  guitar :: {
+    name: "Richie",
+    last_name: "Sambora"
+  }
+}
diff --git a/test/golden/importbin_nonutf8.golden b/test/golden/importbin_nonutf8.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/importbin_nonutf8.golden
@@ -0,0 +1,5 @@
+[
+    255,
+    0,
+    254
+]
diff --git a/test/golden/importbin_nonutf8.jsonnet b/test/golden/importbin_nonutf8.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/importbin_nonutf8.jsonnet
@@ -0,0 +1,1 @@
+importbin "nonutf8.bin"
diff --git a/test/golden/imports.golden b/test/golden/imports.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/imports.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/imports.jsonnet b/test/golden/imports.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/imports.jsonnet
@@ -0,0 +1,6 @@
+
+local imports = import "imports.libsonnet";
+
+std.assertEqual(imports.foo, 'bar') &&
+
+true
diff --git a/test/golden/imports.libsonnet b/test/golden/imports.libsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/imports.libsonnet
@@ -0,0 +1,4 @@
+{
+  foo: 'bar',
+  bar: 'baz'
+}
diff --git a/test/golden/importstr-nonexistent.golden b/test/golden/importstr-nonexistent.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/importstr-nonexistent.golden
@@ -0,0 +1,2 @@
+Parse error: this file does not exist (No such file or directory)
+    ./test/golden/importstr-nonexistent.jsonnet:1:1-37
diff --git a/test/golden/importstr-nonexistent.jsonnet b/test/golden/importstr-nonexistent.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/importstr-nonexistent.jsonnet
@@ -0,0 +1,1 @@
+importstr "this file does not exist"
diff --git a/test/golden/importstr-twice.golden b/test/golden/importstr-twice.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/importstr-twice.golden
@@ -0,0 +1,1 @@
+"{ foo :: \"bar\" }\n{ foo :: \"bar\" }\n"
diff --git a/test/golden/importstr-twice.jsonnet b/test/golden/importstr-twice.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/importstr-twice.jsonnet
@@ -0,0 +1,3 @@
+local x = importstr "empty.jsonnet";
+local y = importstr "empty.jsonnet";
+x + y
diff --git a/test/golden/importstr.golden b/test/golden/importstr.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/importstr.golden
@@ -0,0 +1,1 @@
+"{ foo :: \"bar\" }\n"
diff --git a/test/golden/importstr.jsonnet b/test/golden/importstr.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/importstr.jsonnet
@@ -0,0 +1,1 @@
+importstr "empty.jsonnet"
diff --git a/test/golden/lazy.golden b/test/golden/lazy.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/lazy.golden
@@ -0,0 +1,1 @@
+0
diff --git a/test/golden/lazy.jsonnet b/test/golden/lazy.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/lazy.jsonnet
@@ -0,0 +1,4 @@
+local x = error "foo", c = false;
+if c
+  then x
+  else 0
diff --git a/test/golden/lib/capture_std.libsonnet b/test/golden/lib/capture_std.libsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/lib/capture_std.libsonnet
@@ -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
diff --git a/test/golden/lib/capture_std_func.libsonnet b/test/golden/lib/capture_std_func.libsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/lib/capture_std_func.libsonnet
@@ -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
diff --git a/test/golden/local.golden b/test/golden/local.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/local.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/local.jsonnet b/test/golden/local.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/local.jsonnet
@@ -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
diff --git a/test/golden/manifest-json-ex.golden b/test/golden/manifest-json-ex.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/manifest-json-ex.golden
@@ -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}"
diff --git a/test/golden/manifest-json-ex.jsonnet b/test/golden/manifest-json-ex.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/manifest-json-ex.jsonnet
@@ -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] }
+  },
+  "  "
+)
diff --git a/test/golden/manifest-yaml-doc.golden b/test/golden/manifest-yaml-doc.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/manifest-yaml-doc.golden
@@ -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"
diff --git a/test/golden/manifest-yaml-doc.jsonnet b/test/golden/manifest-yaml-doc.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/manifest-yaml-doc.jsonnet
@@ -0,0 +1,7 @@
+std.manifestYamlDoc(
+  {
+      x: [1, 2, 3, true, false, null,
+          "string\nstring\n"],
+      y: { a: 1, b: 2, c: [1, 2] }
+  }
+)
diff --git a/test/golden/null.golden b/test/golden/null.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/null.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/null.jsonnet b/test/golden/null.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/null.jsonnet
@@ -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
diff --git a/test/golden/object-methods.golden b/test/golden/object-methods.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/object-methods.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/object-methods.jsonnet b/test/golden/object-methods.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/object-methods.jsonnet
@@ -0,0 +1,7 @@
+local test = {
+  foo(x): x + 1
+};
+
+std.assertEqual(test.foo(5), 6) &&
+
+true
diff --git a/test/golden/object.golden b/test/golden/object.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/object.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/object.jsonnet b/test/golden/object.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/object.jsonnet
@@ -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
diff --git a/test/golden/oop.golden b/test/golden/oop.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/oop.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/oop.jsonnet b/test/golden/oop.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/oop.jsonnet
@@ -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
diff --git a/test/golden/oop_extra.golden b/test/golden/oop_extra.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/oop_extra.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/oop_extra.jsonnet b/test/golden/oop_extra.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/oop_extra.jsonnet
@@ -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
diff --git a/test/golden/outermost.golden b/test/golden/outermost.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/outermost.golden
@@ -0,0 +1,22 @@
+[
+    {
+        "bar": {
+            "baz": {
+                "qux": {
+                    "quux": "test1"
+                }
+            }
+        },
+        "foo": "test1"
+    },
+    {
+        "bar": {
+            "baz": {
+                "qux": {
+                    "quux": "test2"
+                }
+            }
+        },
+        "foo": "test2"
+    }
+]
diff --git a/test/golden/outermost.jsonnet b/test/golden/outermost.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/outermost.jsonnet
@@ -0,0 +1,24 @@
+local test = [
+  {
+    foo: "test1",
+    bar: {
+      baz: {
+        qux: {
+          quux: $.foo
+        }
+      }
+    }
+  },
+  {
+    foo: "test2",
+    bar: {
+      baz: {
+        qux: {
+          quux: $["foo"]
+        }
+      }
+    }
+  }
+];
+
+test
diff --git a/test/golden/precedence.golden b/test/golden/precedence.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/precedence.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/precedence.jsonnet b/test/golden/precedence.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/precedence.jsonnet
@@ -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
diff --git a/test/golden/recursive-functions.golden b/test/golden/recursive-functions.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/recursive-functions.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/recursive-functions.jsonnet b/test/golden/recursive-functions.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/recursive-functions.jsonnet
@@ -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
diff --git a/test/golden/slice.sugar.golden b/test/golden/slice.sugar.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/slice.sugar.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/slice.sugar.jsonnet b/test/golden/slice.sugar.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/slice.sugar.jsonnet
@@ -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,
+)
diff --git a/test/golden/std-all-hidden.golden b/test/golden/std-all-hidden.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/std-all-hidden.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/std-all-hidden.jsonnet b/test/golden/std-all-hidden.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/std-all-hidden.jsonnet
@@ -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, {})
diff --git a/test/golden/stdlib.golden b/test/golden/stdlib.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/stdlib.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/stdlib.jsonnet b/test/golden/stdlib.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/stdlib.jsonnet
@@ -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
diff --git a/test/golden/unicode.golden b/test/golden/unicode.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/unicode.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/unicode.jsonnet b/test/golden/unicode.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/unicode.jsonnet
@@ -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
diff --git a/test/golden/unparsed.golden b/test/golden/unparsed.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/unparsed.golden
@@ -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
+}
diff --git a/test/golden/unparsed.jsonnet b/test/golden/unparsed.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/unparsed.jsonnet
@@ -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,
+}
diff --git a/test/golden/verbatim-strings.golden b/test/golden/verbatim-strings.golden
new file mode 100644
--- /dev/null
+++ b/test/golden/verbatim-strings.golden
@@ -0,0 +1,1 @@
+true
diff --git a/test/golden/verbatim-strings.jsonnet b/test/golden/verbatim-strings.jsonnet
new file mode 100644
--- /dev/null
+++ b/test/golden/verbatim-strings.jsonnet
@@ -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
