[
  {
    "path": ".dockerignore",
    "content": ".direnv/\n.git/\n.github/\ndist/\ndist-newstyle/\nresult/\nDockerfile\n.dockerignore\n.envrc\n.ghci\n.gitignore\n./*.ll\n./*.o\n./*.a\n./*.wasm\n./*.eclair\n./*.dl\n./logo*\nhie.yaml\n"
  },
  {
    "path": ".ghci",
    "content": ":set prompt >\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: luc-tielen\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: \"Build\"\non: [push, pull_request]\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n    runs-on: ${{matrix.os}}\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Build and test\n        run: |\n          set -eo pipefail\n          export TIMESTAMP=$(date +%s)\n          docker build -f Dockerfile . -t eclair:$TIMESTAMP | tee eclair-lang-${{matrix.os}}.log\n          docker run --rm eclair:$TIMESTAMP bash -c \"make test\" | tee -a eclair-lang-${{matrix.os}}.log\n\n      - name: Check for disabled tests\n        run: |\n          ./tests/check.sh\n\n      - name: Upload logs\n        if: ${{ always() }}\n        uses: actions/upload-artifact@v3\n        with:\n          name: eclair-lang-${{matrix.os}}.log\n          path: eclair-lang-${{matrix.os}}.log\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: lint\non:\n  pull_request:\n  push:\n    branches:\n      - main\n      - \"releases/*\"\njobs:\n  hlint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: \"Set up HLint\"\n        uses: rwe/actions-hlint-setup@v1\n        with:\n          version: \"3.6.1\"\n\n      - name: \"Run HLint\"\n        uses: rwe/actions-hlint-run@v2\n        with:\n          path: '[\"lib/\", \"src/\", \"tests/\"]'\n          fail-on: warning\n"
  },
  {
    "path": ".gitignore",
    "content": "dist-newstyle/\ndist/\n.direnv/\n.devcontainer/\ncabal.project.local*\n\n*.ll\n*.bc\n*.o\n*.a\n*.s\n/*.wasm\n/*.dl\n/*.eclair\n/*.js\n\nresult\neclair.prof\neclair.svg\n*.eventlog*\neclair.hp\nperf.data\nperf.data.old\nperf.svg\n\nTODO*\n"
  },
  {
    "path": ".hlint.yaml",
    "content": "# HLint configuration file\n# https://github.com/ndmitchell/hlint\n##########################\n\n# This file contains a template configuration file, which is typically\n# placed as .hlint.yaml in the root of your project\n\n\n# Specify additional command line arguments\n#\n# - arguments: [--color, --cpp-simple, -XQuasiQuotes]\n\n\n# Control which extensions/flags/modules/functions can be used\n#\n# - extensions:\n#   - default: false # all extension are banned by default\n#   - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used\n#   - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module\n#\n# - flags:\n#   - {name: -w, within: []} # -w is allowed nowhere\n#\n# - modules:\n#   - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set'\n#   - {name: Control.Arrow, within: []} # Certain modules are banned entirely\n#\n# - functions:\n#   - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules\n\n- arguments:\n  - \"-XRecursiveDo\"\n\n# Add custom hints for this project\n#\n# Will suggest replacing \"wibbleMany [myvar]\" with \"wibbleOne myvar\"\n# - error: {lhs: \"wibbleMany [x]\", rhs: wibbleOne x}\n\n# The hints are named by the string they display in warning messages.\n# For example, if you see a warning starting like\n#\n# Main.hs:116:51: Warning: Redundant ==\n#\n# You can refer to that hint with `{name: Redundant ==}` (see below).\n\n# Turn on hints that are off by default\n#\n# Ban \"module X(module X) where\", to require a real export list\n# - warn: {name: Use explicit module export list}\n#\n# Replace a $ b $ c with a . b $ c\n# - group: {name: dollar, enabled: true}\n#\n# Generalise map to fmap, ++ to <>\n# - group: {name: generalise, enabled: true}\n\n\n# Ignore some builtin hints\n# - ignore: {name: Use let}\n# - ignore: {name: Use const, within: SpecialModule} # Only within certain modules\n- ignore: {name: Reduce duplication}\n\n\n# Define some custom infix operators\n# - fixity: infixr 3 ~^#^~\n\n\n# To generate a suitable file for HLint do:\n# $ hlint --default > .hlint.yaml\n\n# Relude-specific (https://github.com/kowainik/relude/blob/main/.hlint.yaml)\n- arguments:\n  - \"-XConstraintKinds\"\n  - \"-XDeriveGeneric\"\n  - \"-XGeneralizedNewtypeDeriving\"\n  - \"-XLambdaCase\"\n  - \"-XOverloadedStrings\"\n  - \"-XRecordWildCards\"\n  - \"-XScopedTypeVariables\"\n  - \"-XStandaloneDeriving\"\n  - \"-XTupleSections\"\n  - \"-XTypeApplications\"\n  - \"-XViewPatterns\"\n- ignore:\n    name: Use head\n- ignore:\n    name: Use Foldable.forM_\n- hint:\n    lhs: \"pure ()\"\n    note: \"Use 'pass'\"\n    rhs: pass\n- hint:\n    lhs: \"return ()\"\n    note: \"Use 'pass'\"\n    rhs: pass\n- hint:\n    lhs: \"(: [])\"\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: \"(:| [])\"\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.Sequence.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.Text.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.Text.Lazy.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.ByteString.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.ByteString.Lazy.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.Map.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.Map.Strict.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.HashMap.Strict.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.HashMap.Lazy.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.IntMap.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.IntMap.Strict.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.Set.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.HashSet.singleton\n    note: \"Use `one`\"\n    rhs: one\n- hint:\n    lhs: Data.IntSet.singleton\n    note: \"Use `one`\"\n    rhs: one\n- warn:\n    lhs: Control.Exception.evaluate\n    rhs: evaluateWHNF\n- warn:\n    lhs: \"Control.Exception.evaluate (force x)\"\n    rhs: evaluateNF x\n- warn:\n    lhs: \"Control.Exception.evaluate (x `deepseq` ())\"\n    rhs: evaluateNF_ x\n- warn:\n    lhs: \"void (evaluateWHNF x)\"\n    rhs: evaluateWHNF_ x\n- warn:\n    lhs: \"void (evaluateNF x)\"\n    rhs: evaluateNF_ x\n- hint:\n    lhs: Control.Exception.throw\n    note: \"Use 'impureThrow'\"\n    rhs: impureThrow\n- warn:\n    lhs: Data.Text.IO.readFile\n    rhs: readFileText\n- warn:\n    lhs: Data.Text.IO.writeFile\n    rhs: writeFileText\n- warn:\n    lhs: Data.Text.IO.appendFile\n    rhs: appendFileText\n- warn:\n    lhs: Data.Text.Lazy.IO.readFile\n    rhs: readFileLText\n- warn:\n    lhs: Data.Text.Lazy.IO.writeFile\n    rhs: writeFileLText\n- warn:\n    lhs: Data.Text.Lazy.IO.appendFile\n    rhs: appendFileLText\n- warn:\n    lhs: Data.ByteString.readFile\n    rhs: readFileBS\n- warn:\n    lhs: Data.ByteString.writeFile\n    rhs: writeFileBS\n- warn:\n    lhs: Data.ByteString.appendFile\n    rhs: appendFileBS\n- warn:\n    lhs: Data.ByteString.Lazy.readFile\n    rhs: readFileLBS\n- warn:\n    lhs: Data.ByteString.Lazy.writeFile\n    rhs: writeFileLBS\n- warn:\n    lhs: Data.ByteString.Lazy.appendFile\n    rhs: appendFileLBS\n- hint:\n    lhs: \"foldl' (flip f)\"\n    note: \"Use 'flipfoldl''\"\n    rhs: \"flipfoldl' f\"\n- warn:\n    lhs: \"foldl' (+) 0\"\n    rhs: sum\n- warn:\n    lhs: \"foldl' (*) 1\"\n    rhs: product\n- hint:\n    lhs: \"fmap and (sequence s)\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: andM s\n- hint:\n    lhs: \"and <$> sequence s\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: andM s\n- hint:\n    lhs: \"fmap or (sequence s)\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: orM s\n- hint:\n    lhs: \"or <$> sequence s\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: orM s\n- hint:\n    lhs: \"fmap and (mapM f s)\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: allM f s\n- hint:\n    lhs: \"and <$> mapM f s\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: allM f s\n- hint:\n    lhs: \"fmap or (mapM f s)\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: anyM f s\n- hint:\n    lhs: \"or <$> mapM f s\"\n    note: Applying this hint would mean that some actions that were being executed previously would no longer be executed.\n    rhs: anyM f s\n- warn:\n    lhs: \"getAlt (foldMap (Alt . f) xs)\"\n    rhs: asumMap xs\n- warn:\n    lhs: \"getAlt . foldMap (Alt . f)\"\n    rhs: asumMap\n- hint:\n    lhs: \"foldr (\\\\x acc -> f x <|> acc) empty\"\n    note: \"Use 'asumMap'\"\n    rhs: asumMap f\n- hint:\n    lhs: \"asum (map f xs)\"\n    note: \"Use 'asumMap'\"\n    rhs: asumMap f xs\n- warn:\n    lhs: \"map fst &&& map snd\"\n    rhs: unzip\n- hint:\n    lhs: \"fmap (fmap f) x\"\n    note: \"Use '(<<$>>)'\"\n    rhs: \"f <<$>> x\"\n- hint:\n    lhs: \"(\\\\f -> f x) <$> ff\"\n    note: Use flap operator\n    rhs: \"ff ?? x\"\n- hint:\n    lhs: \"fmap (\\\\f -> f x) ff\"\n    note: Use flap operator\n    rhs: \"ff ?? x\"\n- hint:\n    lhs: \"fmap ($ x) ff\"\n    note: Use flap operator\n    rhs: \"ff ?? x\"\n- hint:\n    lhs: \"($ x) <$> ff\"\n    note: Use flap operator\n    rhs: \"ff ?? x\"\n- warn:\n    lhs: \"fmap f (nonEmpty x)\"\n    rhs: viaNonEmpty f x\n- warn:\n    lhs: fmap f . nonEmpty\n    rhs: viaNonEmpty f\n- warn:\n    lhs: \"f <$> nonEmpty x\"\n    rhs: viaNonEmpty f x\n- warn:\n    lhs: partitionEithers . map f\n    rhs: partitionWith f\n- warn:\n    lhs: partitionEithers $ map f x\n    rhs: partitionWith f x\n- warn:\n    lhs: \"f >>= guard\"\n    rhs: guardM f\n- warn:\n    lhs: guard =<< f\n    rhs: guardM f\n- warn:\n    lhs: forever\n    note: \"'forever' is loosely typed and may hide errors\"\n    rhs: infinitely\n- warn:\n    lhs: \"whenM (not <$> x)\"\n    rhs: unlessM x\n- warn:\n    lhs: \"unlessM (not <$> x)\"\n    rhs: whenM x\n- warn:\n    lhs: \"either (const True) (const False)\"\n    rhs: isLeft\n- warn:\n    lhs: \"either (const False) (const True)\"\n    rhs: isRight\n- warn:\n    lhs: \"either id (const a)\"\n    rhs: fromLeft a\n- warn:\n    lhs: \"either (const b) id\"\n    rhs: fromRight b\n- warn:\n    lhs: \"either Just (const Nothing)\"\n    rhs: leftToMaybe\n- warn:\n    lhs: \"either (const Nothing) Just\"\n    rhs: rightToMaybe\n- warn:\n    lhs: \"maybe (Left l) Right\"\n    rhs: maybeToRight l\n- warn:\n    lhs: \"maybe (Right r) Left\"\n    rhs: maybeToLeft r\n- warn:\n    lhs: \"case m of Just x -> f x; Nothing -> pure ()\"\n    rhs: whenJust m f\n- warn:\n    lhs: \"case m of Just x -> f x; Nothing -> return ()\"\n    rhs: whenJust m f\n- warn:\n    lhs: \"case m of Just x -> f x; Nothing -> pass\"\n    rhs: whenJust m f\n- warn:\n    lhs: \"case m of Nothing -> pure ()  ; Just x -> f x\"\n    rhs: whenJust m f\n- warn:\n    lhs: \"case m of Nothing -> return (); Just x -> f x\"\n    rhs: whenJust m f\n- warn:\n    lhs: \"case m of Nothing -> pass     ; Just x -> f x\"\n    rhs: whenJust m f\n- warn:\n    lhs: \"maybe (pure ())   f m\"\n    rhs: whenJust m f\n- warn:\n    lhs: \"maybe (return ()) f m\"\n    rhs: whenJust m f\n- warn:\n    lhs: maybe pass        f m\n    rhs: whenJust m f\n- warn:\n    lhs: \"m >>= \\\\a -> whenJust a f\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= \\\\case Just x -> f x; Nothing -> pure ()\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= \\\\case Just x -> f x; Nothing -> return ()\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= \\\\case Just x -> f x; Nothing -> pass\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= \\\\case Nothing -> pure ()  ; Just x -> f x\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= \\\\case Nothing -> return (); Just x -> f x\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= \\\\case Nothing -> pass     ; Just x -> f x\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"maybe (pure ())   f =<< m\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"maybe (return ()) f =<< m\"\n    rhs: whenJustM m f\n- warn:\n    lhs: maybe pass        f =<< m\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= maybe (pure ())   f\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= maybe (return ()) f\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"m >>= maybe pass        f\"\n    rhs: whenJustM m f\n- warn:\n    lhs: \"case m of Just _ -> pure ()  ; Nothing -> x\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"case m of Just _ -> return (); Nothing -> x\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"case m of Just _ -> pass     ; Nothing -> x\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"case m of Nothing -> x; Just _ -> pure ()\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"case m of Nothing -> x; Just _ -> return ()\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"case m of Nothing -> x; Just _ -> pass\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"maybe x (\\\\_ -> pure ()    ) m\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"maybe x (\\\\_ -> return ()  ) m\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"maybe x (\\\\_ -> pass       ) m\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"maybe x (const (pure ()  )) m\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"maybe x (const (return ())) m\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"maybe x (const pass) m\"\n    rhs: whenNothing_ m x\n- warn:\n    lhs: \"m >>= \\\\a -> whenNothing_ a x\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= \\\\case Just _ -> pure ()  ; Nothing -> x\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= \\\\case Just _ -> return (); Nothing -> x\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= \\\\case Just _ -> pass     ; Nothing -> x\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= \\\\case Nothing -> x; Just _ -> pure ()\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= \\\\case Nothing -> x; Just _ -> return ()\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= \\\\case Nothing -> x; Just _ -> pass\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"maybe x (\\\\_ -> pure ()    ) =<< m\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"maybe x (\\\\_ -> return ()  ) =<< m\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"maybe x (\\\\_ -> pass       ) =<< m\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"maybe x (const (pure ()  )) =<< m\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"maybe x (const (return ())) =<< m\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"maybe x (const pass) =<< m\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= maybe x (\\\\_ -> pure ())\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= maybe x (\\\\_ -> return ())\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= maybe x (\\\\_ -> pass)\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= maybe x (const (pure ())  )\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= maybe x (const (return ()))\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"m >>= maybe x (const pass)\"\n    rhs: whenNothingM_ m x\n- warn:\n    lhs: \"whenLeft ()\"\n    rhs: whenLeft_\n- warn:\n    lhs: \"case m of Left x -> f x; Right _ -> pure ()\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"case m of Left x -> f x; Right _ -> return ()\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"case m of Left x -> f x; Right _ -> pass\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"case m of Right _ -> pure ()  ; Left x -> f x\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"case m of Right _ -> return (); Left x -> f x\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"case m of Right _ -> pass     ; Left x -> f x\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"either f (\\\\_ -> pure ()    ) m\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"either f (\\\\_ -> return ()  ) m\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"either f (\\\\_ -> pass       ) m\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"either f (const (pure ()  )) m\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"either f (const (return ())) m\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"either f (const pass) m\"\n    rhs: whenLeft_ m f\n- warn:\n    lhs: \"m >>= \\\\a -> whenLeft_ a f\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Left x -> f x; Right _ -> pure ()\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Left x -> f x; Right _ -> return ()\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Left x -> f x; Right _ -> pass\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Right _ -> pure ()  ; Left x -> f x\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Right _ -> return (); Left x -> f x\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Right _ -> pass     ; Left x -> f x\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"either f (\\\\_ -> pure ()    ) =<< m\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"either f (\\\\_ -> return ()  ) =<< m\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"either f (\\\\_ -> pass       ) =<< m\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"either f (const (pure ()  )) =<< m\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"either f (const (return ())) =<< m\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"either f (const pass) =<< m\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= either f (\\\\_ -> pure ())\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= either f (\\\\_ -> return ())\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= either f (\\\\_ -> pass)\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= either f (const (pure ())  )\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= either f (const (return ()))\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"m >>= either f (const pass)\"\n    rhs: whenLeftM_ m f\n- warn:\n    lhs: \"whenRight ()\"\n    rhs: whenRight_\n- warn:\n    lhs: \"case m of Right x -> f x; Left _ -> pure ()\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"case m of Right x -> f x; Left _ -> return ()\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"case m of Right x -> f x; Left _ -> pass\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"case m of Left _ -> pure ()  ; Right x -> f x\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"case m of Left _ -> return (); Right x -> f x\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"case m of Left _ -> pass     ; Right x -> f x\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"either (\\\\_ -> pure ()    ) f m\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"either (\\\\_ -> return ()  ) f m\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"either (\\\\_ -> pass       ) f m\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"either (const (pure ()  )) f m\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"either (const (return ())) f m\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"either (const pass) f m\"\n    rhs: whenRight_ m f\n- warn:\n    lhs: \"m >>= \\\\a -> whenRight_ a f\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Right x -> f x; Left _ -> pure ()  \"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Right x -> f x; Left _ -> return ()\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Right x -> f x; Left _ -> pass\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Left _ -> pure ()  ; Right x -> f x\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Left _ -> return (); Right x -> f x\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= \\\\case Left _ -> pass     ; Right x -> f x\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"either (\\\\_ -> pure ()    ) f =<< m\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"either (\\\\_ -> return ()  ) f =<< m\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"either (\\\\_ -> pass       ) f =<< m\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"either (const (pure ()  )) f =<< m\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"either (const (return ())) f =<< m\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"either (const pass) f =<< m\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= either (\\\\_ -> pure ())   f\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= either (\\\\_ -> return ()) f\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= either (\\\\_ -> pass)      f\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= either (const (pure ())  ) f\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= either (const (return ())) f\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"m >>= either (const pass) f\"\n    rhs: whenRightM_ m f\n- warn:\n    lhs: \"case m of Left x -> f x; Right _ -> pure d  \"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"case m of Left x -> f x; Right _ -> return d\"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"case m of Right _ -> pure d  ; Left x -> f x\"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"case m of Right _ -> return d; Left x -> f x\"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"either f (\\\\_ -> pure d    ) m\"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"either f (\\\\_ -> return d  ) m\"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"either f (const (pure d  )) m\"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"either f (const (return d)) m\"\n    rhs: whenLeft d m f\n- warn:\n    lhs: \"m >>= \\\\a -> whenLeft d a f\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= \\\\case Left x -> f x; Right _ -> pure d\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= \\\\case Left x -> f x; Right _ -> return d\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= \\\\case Right _ -> pure d  ; Left x -> f x\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= \\\\case Right _ -> return d; Left x -> f x\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"either f (\\\\_ -> pure d    ) =<< m\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"either f (\\\\_ -> return d  ) =<< m\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"either f (const (pure d  )) =<< m\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"either f (const (return d)) =<< m\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= either f (\\\\_ -> pure d)\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= either f (\\\\_ -> return d)\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= either f (const (pure d))\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"m >>= either f (const (return d))\"\n    rhs: whenLeftM d m f\n- warn:\n    lhs: \"case m of Right x -> f x; Left _ -> pure d\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"case m of Right x -> f x; Left _ -> return d\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"case m of Left _ -> pure d  ; Right x -> f x\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"case m of Left _ -> return d; Right x -> f x\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"either (\\\\_ -> pure d    ) f m\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"either (\\\\_ -> return d  ) f m\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"either (const (pure d  )) f m\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"either (const (return d)) f m\"\n    rhs: whenRight d m f\n- warn:\n    lhs: \"m >>= \\\\a -> whenRight d a f\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= \\\\case Right x -> f x; Left _ -> pure d\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= \\\\case Right x -> f x; Left _ -> return d\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= \\\\case Left _ -> pure d  ; Right x -> f x\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= \\\\case Left _ -> return d; Right x -> f x\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"either (\\\\_ -> pure d    ) f =<< m\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"either (\\\\_ -> return d  ) f =<< m\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"either (const (pure d  )) f =<< m\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"either (const (return d)) f =<< m\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= either (\\\\_ -> pure d)   f\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= either (\\\\_ -> return d) f\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= either (const (pure d)  ) f\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"m >>= either (const (return d)) f\"\n    rhs: whenRightM d m f\n- warn:\n    lhs: \"case m of [] -> return (); (x:xs) -> f (x :| xs)\"\n    rhs: whenNotNull m f\n- warn:\n    lhs: \"case m of [] -> pure ()  ; (x:xs) -> f (x :| xs)\"\n    rhs: whenNotNull m f\n- warn:\n    lhs: \"case m of [] -> pass     ; (x:xs) -> f (x :| xs)\"\n    rhs: whenNotNull m f\n- warn:\n    lhs: \"case m of (x:xs) -> f (x :| xs); [] -> return ()\"\n    rhs: whenNotNull m f\n- warn:\n    lhs: \"case m of (x:xs) -> f (x :| xs); [] -> pure ()  \"\n    rhs: whenNotNull m f\n- warn:\n    lhs: \"case m of (x:xs) -> f (x :| xs); [] -> pass     \"\n    rhs: whenNotNull m f\n- warn:\n    lhs: \"m >>= \\\\case [] -> pass     ; (x:xs) -> f (x :| xs)\"\n    rhs: whenNotNullM m f\n- warn:\n    lhs: \"m >>= \\\\case [] -> pure ()  ; (x:xs) -> f (x :| xs)\"\n    rhs: whenNotNullM m f\n- warn:\n    lhs: \"m >>= \\\\case [] -> return (); (x:xs) -> f (x :| xs)\"\n    rhs: whenNotNullM m f\n- warn:\n    lhs: \"m >>= \\\\case (x:xs) -> f (x :| xs); [] -> pass     \"\n    rhs: whenNotNullM m f\n- warn:\n    lhs: \"m >>= \\\\case (x:xs) -> f (x :| xs); [] -> pure ()  \"\n    rhs: whenNotNullM m f\n- warn:\n    lhs: \"m >>= \\\\case (x:xs) -> f (x :| xs); [] -> return ()\"\n    rhs: whenNotNullM m f\n- warn:\n    lhs: mapMaybe leftToMaybe\n    rhs: lefts\n- warn:\n    lhs: mapMaybe rightToMaybe\n    rhs: rights\n- warn:\n    lhs: flip runReaderT\n    rhs: usingReaderT\n- warn:\n    lhs: flip runReader\n    rhs: usingReader\n- warn:\n    lhs: flip runStateT\n    rhs: usingStateT\n- warn:\n    lhs: flip runState\n    rhs: usingState\n- warn:\n    lhs: \"fst <$> usingStateT s st\"\n    rhs: evaluatingStateT s st\n- warn:\n    lhs: \"fst (usingState s st)\"\n    rhs: evaluatingState s st\n- warn:\n    lhs: \"snd <$> usingStateT s st\"\n    rhs: executingStateT s st\n- warn:\n    lhs: \"snd (usingState s st)\"\n    rhs: executingState s st\n- warn:\n    lhs: \"MaybeT (pure m)\"\n    rhs: hoistMaybe m\n- warn:\n    lhs: \"MaybeT (return m)\"\n    rhs: hoistMaybe m\n- warn:\n    lhs: MaybeT . pure\n    rhs: hoistMaybe\n- warn:\n    lhs: MaybeT . return\n    rhs: hoistMaybe\n- warn:\n    lhs: \"ExceptT (pure m)\"\n    rhs: hoistEither m\n- warn:\n    lhs: \"ExceptT (return m)\"\n    rhs: hoistEither m\n- warn:\n    lhs: ExceptT . pure\n    rhs: hoistEither\n- warn:\n    lhs: ExceptT . return\n    rhs: hoistEither\n- warn:\n    lhs: fromMaybe mempty\n    rhs: maybeToMonoid\n- warn:\n    lhs: \"m ?: mempty\"\n    rhs: maybeToMonoid m\n- warn:\n    lhs: \"Data.Map.toAscList (Data.Map.fromList x)\"\n    rhs: sortWith fst x\n- warn:\n    lhs: \"Data.Map.toDescList (Data.Map.fromList x)\"\n    rhs: \"sortWith (Down . fst) x\"\n- warn:\n    lhs: \"Data.Set.toList (Data.Set.fromList l)\"\n    rhs: sortNub l\n- warn:\n    lhs: \"Data.Set.assocs (Data.Set.fromList l)\"\n    rhs: sortNub l\n- warn:\n    lhs: \"Data.Set.toAscList (Data.Set.fromList l)\"\n    rhs: sortNub l\n- warn:\n    lhs: \"Data.HashSet.toList (Data.HashSet.fromList l)\"\n    rhs: unstableNub l\n- warn:\n    lhs: nub\n    note: \"'nub' is O(n^2), 'ordNub' is O(n log n)\"\n    rhs: ordNub\n- warn:\n    lhs: \"sortBy (comparing f)\"\n    note: \"If the function you are using for 'comparing' is slow, use 'sortOn' instead of 'sortWith', because 'sortOn' caches applications the function and 'sortWith' doesn't.\"\n    rhs: sortWith f\n- warn:\n    lhs: sortOn fst\n    note: \"'sortWith' will be faster here because it doesn't do caching\"\n    rhs: sortWith fst\n- warn:\n    lhs: sortOn snd\n    note: \"'sortWith' will be faster here because it doesn't do caching\"\n    rhs: sortWith snd\n- warn:\n    lhs: \"sortOn (Down . fst)\"\n    note: \"'sortWith' will be faster here because it doesn't do caching\"\n    rhs: \"sortWith (Down . fst)\"\n- warn:\n    lhs: \"sortOn (Down . snd)\"\n    note: \"'sortWith' will be faster here because it doesn't do caching\"\n    rhs: \"sortWith (Down . snd)\"\n- warn:\n    lhs: Data.Text.IO.putStr\n    rhs: putText\n- warn:\n    lhs: Data.Text.IO.putStrLn\n    rhs: putTextLn\n- warn:\n    lhs: Data.Text.Lazy.IO.putStr\n    rhs: putLText\n- warn:\n    lhs: Data.Text.Lazy.IO.putStrLn\n    rhs: putLTextLn\n- warn:\n    lhs: Data.ByteString.Char8.putStr\n    rhs: putBS\n- warn:\n    lhs: Data.ByteString.Char8.putStrLn\n    rhs: putBSLn\n- warn:\n    lhs: Data.ByteString.Lazy.Char8.putStr\n    rhs: putLBS\n- warn:\n    lhs: Data.ByteString.Lazy.Char8.putStrLn\n    rhs: putLBSLn\n- warn:\n    lhs: Data.Text.Lazy.Text\n    rhs: LText\n- warn:\n    lhs: Data.ByteString.Lazy.ByteString\n    rhs: LByteString\n- warn:\n    lhs: Data.ByteString.UTF8.fromString\n    rhs: encodeUtf8\n- warn:\n    lhs: Data.ByteString.UTF8.toString\n    rhs: decodeUtf8\n- warn:\n    lhs: Data.Text.Encoding.encodeUtf8\n    rhs: encodeUtf8\n- warn:\n    lhs: Data.Text.Encoding.decodeUtf8\n    rhs: decodeUtf8\n- warn:\n    lhs: \"Data.ByteString.Lazy.toStrict (encodeUtf8 x)\"\n    rhs: encodeUtf8 x\n- warn:\n    lhs: \"toStrict (encodeUtf8 x)\"\n    rhs: encodeUtf8 x\n- warn:\n    lhs: \"decodeUtf8 (Data.ByteString.Lazy.fromStrict x)\"\n    rhs: decodeUtf8 x\n- warn:\n    lhs: \"decodeUtf8 (fromStrict x)\"\n    rhs: decodeUtf8 x\n- warn:\n    lhs: Data.ByteString.Lazy.UTF8.fromString\n    rhs: encodeUtf8\n- warn:\n    lhs: Data.ByteString.Lazy.UTF8.toString\n    rhs: decodeUtf8\n- warn:\n    lhs: \"Data.ByteString.Lazy.fromStrict (Data.Text.Encoding.encodeUtf8 x)\"\n    rhs: encodeUtf8 x\n- warn:\n    lhs: \"Data.ByteString.Lazy.fromStrict (encodeUtf8 x)\"\n    rhs: encodeUtf8 x\n- warn:\n    lhs: \"Data.Text.Encoding.decodeUtf8 (Data.ByteString.Lazy.toStrict x)\"\n    rhs: decodeUtf8 x\n- warn:\n    lhs: \"Data.Text.Encoding.decodeUtf8 (toStrict x)\"\n    rhs: decodeUtf8 x\n- warn:\n    lhs: \"decodeUtf8 (Data.ByteString.Lazy.toStrict x)\"\n    rhs: decodeUtf8 x\n- warn:\n    lhs: \"decodeUtf8 (toStrict x)\"\n    rhs: decodeUtf8 x\n- warn:\n    lhs: Data.Text.pack\n    rhs: toText\n- warn:\n    lhs: Data.Text.unpack\n    rhs: toString\n- warn:\n    lhs: Data.Text.Lazy.pack\n    rhs: toLText\n- warn:\n    lhs: Data.Text.Lazy.unpack\n    rhs: toString\n- warn:\n    lhs: Data.Text.Lazy.toStrict\n    rhs: toText\n- warn:\n    lhs: Data.Text.Lazy.fromStrict\n    rhs: toLText\n- warn:\n    lhs: \"Data.Text.pack (show x)\"\n    rhs: show x\n- warn:\n    lhs: \"Data.Text.Lazy.pack (show x)\"\n    rhs: show x\n- warn:\n    lhs: Data.ByteString.Lazy.fromStrict\n    rhs: fromStrict\n- warn:\n    lhs: Data.ByteString.Lazy.toStrict\n    rhs: toStrict\n- warn:\n    lhs: Data.Text.Lazy.fromStrict\n    rhs: fromStrict\n- warn:\n    lhs: Data.Text.Lazy.toStrict\n    rhs: toStrict\n- warn:\n    lhs: Control.Applicative.Alternative\n    name: \"Use 'Alternative' from Relude\"\n    note: \"'Alternative' is already exported from Relude\"\n    rhs: Alternative\n- warn:\n    lhs: Control.Applicative.empty\n    name: \"Use 'empty' from Relude\"\n    note: \"'empty' is already exported from Relude\"\n    rhs: empty\n- warn:\n    lhs: \"(Control.Applicative.<|>)\"\n    name: \"Use '<|>' from Relude\"\n    note: \"Operator '(<|>)' is already exported from Relude\"\n    rhs: \"(<|>)\"\n- warn:\n    lhs: Control.Applicative.some\n    name: \"Use 'some' from Relude\"\n    note: \"'some' is already exported from Relude\"\n    rhs: some\n- warn:\n    lhs: Control.Applicative.many\n    name: \"Use 'many' from Relude\"\n    note: \"'many' is already exported from Relude\"\n    rhs: many\n- warn:\n    lhs: Control.Applicative.Const\n    name: \"Use 'Const' from Relude\"\n    note: \"'Const' is already exported from Relude\"\n    rhs: Const\n- warn:\n    lhs: Control.Applicative.getConst\n    name: \"Use 'getConst' from Relude\"\n    note: \"'getConst' is already exported from Relude\"\n    rhs: getConst\n- warn:\n    lhs: Control.Applicative.ZipList\n    name: \"Use 'ZipList' from Relude\"\n    note: \"'ZipList' is already exported from Relude\"\n    rhs: ZipList\n- warn:\n    lhs: Control.Applicative.getZipList\n    name: \"Use 'getZipList' from Relude\"\n    note: \"'getZipList' is already exported from Relude\"\n    rhs: getZipList\n- warn:\n    lhs: Control.Applicative.liftA2\n    name: \"Use 'liftA2' from Relude\"\n    note: \"'liftA2' is already exported from Relude\"\n    rhs: liftA2\n- warn:\n    lhs: Control.Applicative.liftA3\n    name: \"Use 'liftA3' from Relude\"\n    note: \"'liftA3' is already exported from Relude\"\n    rhs: liftA3\n- warn:\n    lhs: Control.Applicative.optional\n    name: \"Use 'optional' from Relude\"\n    note: \"'optional' is already exported from Relude\"\n    rhs: optional\n- warn:\n    lhs: \"(Control.Applicative.<**>)\"\n    name: \"Use '<**>' from Relude\"\n    note: \"Operator '(<**>)' is already exported from Relude\"\n    rhs: \"(<**>)\"\n- warn:\n    lhs: Data.Bits.xor\n    name: \"Use 'xor' from Relude\"\n    note: \"'xor' is already exported from Relude\"\n    rhs: xor\n- warn:\n    lhs: Data.Char.chr\n    name: \"Use 'chr' from Relude\"\n    note: \"'chr' is already exported from Relude\"\n    rhs: chr\n- warn:\n    lhs: Data.Int.Int8\n    name: \"Use 'Int8' from Relude\"\n    note: \"'Int8' is already exported from Relude\"\n    rhs: Int8\n- warn:\n    lhs: Data.Int.Int16\n    name: \"Use 'Int16' from Relude\"\n    note: \"'Int16' is already exported from Relude\"\n    rhs: Int16\n- warn:\n    lhs: Data.Int.Int32\n    name: \"Use 'Int32' from Relude\"\n    note: \"'Int32' is already exported from Relude\"\n    rhs: Int32\n- warn:\n    lhs: Data.Int.Int64\n    name: \"Use 'Int64' from Relude\"\n    note: \"'Int64' is already exported from Relude\"\n    rhs: Int64\n- warn:\n    lhs: Data.Word.Word8\n    name: \"Use 'Word8' from Relude\"\n    note: \"'Word8' is already exported from Relude\"\n    rhs: Word8\n- warn:\n    lhs: Data.Word.Word16\n    name: \"Use 'Word16' from Relude\"\n    note: \"'Word16' is already exported from Relude\"\n    rhs: Word16\n- warn:\n    lhs: Data.Word.Word32\n    name: \"Use 'Word32' from Relude\"\n    note: \"'Word32' is already exported from Relude\"\n    rhs: Word32\n- warn:\n    lhs: Data.Word.Word64\n    name: \"Use 'Word64' from Relude\"\n    note: \"'Word64' is already exported from Relude\"\n    rhs: Word64\n- warn:\n    lhs: Data.Word.byteSwap16\n    name: \"Use 'byteSwap16' from Relude\"\n    note: \"'byteSwap16' is already exported from Relude\"\n    rhs: byteSwap16\n- warn:\n    lhs: Data.Word.byteSwap32\n    name: \"Use 'byteSwap32' from Relude\"\n    note: \"'byteSwap32' is already exported from Relude\"\n    rhs: byteSwap32\n- warn:\n    lhs: Data.Word.byteSwap64\n    name: \"Use 'byteSwap64' from Relude\"\n    note: \"'byteSwap64' is already exported from Relude\"\n    rhs: byteSwap64\n- warn:\n    lhs: Numeric.Natural.Natural\n    name: \"Use 'Natural' from Relude\"\n    note: \"'Natural' is already exported from Relude\"\n    rhs: Natural\n- warn:\n    lhs: System.IO.IOMode\n    name: \"Use 'IOMode' from Relude\"\n    note: \"'IOMode' is already exported from Relude\"\n    rhs: IOMode\n- warn:\n    lhs: System.IO.ReadMode\n    name: \"Use 'ReadMode' from Relude\"\n    note: \"'ReadMode' is already exported from Relude\"\n    rhs: ReadMode\n- warn:\n    lhs: System.IO.WriteMode\n    name: \"Use 'WriteMode' from Relude\"\n    note: \"'WriteMode' is already exported from Relude\"\n    rhs: WriteMode\n- warn:\n    lhs: System.IO.AppendMode\n    name: \"Use 'AppendMode' from Relude\"\n    note: \"'AppendMode' is already exported from Relude\"\n    rhs: AppendMode\n- warn:\n    lhs: System.IO.ReadWriteMode\n    name: \"Use 'ReadWriteMode' from Relude\"\n    note: \"'ReadWriteMode' is already exported from Relude\"\n    rhs: ReadWriteMode\n- warn:\n    lhs: Data.Ord.Down\n    name: \"Use 'Down' from Relude\"\n    note: \"'Down' is already exported from Relude\"\n    rhs: Down\n- warn:\n    lhs: Data.Ord.comparing\n    name: \"Use 'comparing' from Relude\"\n    note: \"'comparing' is already exported from Relude\"\n    rhs: comparing\n- warn:\n    lhs: Data.Coerce.Coercible\n    name: \"Use 'Coercible' from Relude\"\n    note: \"'Coercible' is already exported from Relude\"\n    rhs: Coercible\n- warn:\n    lhs: Data.Coerce.coerce\n    name: \"Use 'coerce' from Relude\"\n    note: \"'coerce' is already exported from Relude\"\n    rhs: coerce\n- warn:\n    lhs: Data.Kind.Constraint\n    name: \"Use 'Constraint' from Relude\"\n    note: \"'Constraint' is already exported from Relude\"\n    rhs: Constraint\n- warn:\n    lhs: Data.Kind.Type\n    name: \"Use 'Type' from Relude\"\n    note: \"'Type' is already exported from Relude\"\n    rhs: Type\n- warn:\n    lhs: Data.Typeable.Typeable\n    name: \"Use 'Typeable' from Relude\"\n    note: \"'Typeable' is already exported from Relude\"\n    rhs: Typeable\n- warn:\n    lhs: Data.Proxy.Proxy\n    name: \"Use 'Proxy' from Relude\"\n    note: \"'Proxy' is already exported from Relude\"\n    rhs: Proxy\n- warn:\n    lhs: Data.Typeable.Typeable\n    name: \"Use 'Typeable' from Relude\"\n    note: \"'Typeable' is already exported from Relude\"\n    rhs: Typeable\n- warn:\n    lhs: Data.Void.Void\n    name: \"Use 'Void' from Relude\"\n    note: \"'Void' is already exported from Relude\"\n    rhs: Void\n- warn:\n    lhs: Data.Void.absurd\n    name: \"Use 'absurd' from Relude\"\n    note: \"'absurd' is already exported from Relude\"\n    rhs: absurd\n- warn:\n    lhs: Data.Void.vacuous\n    name: \"Use 'vacuous' from Relude\"\n    note: \"'vacuous' is already exported from Relude\"\n    rhs: vacuous\n- warn:\n    lhs: Data.Base.maxInt\n    name: \"Use 'maxInt' from Relude\"\n    note: \"'maxInt' is already exported from Relude\"\n    rhs: maxInt\n- warn:\n    lhs: Data.Base.minInt\n    name: \"Use 'minInt' from Relude\"\n    note: \"'minInt' is already exported from Relude\"\n    rhs: minInt\n- warn:\n    lhs: Data.Base.ord\n    name: \"Use 'ord' from Relude\"\n    note: \"'ord' is already exported from Relude\"\n    rhs: ord\n- warn:\n    lhs: GHC.Enum.boundedEnumFrom\n    name: \"Use 'boundedEnumFrom' from Relude\"\n    note: \"'boundedEnumFrom' is already exported from Relude\"\n    rhs: boundedEnumFrom\n- warn:\n    lhs: GHC.Enum.boundedEnumFromThen\n    name: \"Use 'boundedEnumFromThen' from Relude\"\n    note: \"'boundedEnumFromThen' is already exported from Relude\"\n    rhs: boundedEnumFromThen\n- warn:\n    lhs: GHC.Generics.Generic\n    name: \"Use 'Generic' from Relude\"\n    note: \"'Generic' is already exported from Relude\"\n    rhs: Generic\n- warn:\n    lhs: GHC.Real.Ratio\n    name: \"Use 'Ratio' from Relude\"\n    note: \"'Ratio' is already exported from Relude\"\n    rhs: Ratio\n- warn:\n    lhs: GHC.Real.Rational\n    name: \"Use 'Rational' from Relude\"\n    note: \"'Rational' is already exported from Relude\"\n    rhs: Rational\n- warn:\n    lhs: GHC.Real.denominator\n    name: \"Use 'denominator' from Relude\"\n    note: \"'denominator' is already exported from Relude\"\n    rhs: denominator\n- warn:\n    lhs: GHC.Real.numerator\n    name: \"Use 'numerator' from Relude\"\n    note: \"'numerator' is already exported from Relude\"\n    rhs: numerator\n- warn:\n    lhs: GHC.TypeNats.CmpNat\n    name: \"Use 'CmpNat' from Relude\"\n    note: \"'CmpNat' is already exported from Relude\"\n    rhs: CmpNat\n- warn:\n    lhs: GHC.TypeNats.KnownNat\n    name: \"Use 'KnownNat' from Relude\"\n    note: \"'KnownNat' is already exported from Relude\"\n    rhs: KnownNat\n- warn:\n    lhs: GHC.TypeNats.Nat\n    name: \"Use 'Nat' from Relude\"\n    note: \"'Nat' is already exported from Relude\"\n    rhs: Nat\n- warn:\n    lhs: GHC.TypeNats.SomeNat\n    name: \"Use 'SomeNat' from Relude\"\n    note: \"'SomeNat' is already exported from Relude\"\n    rhs: SomeNat\n- warn:\n    lhs: GHC.TypeNats.natVal\n    name: \"Use 'natVal' from Relude\"\n    note: \"'natVal' is already exported from Relude\"\n    rhs: natVal\n- warn:\n    lhs: GHC.TypeNats.someNatVal\n    name: \"Use 'someNatVal' from Relude\"\n    note: \"'someNatVal' is already exported from Relude\"\n    rhs: someNatVal\n- warn:\n    lhs: GHC.TypeLits.CmpNat\n    name: \"Use 'CmpNat' from Relude\"\n    note: \"'CmpNat' is already exported from Relude\"\n    rhs: CmpNat\n- warn:\n    lhs: GHC.TypeLits.KnownNat\n    name: \"Use 'KnownNat' from Relude\"\n    note: \"'KnownNat' is already exported from Relude\"\n    rhs: KnownNat\n- warn:\n    lhs: GHC.TypeLits.Nat\n    name: \"Use 'Nat' from Relude\"\n    note: \"'Nat' is already exported from Relude\"\n    rhs: Nat\n- warn:\n    lhs: GHC.TypeLits.SomeNat\n    name: \"Use 'SomeNat' from Relude\"\n    note: \"'SomeNat' is already exported from Relude\"\n    rhs: SomeNat\n- warn:\n    lhs: GHC.TypeLits.natVal\n    name: \"Use 'natVal' from Relude\"\n    note: \"'natVal' is already exported from Relude\"\n    rhs: natVal\n- warn:\n    lhs: GHC.TypeLits.someNatVal\n    name: \"Use 'someNatVal' from Relude\"\n    note: \"'someNatVal' is already exported from Relude\"\n    rhs: someNatVal\n- warn:\n    lhs: GHC.ExecutionStack.getStackTrace\n    name: \"Use 'getStackTrace' from Relude\"\n    note: \"'getStackTrace' is already exported from Relude\"\n    rhs: getStackTrace\n- warn:\n    lhs: GHC.ExecutionStack.showStackTrace\n    name: \"Use 'showStackTrace' from Relude\"\n    note: \"'showStackTrace' is already exported from Relude\"\n    rhs: showStackTrace\n- warn:\n    lhs: GHC.OverloadedLabels.IsLabel\n    name: \"Use 'IsLabel' from Relude\"\n    note: \"'IsLabel' is already exported from Relude\"\n    rhs: IsLabel\n- warn:\n    lhs: GHC.OverloadedLabels.fromLabel\n    name: \"Use 'fromLabel' from Relude\"\n    note: \"'fromLabel' is already exported from Relude\"\n    rhs: fromLabel\n- warn:\n    lhs: GHC.Stack.CallStack\n    name: \"Use 'CallStack' from Relude\"\n    note: \"'CallStack' is already exported from Relude\"\n    rhs: CallStack\n- warn:\n    lhs: GHC.Stack.HasCallStack\n    name: \"Use 'HasCallStack' from Relude\"\n    note: \"'HasCallStack' is already exported from Relude\"\n    rhs: HasCallStack\n- warn:\n    lhs: GHC.Stack.callStack\n    name: \"Use 'callStack' from Relude\"\n    note: \"'callStack' is already exported from Relude\"\n    rhs: callStack\n- warn:\n    lhs: GHC.Stack.currentCallStack\n    name: \"Use 'currentCallStack' from Relude\"\n    note: \"'currentCallStack' is already exported from Relude\"\n    rhs: currentCallStack\n- warn:\n    lhs: GHC.Stack.getCallStack\n    name: \"Use 'getCallStack' from Relude\"\n    note: \"'getCallStack' is already exported from Relude\"\n    rhs: getCallStack\n- warn:\n    lhs: GHC.Stack.prettyCallStack\n    name: \"Use 'prettyCallStack' from Relude\"\n    note: \"'prettyCallStack' is already exported from Relude\"\n    rhs: prettyCallStack\n- warn:\n    lhs: GHC.Stack.prettySrcLoc\n    name: \"Use 'prettySrcLoc' from Relude\"\n    note: \"'prettySrcLoc' is already exported from Relude\"\n    rhs: prettySrcLoc\n- warn:\n    lhs: GHC.Stack.withFrozenCallStack\n    name: \"Use 'withFrozenCallStack' from Relude\"\n    note: \"'withFrozenCallStack' is already exported from Relude\"\n    rhs: withFrozenCallStack\n- warn:\n    lhs: Data.Bifoldable.Bifoldable\n    name: \"Use 'Bifoldable' from Relude\"\n    note: \"'Bifoldable' is already exported from Relude\"\n    rhs: Bifoldable\n- warn:\n    lhs: Data.Bifoldable.bifold\n    name: \"Use 'bifold' from Relude\"\n    note: \"'bifold' is already exported from Relude\"\n    rhs: bifold\n- warn:\n    lhs: Data.Bifoldable.bifoldMap\n    name: \"Use 'bifoldMap' from Relude\"\n    note: \"'bifoldMap' is already exported from Relude\"\n    rhs: bifoldMap\n- warn:\n    lhs: Data.Bifoldable.bifoldr\n    name: \"Use 'bifoldr' from Relude\"\n    note: \"'bifoldr' is already exported from Relude\"\n    rhs: bifoldr\n- warn:\n    lhs: Data.Bifoldable.bifoldl\n    name: \"Use 'bifoldl' from Relude\"\n    note: \"'bifoldl' is already exported from Relude\"\n    rhs: bifoldl\n- warn:\n    lhs: \"Data.Bifoldable.bifoldl'\"\n    name: \"Use 'bifoldl'' from Relude\"\n    note: \"'bifoldl'' is already exported from Relude\"\n    rhs: \"bifoldl'\"\n- warn:\n    lhs: Data.Bifoldable.bifoldlM\n    name: \"Use 'bifoldlM' from Relude\"\n    note: \"'bifoldlM' is already exported from Relude\"\n    rhs: bifoldlM\n- warn:\n    lhs: \"Data.Bifoldable.bifoldr'\"\n    name: \"Use 'bifoldr'' from Relude\"\n    note: \"'bifoldr'' is already exported from Relude\"\n    rhs: \"bifoldr'\"\n- warn:\n    lhs: Data.Bifoldable.bifoldrM\n    name: \"Use 'bifoldrM' from Relude\"\n    note: \"'bifoldrM' is already exported from Relude\"\n    rhs: bifoldrM\n- warn:\n    lhs: Data.Bifoldable.bitraverse_\n    name: \"Use 'bitraverse_' from Relude\"\n    note: \"'bitraverse_' is already exported from Relude\"\n    rhs: bitraverse_\n- warn:\n    lhs: Data.Bifoldable.bifor_\n    name: \"Use 'bifor_' from Relude\"\n    note: \"'bifor_' is already exported from Relude\"\n    rhs: bifor_\n- warn:\n    lhs: Data.Bifoldable.biasum\n    name: \"Use 'biasum' from Relude\"\n    note: \"'biasum' is already exported from Relude\"\n    rhs: biasum\n- warn:\n    lhs: Data.Bifoldable.bisequence_\n    name: \"Use 'bisequence_' from Relude\"\n    note: \"'bisequence_' is already exported from Relude\"\n    rhs: bisequence_\n- warn:\n    lhs: Data.Bifoldable.biList\n    name: \"Use 'biList' from Relude\"\n    note: \"'biList' is already exported from Relude\"\n    rhs: biList\n- warn:\n    lhs: Data.Bifoldable.binull\n    name: \"Use 'binull' from Relude\"\n    note: \"'binull' is already exported from Relude\"\n    rhs: binull\n- warn:\n    lhs: Data.Bifoldable.bilength\n    name: \"Use 'bilength' from Relude\"\n    note: \"'bilength' is already exported from Relude\"\n    rhs: bilength\n- warn:\n    lhs: Data.Bifoldable.bielem\n    name: \"Use 'bielem' from Relude\"\n    note: \"'bielem' is already exported from Relude\"\n    rhs: bielem\n- warn:\n    lhs: Data.Bifoldable.biand\n    name: \"Use 'biand' from Relude\"\n    note: \"'biand' is already exported from Relude\"\n    rhs: biand\n- warn:\n    lhs: Data.Bifoldable.bior\n    name: \"Use 'bior' from Relude\"\n    note: \"'bior' is already exported from Relude\"\n    rhs: bior\n- warn:\n    lhs: Data.Bifoldable.biany\n    name: \"Use 'biany' from Relude\"\n    note: \"'biany' is already exported from Relude\"\n    rhs: biany\n- warn:\n    lhs: Data.Bifoldable.biall\n    name: \"Use 'biall' from Relude\"\n    note: \"'biall' is already exported from Relude\"\n    rhs: biall\n- warn:\n    lhs: Data.Bifoldable.bifind\n    name: \"Use 'bifind' from Relude\"\n    note: \"'bifind' is already exported from Relude\"\n    rhs: bifind\n- warn:\n    lhs: Data.Bitraversable.Bitraversable\n    name: \"Use 'Bitraversable' from Relude\"\n    note: \"'Bitraversable' is already exported from Relude\"\n    rhs: Bitraversable\n- warn:\n    lhs: Data.Bitraversable.bitraverse\n    name: \"Use 'bitraverse' from Relude\"\n    note: \"'bitraverse' is already exported from Relude\"\n    rhs: bitraverse\n- warn:\n    lhs: Data.Bitraversable.bisequence\n    name: \"Use 'bisequence' from Relude\"\n    note: \"'bisequence' is already exported from Relude\"\n    rhs: bisequence\n- warn:\n    lhs: Data.Bitraversable.bifor\n    name: \"Use 'bifor' from Relude\"\n    note: \"'bifor' is already exported from Relude\"\n    rhs: bifor\n- warn:\n    lhs: Data.Bitraversable.bimapDefault\n    name: \"Use 'bimapDefault' from Relude\"\n    note: \"'bimapDefault' is already exported from Relude\"\n    rhs: bimapDefault\n- warn:\n    lhs: Data.Bitraversable.bifoldMapDefault\n    name: \"Use 'bifoldMapDefault' from Relude\"\n    note: \"'bifoldMapDefault' is already exported from Relude\"\n    rhs: bifoldMapDefault\n- warn:\n    lhs: Control.Monad.guard\n    name: \"Use 'guard' from Relude\"\n    note: \"'guard' is already exported from Relude\"\n    rhs: guard\n- warn:\n    lhs: Control.Monad.unless\n    name: \"Use 'unless' from Relude\"\n    note: \"'unless' is already exported from Relude\"\n    rhs: unless\n- warn:\n    lhs: Control.Monad.when\n    name: \"Use 'when' from Relude\"\n    note: \"'when' is already exported from Relude\"\n    rhs: when\n- warn:\n    lhs: Data.Bool.bool\n    name: \"Use 'bool' from Relude\"\n    note: \"'bool' is already exported from Relude\"\n    rhs: bool\n- warn:\n    lhs: Data.Hashable.Hashable\n    name: \"Use 'Hashable' from Relude\"\n    note: \"'Hashable' is already exported from Relude\"\n    rhs: Hashable\n- warn:\n    lhs: Data.Hashable.hashWithSalt\n    name: \"Use 'hashWithSalt' from Relude\"\n    note: \"'hashWithSalt' is already exported from Relude\"\n    rhs: hashWithSalt\n- warn:\n    lhs: Data.HashMap.Strict.HashMap\n    name: \"Use 'HashMap' from Relude\"\n    note: \"'HashMap' is already exported from Relude\"\n    rhs: HashMap\n- warn:\n    lhs: Data.HashSet.HashSet\n    name: \"Use 'HashSet' from Relude\"\n    note: \"'HashSet' is already exported from Relude\"\n    rhs: HashSet\n- warn:\n    lhs: Data.IntMap.Strict.IntMap\n    name: \"Use 'IntMap' from Relude\"\n    note: \"'IntMap' is already exported from Relude\"\n    rhs: IntMap\n- warn:\n    lhs: Data.IntSet.IntSet\n    name: \"Use 'IntSet' from Relude\"\n    note: \"'IntSet' is already exported from Relude\"\n    rhs: IntSet\n- warn:\n    lhs: Data.Map.Strict.Map\n    name: \"Use 'Map' from Relude\"\n    note: \"'Map' is already exported from Relude\"\n    rhs: Map\n- warn:\n    lhs: Data.Sequence.Sequence\n    name: \"Use 'Sequence' from Relude\"\n    note: \"'Sequence' is already exported from Relude\"\n    rhs: Sequence\n- warn:\n    lhs: Data.Set.Set\n    name: \"Use 'Set' from Relude\"\n    note: \"'Set' is already exported from Relude\"\n    rhs: Set\n- warn:\n    lhs: Data.Tuple.swap\n    name: \"Use 'swap' from Relude\"\n    note: \"'swap' is already exported from Relude\"\n    rhs: swap\n- warn:\n    lhs: Data.Vector.Vector\n    name: \"Use 'Vector' from Relude\"\n    note: \"'Vector' is already exported from Relude\"\n    rhs: Vector\n- warn:\n    lhs: GHC.Exts.IsList\n    name: \"Use 'IsList' from Relude\"\n    note: \"'IsList' is already exported from Relude\"\n    rhs: IsList\n- warn:\n    lhs: GHC.Exts.fromList\n    name: \"Use 'fromList' from Relude\"\n    note: \"'fromList' is already exported from Relude\"\n    rhs: fromList\n- warn:\n    lhs: GHC.Exts.fromListN\n    name: \"Use 'fromListN' from Relude\"\n    note: \"'fromListN' is already exported from Relude\"\n    rhs: fromListN\n- warn:\n    lhs: Debug.Trace.trace\n    name: \"Use 'trace' from Relude\"\n    note: \"'trace' is already exported from Relude\"\n    rhs: trace\n- warn:\n    lhs: Debug.Trace.traceShow\n    name: \"Use 'traceShow' from Relude\"\n    note: \"'traceShow' is already exported from Relude\"\n    rhs: traceShow\n- warn:\n    lhs: Debug.Trace.traceShowId\n    name: \"Use 'traceShowId' from Relude\"\n    note: \"'traceShowId' is already exported from Relude\"\n    rhs: traceShowId\n- warn:\n    lhs: Debug.Trace.traceShowM\n    name: \"Use 'traceShowM' from Relude\"\n    note: \"'traceShowM' is already exported from Relude\"\n    rhs: traceShowM\n- warn:\n    lhs: Debug.Trace.traceM\n    name: \"Use 'traceM' from Relude\"\n    note: \"'traceM' is already exported from Relude\"\n    rhs: traceM\n- warn:\n    lhs: Debug.Trace.traceId\n    name: \"Use 'traceId' from Relude\"\n    note: \"'traceId' is already exported from Relude\"\n    rhs: traceId\n- warn:\n    lhs: Control.DeepSeq.NFData\n    name: \"Use 'NFData' from Relude\"\n    note: \"'NFData' is already exported from Relude\"\n    rhs: NFData\n- warn:\n    lhs: Control.DeepSeq.rnf\n    name: \"Use 'rnf' from Relude\"\n    note: \"'rnf' is already exported from Relude\"\n    rhs: rnf\n- warn:\n    lhs: Control.DeepSeq.deepseq\n    name: \"Use 'deepseq' from Relude\"\n    note: \"'deepseq' is already exported from Relude\"\n    rhs: deepseq\n- warn:\n    lhs: Control.DeepSeq.force\n    name: \"Use 'force' from Relude\"\n    note: \"'force' is already exported from Relude\"\n    rhs: force\n- warn:\n    lhs: \"(Control.DeepSeq.$!!)\"\n    name: \"Use '$!!' from Relude\"\n    note: \"Operator '($!!)' is already exported from Relude\"\n    rhs: \"($!!)\"\n- warn:\n    lhs: Control.Exception.Exception\n    name: \"Use 'Exception' from Relude\"\n    note: \"'Exception' is already exported from Relude\"\n    rhs: Exception\n- warn:\n    lhs: Control.Exception.SomeException\n    name: \"Use 'SomeException' from Relude\"\n    note: \"'SomeException' is already exported from Relude\"\n    rhs: SomeException\n- warn:\n    lhs: Control.Exception.toException\n    name: \"Use 'toException' from Relude\"\n    note: \"'toException' is already exported from Relude\"\n    rhs: toException\n- warn:\n    lhs: Control.Exception.fromException\n    name: \"Use 'fromException' from Relude\"\n    note: \"'fromException' is already exported from Relude\"\n    rhs: fromException\n- warn:\n    lhs: Control.Exception.displayException\n    name: \"Use 'displayException' from Relude\"\n    note: \"'displayException' is already exported from Relude\"\n    rhs: displayException\n- warn:\n    lhs: Data.Foldable.asum\n    name: \"Use 'asum' from Relude\"\n    note: \"'asum' is already exported from Relude\"\n    rhs: asum\n- warn:\n    lhs: Data.Foldable.find\n    name: \"Use 'find' from Relude\"\n    note: \"'find' is already exported from Relude\"\n    rhs: find\n- warn:\n    lhs: Data.Foldable.find\n    name: \"Use 'find' from Relude\"\n    note: \"'find' is already exported from Relude\"\n    rhs: find\n# - warn:\n#     lhs: Data.Foldable.fold\n#     name: \"Use 'fold' from Relude\"\n#     note: \"'fold' is already exported from Relude\"\n#     rhs: fold\n- warn:\n    lhs: \"Data.Foldable.foldl'\"\n    name: \"Use 'foldl'' from Relude\"\n    note: \"'foldl'' is already exported from Relude\"\n    rhs: \"foldl'\"\n- warn:\n    lhs: Data.Foldable.forM_\n    name: \"Use 'forM_' from Relude\"\n    note: \"'forM_' is already exported from Relude\"\n    rhs: forM_\n- warn:\n    lhs: Data.Foldable.for_\n    name: \"Use 'for_' from Relude\"\n    note: \"'for_' is already exported from Relude\"\n    rhs: for_\n- warn:\n    lhs: Data.Foldable.sequenceA_\n    name: \"Use 'sequenceA_' from Relude\"\n    note: \"'sequenceA_' is already exported from Relude\"\n    rhs: sequenceA_\n- warn:\n    lhs: Data.Foldable.toList\n    name: \"Use 'toList' from Relude\"\n    note: \"'toList' is already exported from Relude\"\n    rhs: toList\n- warn:\n    lhs: Data.Foldable.traverse_\n    name: \"Use 'traverse_' from Relude\"\n    note: \"'traverse_' is already exported from Relude\"\n    rhs: traverse_\n- warn:\n    lhs: Data.Traversable.forM\n    name: \"Use 'forM' from Relude\"\n    note: \"'forM' is already exported from Relude\"\n    rhs: forM\n- warn:\n    lhs: Data.Traversable.mapAccumL\n    name: \"Use 'mapAccumL' from Relude\"\n    note: \"'mapAccumL' is already exported from Relude\"\n    rhs: mapAccumL\n- warn:\n    lhs: Data.Traversable.mapAccumR\n    name: \"Use 'mapAccumR' from Relude\"\n    note: \"'mapAccumR' is already exported from Relude\"\n    rhs: mapAccumR\n- warn:\n    lhs: \"(Control.Arrow.&&&)\"\n    name: \"Use '&&&' from Relude\"\n    note: \"Operator '(&&&)' is already exported from Relude\"\n    rhs: \"(&&&)\"\n- warn:\n    lhs: \"(Control.Category.>>>)\"\n    name: \"Use '>>>' from Relude\"\n    note: \"Operator '(>>>)' is already exported from Relude\"\n    rhs: \"(>>>)\"\n- warn:\n    lhs: \"(Control.Category.<<<)\"\n    name: \"Use '<<<' from Relude\"\n    note: \"Operator '(<<<)' is already exported from Relude\"\n    rhs: \"(<<<)\"\n- warn:\n    lhs: Data.Function.fix\n    name: \"Use 'fix' from Relude\"\n    note: \"'fix' is already exported from Relude\"\n    rhs: fix\n- warn:\n    lhs: Data.Function.on\n    name: \"Use 'on' from Relude\"\n    note: \"'on' is already exported from Relude\"\n    rhs: 'on'\n- warn:\n    lhs: Data.Bifunctor.Bifunctor\n    name: \"Use 'Bifunctor' from Relude\"\n    note: \"'Bifunctor' is already exported from Relude\"\n    rhs: Bifunctor\n- warn:\n    lhs: Data.Bifunctor.bimap\n    name: \"Use 'bimap' from Relude\"\n    note: \"'bimap' is already exported from Relude\"\n    rhs: bimap\n- warn:\n    lhs: Data.Bifunctor.first\n    name: \"Use 'first' from Relude\"\n    note: \"'first' is already exported from Relude\"\n    rhs: first\n- warn:\n    lhs: Data.Bifunctor.second\n    name: \"Use 'second' from Relude\"\n    note: \"'second' is already exported from Relude\"\n    rhs: second\n- warn:\n    lhs: Data.Functor.void\n    name: \"Use 'void' from Relude\"\n    note: \"'void' is already exported from Relude\"\n    rhs: void\n- warn:\n    lhs: \"(Data.Functor.$>)\"\n    name: \"Use '$>' from Relude\"\n    note: \"Operator '($>)' is already exported from Relude\"\n    rhs: \"($>)\"\n- warn:\n    lhs: \"(Data.Functor.<&>)\"\n    name: \"Use '<&>' from Relude\"\n    note: \"Operator '(<&>)' is already exported from Relude\"\n    rhs: \"(<&>)\"\n- warn:\n    lhs: Data.Functor.Compose.Compose\n    name: \"Use 'Compose' from Relude\"\n    note: \"'Compose' is already exported from Relude\"\n    rhs: Compose\n- warn:\n    lhs: Data.Functor.Compose.getCompose\n    name: \"Use 'getCompose' from Relude\"\n    note: \"'getCompose' is already exported from Relude\"\n    rhs: getCompose\n- warn:\n    lhs: Data.Functor.Identity.Identity\n    name: \"Use 'Identity' from Relude\"\n    note: \"'Identity' is already exported from Relude\"\n    rhs: Identity\n- warn:\n    lhs: Data.Functor.Identity.runIdentity\n    name: \"Use 'runIdentity' from Relude\"\n    note: \"'runIdentity' is already exported from Relude\"\n    rhs: runIdentity\n- warn:\n    lhs: Control.Concurrent.MVar.MVar\n    name: \"Use 'MVar' from Relude\"\n    note: \"'MVar' is already exported from Relude\"\n    rhs: MVar\n- warn:\n    lhs: Control.Concurrent.MVar.newEmptyMVar\n    name: \"Use 'newEmptyMVar' from Relude\"\n    note: \"'newEmptyMVar' is already exported from Relude\"\n    rhs: newEmptyMVar\n- warn:\n    lhs: Control.Concurrent.MVar.newMVar\n    name: \"Use 'newMVar' from Relude\"\n    note: \"'newMVar' is already exported from Relude\"\n    rhs: newMVar\n- warn:\n    lhs: Control.Concurrent.MVar.putMVar\n    name: \"Use 'putMVar' from Relude\"\n    note: \"'putMVar' is already exported from Relude\"\n    rhs: putMVar\n- warn:\n    lhs: Control.Concurrent.MVar.readMVar\n    name: \"Use 'readMVar' from Relude\"\n    note: \"'readMVar' is already exported from Relude\"\n    rhs: readMVar\n- warn:\n    lhs: Control.Concurrent.MVar.swapMVar\n    name: \"Use 'swapMVar' from Relude\"\n    note: \"'swapMVar' is already exported from Relude\"\n    rhs: swapMVar\n- warn:\n    lhs: Control.Concurrent.MVar.takeMVar\n    name: \"Use 'takeMVar' from Relude\"\n    note: \"'takeMVar' is already exported from Relude\"\n    rhs: takeMVar\n- warn:\n    lhs: Control.Concurrent.MVar.tryPutMVar\n    name: \"Use 'tryPutMVar' from Relude\"\n    note: \"'tryPutMVar' is already exported from Relude\"\n    rhs: tryPutMVar\n- warn:\n    lhs: Control.Concurrent.MVar.tryReadMVar\n    name: \"Use 'tryReadMVar' from Relude\"\n    note: \"'tryReadMVar' is already exported from Relude\"\n    rhs: tryReadMVar\n- warn:\n    lhs: Control.Concurrent.MVar.tryTakeMVar\n    name: \"Use 'tryTakeMVar' from Relude\"\n    note: \"'tryTakeMVar' is already exported from Relude\"\n    rhs: tryTakeMVar\n- warn:\n    lhs: Control.Monad.STM.STM\n    name: \"Use 'STM' from Relude\"\n    note: \"'STM' is already exported from Relude\"\n    rhs: STM\n- warn:\n    lhs: Control.Monad.STM.atomically\n    name: \"Use 'atomically' from Relude\"\n    note: \"'atomically' is already exported from Relude\"\n    rhs: atomically\n- warn:\n    lhs: Control.Monad.STM.throwSTM\n    name: \"Use 'throwSTM' from Relude\"\n    note: \"'throwSTM' is already exported from Relude\"\n    rhs: throwSTM\n- warn:\n    lhs: Control.Monad.STM.catchSTM\n    name: \"Use 'catchSTM' from Relude\"\n    note: \"'catchSTM' is already exported from Relude\"\n    rhs: catchSTM\n- warn:\n    lhs: Control.Concurrent.STM.TVar.TVar\n    name: \"Use 'TVar' from Relude\"\n    note: \"'TVar' is already exported from Relude\"\n    rhs: TVar\n- warn:\n    lhs: Control.Concurrent.STM.TVar.newTVarIO\n    name: \"Use 'newTVarIO' from Relude\"\n    note: \"'newTVarIO' is already exported from Relude\"\n    rhs: newTVarIO\n- warn:\n    lhs: Control.Concurrent.STM.TVar.readTVarIO\n    name: \"Use 'readTVarIO' from Relude\"\n    note: \"'readTVarIO' is already exported from Relude\"\n    rhs: readTVarIO\n- warn:\n    lhs: \"Control.Concurrent.STM.TVar.modifyTVar'\"\n    name: \"Use 'modifyTVar'' from Relude\"\n    note: \"'modifyTVar'' is already exported from Relude\"\n    rhs: \"modifyTVar'\"\n- warn:\n    lhs: Control.Concurrent.STM.TVar.newTVar\n    name: \"Use 'newTVar' from Relude\"\n    note: \"'newTVar' is already exported from Relude\"\n    rhs: newTVar\n- warn:\n    lhs: Control.Concurrent.STM.TVar.readTVar\n    name: \"Use 'readTVar' from Relude\"\n    note: \"'readTVar' is already exported from Relude\"\n    rhs: readTVar\n- warn:\n    lhs: Control.Concurrent.STM.TVar.writeTVar\n    name: \"Use 'writeTVar' from Relude\"\n    note: \"'writeTVar' is already exported from Relude\"\n    rhs: writeTVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.TMVar\n    name: \"Use 'TMVar' from Relude\"\n    note: \"'TMVar' is already exported from Relude\"\n    rhs: TMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.newTMVar\n    name: \"Use 'newTMVar' from Relude\"\n    note: \"'newTMVar' is already exported from Relude\"\n    rhs: newTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.newEmptyTMVar\n    name: \"Use 'newEmptyTMVar' from Relude\"\n    note: \"'newEmptyTMVar' is already exported from Relude\"\n    rhs: newEmptyTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.newTMVarIO\n    name: \"Use 'newTMVarIO' from Relude\"\n    note: \"'newTMVarIO' is already exported from Relude\"\n    rhs: newTMVarIO\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.newEmptyTMVarIO\n    name: \"Use 'newEmptyTMVarIO' from Relude\"\n    note: \"'newEmptyTMVarIO' is already exported from Relude\"\n    rhs: newEmptyTMVarIO\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.takeTMVar\n    name: \"Use 'takeTMVar' from Relude\"\n    note: \"'takeTMVar' is already exported from Relude\"\n    rhs: takeTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.putTMVar\n    name: \"Use 'putTMVar' from Relude\"\n    note: \"'putTMVar' is already exported from Relude\"\n    rhs: putTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.readTMVar\n    name: \"Use 'readTMVar' from Relude\"\n    note: \"'readTMVar' is already exported from Relude\"\n    rhs: readTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.tryReadTMVar\n    name: \"Use 'tryReadTMVar' from Relude\"\n    note: \"'tryReadTMVar' is already exported from Relude\"\n    rhs: tryReadTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.swapTMVar\n    name: \"Use 'swapTMVar' from Relude\"\n    note: \"'swapTMVar' is already exported from Relude\"\n    rhs: swapTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.tryTakeTMVar\n    name: \"Use 'tryTakeTMVar' from Relude\"\n    note: \"'tryTakeTMVar' is already exported from Relude\"\n    rhs: tryTakeTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.tryPutTMVar\n    name: \"Use 'tryPutTMVar' from Relude\"\n    note: \"'tryPutTMVar' is already exported from Relude\"\n    rhs: tryPutTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.isEmptyTMVar\n    name: \"Use 'isEmptyTMVar' from Relude\"\n    note: \"'isEmptyTMVar' is already exported from Relude\"\n    rhs: isEmptyTMVar\n- warn:\n    lhs: Control.Concurrent.STM.TMVar.mkWeakTMVar\n    name: \"Use 'mkWeakTMVar' from Relude\"\n    note: \"'mkWeakTMVar' is already exported from Relude\"\n    rhs: mkWeakTMVar\n- warn:\n    lhs: Data.IORef.IORef\n    name: \"Use 'IORef' from Relude\"\n    note: \"'IORef' is already exported from Relude\"\n    rhs: IORef\n- warn:\n    lhs: Data.IORef.atomicModifyIORef\n    name: \"Use 'atomicModifyIORef' from Relude\"\n    note: \"'atomicModifyIORef' is already exported from Relude\"\n    rhs: atomicModifyIORef\n- warn:\n    lhs: \"Data.IORef.atomicModifyIORef'\"\n    name: \"Use 'atomicModifyIORef'' from Relude\"\n    note: \"'atomicModifyIORef'' is already exported from Relude\"\n    rhs: \"atomicModifyIORef'\"\n- warn:\n    lhs: Data.IORef.atomicWriteIORef\n    name: \"Use 'atomicWriteIORef' from Relude\"\n    note: \"'atomicWriteIORef' is already exported from Relude\"\n    rhs: atomicWriteIORef\n- warn:\n    lhs: Data.IORef.modifyIORef\n    name: \"Use 'modifyIORef' from Relude\"\n    note: \"'modifyIORef' is already exported from Relude\"\n    rhs: modifyIORef\n- warn:\n    lhs: \"Data.IORef.modifyIORef'\"\n    name: \"Use 'modifyIORef'' from Relude\"\n    note: \"'modifyIORef'' is already exported from Relude\"\n    rhs: \"modifyIORef'\"\n- warn:\n    lhs: Data.IORef.newIORef\n    name: \"Use 'newIORef' from Relude\"\n    note: \"'newIORef' is already exported from Relude\"\n    rhs: newIORef\n- warn:\n    lhs: Data.IORef.readIORef\n    name: \"Use 'readIORef' from Relude\"\n    note: \"'readIORef' is already exported from Relude\"\n    rhs: readIORef\n- warn:\n    lhs: Data.IORef.writeIORef\n    name: \"Use 'writeIORef' from Relude\"\n    note: \"'writeIORef' is already exported from Relude\"\n    rhs: writeIORef\n- warn:\n    lhs: \"atomicModifyIORef ref (\\\\a -> (f a, ()))\"\n    rhs: atomicModifyIORef_ ref f\n- warn:\n    lhs: \"atomicModifyIORef ref $ \\\\a -> (f a, ())\"\n    rhs: atomicModifyIORef_ ref f\n- warn:\n    lhs: \"atomicModifyIORef' ref $ \\\\a -> (f a, ())\"\n    rhs: \"atomicModifyIORef'_ ref f\"\n- warn:\n    lhs: \"atomicModifyIORef' ref (\\\\a -> (f a, ()))\"\n    rhs: \"atomicModifyIORef'_ ref f\"\n- warn:\n    lhs: Data.Text.IO.getLine\n    name: \"Use 'getLine' from Relude\"\n    note: \"'getLine' is already exported from Relude\"\n    rhs: getLine\n- warn:\n    lhs: System.IO.hFlush\n    name: \"Use 'hFlush' from Relude\"\n    note: \"'hFlush' is already exported from Relude\"\n    rhs: hFlush\n- warn:\n    lhs: System.IO.hIsEOF\n    name: \"Use 'hIsEOF' from Relude\"\n    note: \"'hIsEOF' is already exported from Relude\"\n    rhs: hIsEOF\n- warn:\n    lhs: System.IO.hSetBuffering\n    name: \"Use 'hSetBuffering' from Relude\"\n    note: \"'hSetBuffering' is already exported from Relude\"\n    rhs: hSetBuffering\n- warn:\n    lhs: System.IO.hGetBuffering\n    name: \"Use 'hGetBuffering' from Relude\"\n    note: \"'hGetBuffering' is already exported from Relude\"\n    rhs: hGetBuffering\n- warn:\n    lhs: System.IO.Handle\n    name: \"Use 'Handle' from Relude\"\n    note: \"'Handle' is already exported from Relude\"\n    rhs: Handle\n- warn:\n    lhs: System.IO.stdin\n    name: \"Use 'stdin' from Relude\"\n    note: \"'stdin' is already exported from Relude\"\n    rhs: stdin\n- warn:\n    lhs: System.IO.stdout\n    name: \"Use 'stdout' from Relude\"\n    note: \"'stdout' is already exported from Relude\"\n    rhs: stdout\n- warn:\n    lhs: System.IO.stderr\n    name: \"Use 'stderr' from Relude\"\n    note: \"'stderr' is already exported from Relude\"\n    rhs: stderr\n- warn:\n    lhs: System.IO.withFile\n    name: \"Use 'withFile' from Relude\"\n    note: \"'withFile' is already exported from Relude\"\n    rhs: withFile\n- warn:\n    lhs: System.IO.BufferMode\n    name: \"Use 'BufferMode' from Relude\"\n    note: \"'BufferMode' is already exported from Relude\"\n    rhs: BufferMode\n- warn:\n    lhs: System.Environment.getArgs\n    name: \"Use 'getArgs' from Relude\"\n    note: \"'getArgs' is already exported from Relude\"\n    rhs: getArgs\n- warn:\n    lhs: System.Environment.lookupEnv\n    name: \"Use 'lookupEnv' from Relude\"\n    note: \"'lookupEnv' is already exported from Relude\"\n    rhs: lookupEnv\n- warn:\n    lhs: Data.List.genericDrop\n    name: \"Use 'genericDrop' from Relude\"\n    note: \"'genericDrop' is already exported from Relude\"\n    rhs: genericDrop\n- warn:\n    lhs: Data.List.genericLength\n    name: \"Use 'genericLength' from Relude\"\n    note: \"'genericLength' is already exported from Relude\"\n    rhs: genericLength\n- warn:\n    lhs: Data.List.genericReplicate\n    name: \"Use 'genericReplicate' from Relude\"\n    note: \"'genericReplicate' is already exported from Relude\"\n    rhs: genericReplicate\n- warn:\n    lhs: Data.List.genericSplitAt\n    name: \"Use 'genericSplitAt' from Relude\"\n    note: \"'genericSplitAt' is already exported from Relude\"\n    rhs: genericSplitAt\n- warn:\n    lhs: Data.List.genericTake\n    name: \"Use 'genericTake' from Relude\"\n    note: \"'genericTake' is already exported from Relude\"\n    rhs: genericTake\n- warn:\n    lhs: Data.List.group\n    name: \"Use 'group' from Relude\"\n    note: \"'group' is already exported from Relude\"\n    rhs: group\n- warn:\n    lhs: Data.List.inits\n    name: \"Use 'inits' from Relude\"\n    note: \"'inits' is already exported from Relude\"\n    rhs: inits\n- warn:\n    lhs: Data.List.intercalate\n    name: \"Use 'intercalate' from Relude\"\n    note: \"'intercalate' is already exported from Relude\"\n    rhs: intercalate\n- warn:\n    lhs: Data.List.intersperse\n    name: \"Use 'intersperse' from Relude\"\n    note: \"'intersperse' is already exported from Relude\"\n    rhs: intersperse\n- warn:\n    lhs: Data.List.isPrefixOf\n    name: \"Use 'isPrefixOf' from Relude\"\n    note: \"'isPrefixOf' is already exported from Relude\"\n    rhs: isPrefixOf\n- warn:\n    lhs: Data.List.permutations\n    name: \"Use 'permutations' from Relude\"\n    note: \"'permutations' is already exported from Relude\"\n    rhs: permutations\n- warn:\n    lhs: \"Data.List.scanl'\"\n    name: \"Use 'scanl'' from Relude\"\n    note: \"'scanl'' is already exported from Relude\"\n    rhs: \"scanl'\"\n- warn:\n    lhs: Data.List.sort\n    name: \"Use 'sort' from Relude\"\n    note: \"'sort' is already exported from Relude\"\n    rhs: sort\n- warn:\n    lhs: Data.List.sortBy\n    name: \"Use 'sortBy' from Relude\"\n    note: \"'sortBy' is already exported from Relude\"\n    rhs: sortBy\n- warn:\n    lhs: Data.List.sortOn\n    name: \"Use 'sortOn' from Relude\"\n    note: \"'sortOn' is already exported from Relude\"\n    rhs: sortOn\n- warn:\n    lhs: Data.List.subsequences\n    name: \"Use 'subsequences' from Relude\"\n    note: \"'subsequences' is already exported from Relude\"\n    rhs: subsequences\n- warn:\n    lhs: Data.List.tails\n    name: \"Use 'tails' from Relude\"\n    note: \"'tails' is already exported from Relude\"\n    rhs: tails\n- warn:\n    lhs: Data.List.transpose\n    name: \"Use 'transpose' from Relude\"\n    note: \"'transpose' is already exported from Relude\"\n    rhs: transpose\n- warn:\n    lhs: Data.List.uncons\n    name: \"Use 'uncons' from Relude\"\n    note: \"'uncons' is already exported from Relude\"\n    rhs: uncons\n- warn:\n    lhs: Data.List.unfoldr\n    name: \"Use 'unfoldr' from Relude\"\n    note: \"'unfoldr' is already exported from Relude\"\n    rhs: unfoldr\n- warn:\n    lhs: Data.List.NonEmpty.NonEmpty\n    name: \"Use 'NonEmpty' from Relude\"\n    note: \"'NonEmpty' is already exported from Relude\"\n    rhs: NonEmpty\n- warn:\n    lhs: \"(Data.List.NonEmpty.:|)\"\n    name: \"Use ':|' from Relude\"\n    note: \"Operator '(:|)' is already exported from Relude\"\n    rhs: \"(:|)\"\n- warn:\n    lhs: Data.List.NonEmpty.nonEmpty\n    name: \"Use 'nonEmpty' from Relude\"\n    note: \"'nonEmpty' is already exported from Relude\"\n    rhs: nonEmpty\n- warn:\n    lhs: Data.List.NonEmpty.head\n    name: \"Use 'head' from Relude\"\n    note: \"'head' is already exported from Relude\"\n    rhs: head\n- warn:\n    lhs: Data.List.NonEmpty.init\n    name: \"Use 'init' from Relude\"\n    note: \"'init' is already exported from Relude\"\n    rhs: init\n- warn:\n    lhs: Data.List.NonEmpty.last\n    name: \"Use 'last' from Relude\"\n    note: \"'last' is already exported from Relude\"\n    rhs: last\n- warn:\n    lhs: Data.List.NonEmpty.tail\n    name: \"Use 'tail' from Relude\"\n    note: \"'tail' is already exported from Relude\"\n    rhs: tail\n- warn:\n    lhs: GHC.Exts.sortWith\n    name: \"Use 'sortWith' from Relude\"\n    note: \"'sortWith' is already exported from Relude\"\n    rhs: sortWith\n- warn:\n    lhs: Control.Monad.Except.ExceptT\n    name: \"Use 'ExceptT' from Relude\"\n    note: \"'ExceptT' is already exported from Relude\"\n    rhs: ExceptT\n- warn:\n    lhs: Control.Monad.Except.runExceptT\n    name: \"Use 'runExceptT' from Relude\"\n    note: \"'runExceptT' is already exported from Relude\"\n    rhs: runExceptT\n- warn:\n    lhs: Control.Monad.Reader.MonadReader\n    name: \"Use 'MonadReader' from Relude\"\n    note: \"'MonadReader' is already exported from Relude\"\n    rhs: MonadReader\n- warn:\n    lhs: Control.Monad.Reader.Reader\n    name: \"Use 'Reader' from Relude\"\n    note: \"'Reader' is already exported from Relude\"\n    rhs: Reader\n- warn:\n    lhs: Control.Monad.Reader.ReaderT\n    name: \"Use 'ReaderT' from Relude\"\n    note: \"'ReaderT' is already exported from Relude\"\n    rhs: ReaderT\n- warn:\n    lhs: Control.Monad.Reader.runReaderT\n    name: \"Use 'runReaderT' from Relude\"\n    note: \"'runReaderT' is already exported from Relude\"\n    rhs: runReaderT\n- warn:\n    lhs: Control.Monad.Reader.ask\n    name: \"Use 'ask' from Relude\"\n    note: \"'ask' is already exported from Relude\"\n    rhs: ask\n- warn:\n    lhs: Control.Monad.Reader.asks\n    name: \"Use 'asks' from Relude\"\n    note: \"'asks' is already exported from Relude\"\n    rhs: asks\n- warn:\n    lhs: Control.Monad.Reader.local\n    name: \"Use 'local' from Relude\"\n    note: \"'local' is already exported from Relude\"\n    rhs: local\n- warn:\n    lhs: Control.Monad.Reader.reader\n    name: \"Use 'reader' from Relude\"\n    note: \"'reader' is already exported from Relude\"\n    rhs: reader\n- warn:\n    lhs: Control.Monad.Reader.runReader\n    name: \"Use 'runReader' from Relude\"\n    note: \"'runReader' is already exported from Relude\"\n    rhs: runReader\n- warn:\n    lhs: Control.Monad.Reader.withReader\n    name: \"Use 'withReader' from Relude\"\n    note: \"'withReader' is already exported from Relude\"\n    rhs: withReader\n- warn:\n    lhs: Control.Monad.Reader.withReaderT\n    name: \"Use 'withReaderT' from Relude\"\n    note: \"'withReaderT' is already exported from Relude\"\n    rhs: withReaderT\n- warn:\n    lhs: Control.Monad.State.Strict.MonadState\n    name: \"Use 'MonadState' from Relude\"\n    note: \"'MonadState' is already exported from Relude\"\n    rhs: MonadState\n- warn:\n    lhs: Control.Monad.State.Strict.State\n    name: \"Use 'State' from Relude\"\n    note: \"'State' is already exported from Relude\"\n    rhs: State\n- warn:\n    lhs: Control.Monad.State.Strict.StateT\n    name: \"Use 'StateT' from Relude\"\n    note: \"'StateT' is already exported from Relude\"\n    rhs: StateT\n- warn:\n    lhs: Control.Monad.State.Strict.runStateT\n    name: \"Use 'runStateT' from Relude\"\n    note: \"'runStateT' is already exported from Relude\"\n    rhs: runStateT\n- warn:\n    lhs: Control.Monad.State.Strict.evalState\n    name: \"Use 'evalState' from Relude\"\n    note: \"'evalState' is already exported from Relude\"\n    rhs: evalState\n- warn:\n    lhs: Control.Monad.State.Strict.evalStateT\n    name: \"Use 'evalStateT' from Relude\"\n    note: \"'evalStateT' is already exported from Relude\"\n    rhs: evalStateT\n- warn:\n    lhs: Control.Monad.State.Strict.execState\n    name: \"Use 'execState' from Relude\"\n    note: \"'execState' is already exported from Relude\"\n    rhs: execState\n- warn:\n    lhs: Control.Monad.State.Strict.execStateT\n    name: \"Use 'execStateT' from Relude\"\n    note: \"'execStateT' is already exported from Relude\"\n    rhs: execStateT\n- warn:\n    lhs: Control.Monad.State.Strict.get\n    name: \"Use 'get' from Relude\"\n    note: \"'get' is already exported from Relude\"\n    rhs: get\n- warn:\n    lhs: Control.Monad.State.Strict.gets\n    name: \"Use 'gets' from Relude\"\n    note: \"'gets' is already exported from Relude\"\n    rhs: gets\n- warn:\n    lhs: Control.Monad.State.Strict.modify\n    name: \"Use 'modify' from Relude\"\n    note: \"'modify' is already exported from Relude\"\n    rhs: modify\n- warn:\n    lhs: \"Control.Monad.State.Strict.modify'\"\n    name: \"Use 'modify'' from Relude\"\n    note: \"'modify'' is already exported from Relude\"\n    rhs: \"modify'\"\n- warn:\n    lhs: Control.Monad.State.Strict.put\n    name: \"Use 'put' from Relude\"\n    note: \"'put' is already exported from Relude\"\n    rhs: put\n- warn:\n    lhs: Control.Monad.State.Strict.runState\n    name: \"Use 'runState' from Relude\"\n    note: \"'runState' is already exported from Relude\"\n    rhs: runState\n- warn:\n    lhs: Control.Monad.State.Strict.state\n    name: \"Use 'state' from Relude\"\n    note: \"'state' is already exported from Relude\"\n    rhs: state\n- warn:\n    lhs: Control.Monad.State.Strict.withState\n    name: \"Use 'withState' from Relude\"\n    note: \"'withState' is already exported from Relude\"\n    rhs: withState\n- warn:\n    lhs: Control.Monad.Trans.MonadIO\n    name: \"Use 'MonadIO' from Relude\"\n    note: \"'MonadIO' is already exported from Relude\"\n    rhs: MonadIO\n- warn:\n    lhs: Control.Monad.Trans.MonadTrans\n    name: \"Use 'MonadTrans' from Relude\"\n    note: \"'MonadTrans' is already exported from Relude\"\n    rhs: MonadTrans\n- warn:\n    lhs: Control.Monad.Trans.lift\n    name: \"Use 'lift' from Relude\"\n    note: \"'lift' is already exported from Relude\"\n    rhs: lift\n- warn:\n    lhs: Control.Monad.Trans.liftIO\n    name: \"Use 'liftIO' from Relude\"\n    note: \"'liftIO' is already exported from Relude\"\n    rhs: liftIO\n- warn:\n    lhs: Control.Monad.Trans.Identity.IdentityT\n    name: \"Use 'IdentityT' from Relude\"\n    note: \"'IdentityT' is already exported from Relude\"\n    rhs: IdentityT\n- warn:\n    lhs: Control.Monad.Trans.Identity.runIdentityT\n    name: \"Use 'runIdentityT' from Relude\"\n    note: \"'runIdentityT' is already exported from Relude\"\n    rhs: runIdentityT\n- warn:\n    lhs: Control.Monad.Trans.Maybe.MaybeT\n    name: \"Use 'MaybeT' from Relude\"\n    note: \"'MaybeT' is already exported from Relude\"\n    rhs: MaybeT\n- warn:\n    lhs: Control.Monad.Trans.Maybe.maybeToExceptT\n    name: \"Use 'maybeToExceptT' from Relude\"\n    note: \"'maybeToExceptT' is already exported from Relude\"\n    rhs: maybeToExceptT\n- warn:\n    lhs: Control.Monad.Trans.Maybe.exceptToMaybeT\n    name: \"Use 'exceptToMaybeT' from Relude\"\n    note: \"'exceptToMaybeT' is already exported from Relude\"\n    rhs: exceptToMaybeT\n- warn:\n    lhs: Control.Monad.MonadPlus\n    name: \"Use 'MonadPlus' from Relude\"\n    note: \"'MonadPlus' is already exported from Relude\"\n    rhs: MonadPlus\n- warn:\n    lhs: Control.Monad.mzero\n    name: \"Use 'mzero' from Relude\"\n    note: \"'mzero' is already exported from Relude\"\n    rhs: mzero\n- warn:\n    lhs: Control.Monad.mplus\n    name: \"Use 'mplus' from Relude\"\n    note: \"'mplus' is already exported from Relude\"\n    rhs: mplus\n- warn:\n    lhs: Control.Monad.filterM\n    name: \"Use 'filterM' from Relude\"\n    note: \"'filterM' is already exported from Relude\"\n    rhs: filterM\n- warn:\n    lhs: Control.Monad.forever\n    name: \"Use 'forever' from Relude\"\n    note: \"'forever' is already exported from Relude\"\n    rhs: forever\n- warn:\n    lhs: Control.Monad.join\n    name: \"Use 'join' from Relude\"\n    note: \"'join' is already exported from Relude\"\n    rhs: join\n- warn:\n    lhs: Control.Monad.mapAndUnzipM\n    name: \"Use 'mapAndUnzipM' from Relude\"\n    note: \"'mapAndUnzipM' is already exported from Relude\"\n    rhs: mapAndUnzipM\n- warn:\n    lhs: Control.Monad.mfilter\n    name: \"Use 'mfilter' from Relude\"\n    note: \"'mfilter' is already exported from Relude\"\n    rhs: mfilter\n- warn:\n    lhs: Control.Monad.replicateM\n    name: \"Use 'replicateM' from Relude\"\n    note: \"'replicateM' is already exported from Relude\"\n    rhs: replicateM\n- warn:\n    lhs: Control.Monad.replicateM_\n    name: \"Use 'replicateM_' from Relude\"\n    note: \"'replicateM_' is already exported from Relude\"\n    rhs: replicateM_\n- warn:\n    lhs: Control.Monad.zipWithM\n    name: \"Use 'zipWithM' from Relude\"\n    note: \"'zipWithM' is already exported from Relude\"\n    rhs: zipWithM\n- warn:\n    lhs: Control.Monad.zipWithM_\n    name: \"Use 'zipWithM_' from Relude\"\n    note: \"'zipWithM_' is already exported from Relude\"\n    rhs: zipWithM_\n- warn:\n    lhs: \"(Control.Monad.<$!>)\"\n    name: \"Use '<$!>' from Relude\"\n    note: \"Operator '(<$!>)' is already exported from Relude\"\n    rhs: \"(<$!>)\"\n- warn:\n    lhs: \"(Control.Monad.<=<)\"\n    name: \"Use '<=<' from Relude\"\n    note: \"Operator '(<=<)' is already exported from Relude\"\n    rhs: \"(<=<)\"\n- warn:\n    lhs: \"(Control.Monad.=<<)\"\n    name: \"Use '=<<' from Relude\"\n    note: \"Operator '(=<<)' is already exported from Relude\"\n    rhs: \"(=<<)\"\n- warn:\n    lhs: \"(Control.Monad.>=>)\"\n    name: \"Use '>=>' from Relude\"\n    note: \"Operator '(>=>)' is already exported from Relude\"\n    rhs: \"(>=>)\"\n- warn:\n    lhs: Control.Monad.Fail.MonadFail\n    name: \"Use 'MonadFail' from Relude\"\n    note: \"'MonadFail' is already exported from Relude\"\n    rhs: MonadFail\n- warn:\n    lhs: Data.Maybe.catMaybes\n    name: \"Use 'catMaybes' from Relude\"\n    note: \"'catMaybes' is already exported from Relude\"\n    rhs: catMaybes\n- warn:\n    lhs: Data.Maybe.fromMaybe\n    name: \"Use 'fromMaybe' from Relude\"\n    note: \"'fromMaybe' is already exported from Relude\"\n    rhs: fromMaybe\n- warn:\n    lhs: Data.Maybe.isJust\n    name: \"Use 'isJust' from Relude\"\n    note: \"'isJust' is already exported from Relude\"\n    rhs: isJust\n- warn:\n    lhs: Data.Maybe.isNothing\n    name: \"Use 'isNothing' from Relude\"\n    note: \"'isNothing' is already exported from Relude\"\n    rhs: isNothing\n- warn:\n    lhs: Data.Maybe.listToMaybe\n    name: \"Use 'listToMaybe' from Relude\"\n    note: \"'listToMaybe' is already exported from Relude\"\n    rhs: listToMaybe\n- warn:\n    lhs: Data.Maybe.mapMaybe\n    name: \"Use 'mapMaybe' from Relude\"\n    note: \"'mapMaybe' is already exported from Relude\"\n    rhs: mapMaybe\n- warn:\n    lhs: Data.Maybe.maybeToList\n    name: \"Use 'maybeToList' from Relude\"\n    note: \"'maybeToList' is already exported from Relude\"\n    rhs: maybeToList\n- warn:\n    lhs: Data.Either.isLeft\n    name: \"Use 'isLeft' from Relude\"\n    note: \"'isLeft' is already exported from Relude\"\n    rhs: isLeft\n- warn:\n    lhs: Data.Either.isRight\n    name: \"Use 'isRight' from Relude\"\n    note: \"'isRight' is already exported from Relude\"\n    rhs: isRight\n- warn:\n    lhs: Data.Either.lefts\n    name: \"Use 'lefts' from Relude\"\n    note: \"'lefts' is already exported from Relude\"\n    rhs: lefts\n- warn:\n    lhs: Data.Either.partitionEithers\n    name: \"Use 'partitionEithers' from Relude\"\n    note: \"'partitionEithers' is already exported from Relude\"\n    rhs: partitionEithers\n- warn:\n    lhs: Data.Either.rights\n    name: \"Use 'rights' from Relude\"\n    note: \"'rights' is already exported from Relude\"\n    rhs: rights\n- warn:\n    lhs: Data.Monoid.All\n    name: \"Use 'All' from Relude\"\n    note: \"'All' is already exported from Relude\"\n    rhs: All\n- warn:\n    lhs: Data.Monoid.getAll\n    name: \"Use 'getAll' from Relude\"\n    note: \"'getAll' is already exported from Relude\"\n    rhs: getAll\n- warn:\n    lhs: Data.Monoid.Alt\n    name: \"Use 'Alt' from Relude\"\n    note: \"'Alt' is already exported from Relude\"\n    rhs: Alt\n- warn:\n    lhs: Data.Monoid.getAlt\n    name: \"Use 'getAlt' from Relude\"\n    note: \"'getAlt' is already exported from Relude\"\n    rhs: getAlt\n- warn:\n    lhs: Data.Monoid.Any\n    name: \"Use 'Any' from Relude\"\n    note: \"'Any' is already exported from Relude\"\n    rhs: Any\n- warn:\n    lhs: Data.Monoid.getAny\n    name: \"Use 'getAny' from Relude\"\n    note: \"'getAny' is already exported from Relude\"\n    rhs: getAny\n- warn:\n    lhs: Data.Monoid.Ap\n    name: \"Use 'Ap' from Relude\"\n    note: \"'Ap' is already exported from Relude\"\n    rhs: Ap\n- warn:\n    lhs: Data.Monoid.getAp\n    name: \"Use 'getAp' from Relude\"\n    note: \"'getAp' is already exported from Relude\"\n    rhs: getAp\n- warn:\n    lhs: Data.Monoid.Dual\n    name: \"Use 'Dual' from Relude\"\n    note: \"'Dual' is already exported from Relude\"\n    rhs: Dual\n- warn:\n    lhs: Data.Monoid.getDual\n    name: \"Use 'getDual' from Relude\"\n    note: \"'getDual' is already exported from Relude\"\n    rhs: getDual\n- warn:\n    lhs: Data.Monoid.Endo\n    name: \"Use 'Endo' from Relude\"\n    note: \"'Endo' is already exported from Relude\"\n    rhs: Endo\n- warn:\n    lhs: Data.Monoid.appEndo\n    name: \"Use 'appEndo' from Relude\"\n    note: \"'appEndo' is already exported from Relude\"\n    rhs: appEndo\n- warn:\n    lhs: Data.Monoid.First\n    name: \"Use 'First' from Relude\"\n    note: \"'First' is already exported from Relude\"\n    rhs: First\n- warn:\n    lhs: Data.Monoid.getFirst\n    name: \"Use 'getFirst' from Relude\"\n    note: \"'getFirst' is already exported from Relude\"\n    rhs: getFirst\n- warn:\n    lhs: Data.Monoid.Last\n    name: \"Use 'Last' from Relude\"\n    note: \"'Last' is already exported from Relude\"\n    rhs: Last\n- warn:\n    lhs: Data.Monoid.getLast\n    name: \"Use 'getLast' from Relude\"\n    note: \"'getLast' is already exported from Relude\"\n    rhs: getLast\n- warn:\n    lhs: Data.Monoid.Product\n    name: \"Use 'Product' from Relude\"\n    note: \"'Product' is already exported from Relude\"\n    rhs: Product\n- warn:\n    lhs: Data.Monoid.getProduct\n    name: \"Use 'getProduct' from Relude\"\n    note: \"'getProduct' is already exported from Relude\"\n    rhs: getProduct\n- warn:\n    lhs: Data.Monoid.Sum\n    name: \"Use 'Sum' from Relude\"\n    note: \"'Sum' is already exported from Relude\"\n    rhs: Sum\n- warn:\n    lhs: Data.Monoid.getSum\n    name: \"Use 'getSum' from Relude\"\n    note: \"'getSum' is already exported from Relude\"\n    rhs: getSum\n- warn:\n    lhs: Data.Semigroup.Semigroup\n    name: \"Use 'Semigroup' from Relude\"\n    note: \"'Semigroup' is already exported from Relude\"\n    rhs: Semigroup\n- warn:\n    lhs: Data.Semigroup.sconcat\n    name: \"Use 'sconcat' from Relude\"\n    note: \"'sconcat' is already exported from Relude\"\n    rhs: sconcat\n- warn:\n    lhs: Data.Semigroup.stimes\n    name: \"Use 'stimes' from Relude\"\n    note: \"'stimes' is already exported from Relude\"\n    rhs: stimes\n- warn:\n    lhs: \"(Data.Semigroup.<>)\"\n    name: \"Use '<>' from Relude\"\n    note: \"Operator '(<>)' is already exported from Relude\"\n    rhs: \"(<>)\"\n- warn:\n    lhs: Data.Semigroup.WrappedMonoid\n    name: \"Use 'WrappedMonoid' from Relude\"\n    note: \"'WrappedMonoid' is already exported from Relude\"\n    rhs: WrappedMonoid\n- warn:\n    lhs: Data.Semigroup.cycle1\n    name: \"Use 'cycle1' from Relude\"\n    note: \"'cycle1' is already exported from Relude\"\n    rhs: cycle1\n- warn:\n    lhs: Data.Semigroup.mtimesDefault\n    name: \"Use 'mtimesDefault' from Relude\"\n    note: \"'mtimesDefault' is already exported from Relude\"\n    rhs: mtimesDefault\n- warn:\n    lhs: Data.Semigroup.stimesIdempotent\n    name: \"Use 'stimesIdempotent' from Relude\"\n    note: \"'stimesIdempotent' is already exported from Relude\"\n    rhs: stimesIdempotent\n- warn:\n    lhs: Data.Semigroup.stimesIdempotentMonoid\n    name: \"Use 'stimesIdempotentMonoid' from Relude\"\n    note: \"'stimesIdempotentMonoid' is already exported from Relude\"\n    rhs: stimesIdempotentMonoid\n- warn:\n    lhs: Data.Semigroup.stimesMonoid\n    name: \"Use 'stimesMonoid' from Relude\"\n    note: \"'stimesMonoid' is already exported from Relude\"\n    rhs: stimesMonoid\n- warn:\n    lhs: Data.ByteString.ByteString\n    name: \"Use 'ByteString' from Relude\"\n    note: \"'ByteString' is already exported from Relude\"\n    rhs: ByteString\n- warn:\n    lhs: Data.ByteString.Short.ShortByteString\n    name: \"Use 'ShortByteString' from Relude\"\n    note: \"'ShortByteString' is already exported from Relude\"\n    rhs: ShortByteString\n- warn:\n    lhs: Data.ByteString.Short.toShort\n    name: \"Use 'toShort' from Relude\"\n    note: \"'toShort' is already exported from Relude\"\n    rhs: toShort\n- warn:\n    lhs: Data.ByteString.Short.fromShort\n    name: \"Use 'fromShort' from Relude\"\n    note: \"'fromShort' is already exported from Relude\"\n    rhs: fromShort\n- warn:\n    lhs: Data.String.IsString\n    name: \"Use 'IsString' from Relude\"\n    note: \"'IsString' is already exported from Relude\"\n    rhs: IsString\n- warn:\n    lhs: Data.String.fromString\n    name: \"Use 'fromString' from Relude\"\n    note: \"'fromString' is already exported from Relude\"\n    rhs: fromString\n- warn:\n    lhs: Data.Text.Text\n    name: \"Use 'Text' from Relude\"\n    note: \"'Text' is already exported from Relude\"\n    rhs: Text\n- warn:\n    lhs: Data.Text.lines\n    name: \"Use 'lines' from Relude\"\n    note: \"'lines' is already exported from Relude\"\n    rhs: lines\n- warn:\n    lhs: Data.Text.unlines\n    name: \"Use 'unlines' from Relude\"\n    note: \"'unlines' is already exported from Relude\"\n    rhs: unlines\n- warn:\n    lhs: Data.Text.words\n    name: \"Use 'words' from Relude\"\n    note: \"'words' is already exported from Relude\"\n    rhs: words\n- warn:\n    lhs: Data.Text.unwords\n    name: \"Use 'unwords' from Relude\"\n    note: \"'unwords' is already exported from Relude\"\n    rhs: unwords\n- warn:\n    lhs: \"Data.Text.Encoding.decodeUtf8'\"\n    name: \"Use 'decodeUtf8'' from Relude\"\n    note: \"'decodeUtf8'' is already exported from Relude\"\n    rhs: \"decodeUtf8'\"\n- warn:\n    lhs: Data.Text.Encoding.decodeUtf8With\n    name: \"Use 'decodeUtf8With' from Relude\"\n    note: \"'decodeUtf8With' is already exported from Relude\"\n    rhs: decodeUtf8With\n- warn:\n    lhs: Data.Text.Encoding.Error.OnDecodeError\n    name: \"Use 'OnDecodeError' from Relude\"\n    note: \"'OnDecodeError' is already exported from Relude\"\n    rhs: OnDecodeError\n- warn:\n    lhs: Data.Text.Encoding.Error.OnError\n    name: \"Use 'OnError' from Relude\"\n    note: \"'OnError' is already exported from Relude\"\n    rhs: OnError\n- warn:\n    lhs: Data.Text.Encoding.Error.UnicodeException\n    name: \"Use 'UnicodeException' from Relude\"\n    note: \"'UnicodeException' is already exported from Relude\"\n    rhs: UnicodeException\n- warn:\n    lhs: Data.Text.Encoding.Error.lenientDecode\n    name: \"Use 'lenientDecode' from Relude\"\n    note: \"'lenientDecode' is already exported from Relude\"\n    rhs: lenientDecode\n- warn:\n    lhs: Data.Text.Encoding.Error.strictDecode\n    name: \"Use 'strictDecode' from Relude\"\n    note: \"'strictDecode' is already exported from Relude\"\n    rhs: strictDecode\n- warn:\n    lhs: Text.Read.Read\n    name: \"Use 'Read' from Relude\"\n    note: \"'Read' is already exported from Relude\"\n    rhs: Read\n- warn:\n    lhs: Text.Read.readMaybe\n    name: \"Use 'readMaybe' from Relude\"\n    note: \"'readMaybe' is already exported from Relude\"\n    rhs: readMaybe\n- warn:\n    lhs: \"(liftIO (newEmptyMVar ))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'newEmptyMVar' from Relude, it's already lifted\"\n    rhs: newEmptyMVar\n- warn:\n    lhs: \"(liftIO (newMVar x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'newMVar' from Relude, it's already lifted\"\n    rhs: newMVar\n- warn:\n    lhs: \"(liftIO (putMVar x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putMVar' from Relude, it's already lifted\"\n    rhs: putMVar\n- warn:\n    lhs: \"(liftIO (readMVar x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readMVar' from Relude, it's already lifted\"\n    rhs: readMVar\n- warn:\n    lhs: \"(liftIO (swapMVar x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'swapMVar' from Relude, it's already lifted\"\n    rhs: swapMVar\n- warn:\n    lhs: \"(liftIO (takeMVar x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'takeMVar' from Relude, it's already lifted\"\n    rhs: takeMVar\n- warn:\n    lhs: \"(liftIO (tryPutMVar x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'tryPutMVar' from Relude, it's already lifted\"\n    rhs: tryPutMVar\n- warn:\n    lhs: \"(liftIO (tryReadMVar x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'tryReadMVar' from Relude, it's already lifted\"\n    rhs: tryReadMVar\n- warn:\n    lhs: \"(liftIO (tryTakeMVar x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'tryTakeMVar' from Relude, it's already lifted\"\n    rhs: tryTakeMVar\n- warn:\n    lhs: \"(liftIO (atomically x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'atomically' from Relude, it's already lifted\"\n    rhs: atomically\n- warn:\n    lhs: \"(liftIO (newTVarIO x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'newTVarIO' from Relude, it's already lifted\"\n    rhs: newTVarIO\n- warn:\n    lhs: \"(liftIO (readTVarIO x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readTVarIO' from Relude, it's already lifted\"\n    rhs: readTVarIO\n- warn:\n    lhs: \"(liftIO (newTMVarIO x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'newTMVarIO' from Relude, it's already lifted\"\n    rhs: newTMVarIO\n- warn:\n    lhs: \"(liftIO (newEmptyTMVarIO ))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'newEmptyTMVarIO' from Relude, it's already lifted\"\n    rhs: newEmptyTMVarIO\n- warn:\n    lhs: \"(liftIO (exitWith x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'exitWith' from Relude, it's already lifted\"\n    rhs: exitWith\n- warn:\n    lhs: \"(liftIO (exitFailure ))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'exitFailure' from Relude, it's already lifted\"\n    rhs: exitFailure\n- warn:\n    lhs: \"(liftIO (exitSuccess ))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'exitSuccess' from Relude, it's already lifted\"\n    rhs: exitSuccess\n- warn:\n    lhs: \"(liftIO (die x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'die' from Relude, it's already lifted\"\n    rhs: die\n- warn:\n    lhs: \"(liftIO (readFile x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readFile' from Relude, it's already lifted\"\n    rhs: readFile\n- warn:\n    lhs: \"(liftIO (writeFile x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'writeFile' from Relude, it's already lifted\"\n    rhs: writeFile\n- warn:\n    lhs: \"(liftIO (appendFile x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'appendFile' from Relude, it's already lifted\"\n    rhs: appendFile\n- warn:\n    lhs: \"(liftIO (readFileText x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readFileText' from Relude, it's already lifted\"\n    rhs: readFileText\n- warn:\n    lhs: \"(liftIO (writeFileText x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'writeFileText' from Relude, it's already lifted\"\n    rhs: writeFileText\n- warn:\n    lhs: \"(liftIO (appendFileText x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'appendFileText' from Relude, it's already lifted\"\n    rhs: appendFileText\n- warn:\n    lhs: \"(liftIO (readFileLText x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readFileLText' from Relude, it's already lifted\"\n    rhs: readFileLText\n- warn:\n    lhs: \"(liftIO (writeFileLText x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'writeFileLText' from Relude, it's already lifted\"\n    rhs: writeFileLText\n- warn:\n    lhs: \"(liftIO (appendFileLText x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'appendFileLText' from Relude, it's already lifted\"\n    rhs: appendFileLText\n- warn:\n    lhs: \"(liftIO (readFileBS x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readFileBS' from Relude, it's already lifted\"\n    rhs: readFileBS\n- warn:\n    lhs: \"(liftIO (writeFileBS x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'writeFileBS' from Relude, it's already lifted\"\n    rhs: writeFileBS\n- warn:\n    lhs: \"(liftIO (appendFileBS x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'appendFileBS' from Relude, it's already lifted\"\n    rhs: appendFileBS\n- warn:\n    lhs: \"(liftIO (readFileLBS x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readFileLBS' from Relude, it's already lifted\"\n    rhs: readFileLBS\n- warn:\n    lhs: \"(liftIO (writeFileLBS x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'writeFileLBS' from Relude, it's already lifted\"\n    rhs: writeFileLBS\n- warn:\n    lhs: \"(liftIO (appendFileLBS x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'appendFileLBS' from Relude, it's already lifted\"\n    rhs: appendFileLBS\n- warn:\n    lhs: \"(liftIO (newIORef x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'newIORef' from Relude, it's already lifted\"\n    rhs: newIORef\n- warn:\n    lhs: \"(liftIO (readIORef x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'readIORef' from Relude, it's already lifted\"\n    rhs: readIORef\n- warn:\n    lhs: \"(liftIO (writeIORef x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'writeIORef' from Relude, it's already lifted\"\n    rhs: writeIORef\n- warn:\n    lhs: \"(liftIO (modifyIORef x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'modifyIORef' from Relude, it's already lifted\"\n    rhs: modifyIORef\n- warn:\n    lhs: \"(liftIO (modifyIORef' x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'modifyIORef'' from Relude, it's already lifted\"\n    rhs: \"modifyIORef'\"\n- warn:\n    lhs: \"(liftIO (atomicModifyIORef x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'atomicModifyIORef' from Relude, it's already lifted\"\n    rhs: atomicModifyIORef\n- warn:\n    lhs: \"(liftIO (atomicModifyIORef' x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'atomicModifyIORef'' from Relude, it's already lifted\"\n    rhs: \"atomicModifyIORef'\"\n- warn:\n    lhs: \"(liftIO (atomicWriteIORef x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'atomicWriteIORef' from Relude, it's already lifted\"\n    rhs: atomicWriteIORef\n- warn:\n    lhs: \"(liftIO (getLine ))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'getLine' from Relude, it's already lifted\"\n    rhs: getLine\n- warn:\n    lhs: \"(liftIO (print x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'print' from Relude, it's already lifted\"\n    rhs: print\n- warn:\n    lhs: \"(liftIO (putStr x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putStr' from Relude, it's already lifted\"\n    rhs: putStr\n- warn:\n    lhs: \"(liftIO (putStrLn x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putStrLn' from Relude, it's already lifted\"\n    rhs: putStrLn\n- warn:\n    lhs: \"(liftIO (putText x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putText' from Relude, it's already lifted\"\n    rhs: putText\n- warn:\n    lhs: \"(liftIO (putTextLn x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putTextLn' from Relude, it's already lifted\"\n    rhs: putTextLn\n- warn:\n    lhs: \"(liftIO (putLText x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putLText' from Relude, it's already lifted\"\n    rhs: putLText\n- warn:\n    lhs: \"(liftIO (putLTextLn x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putLTextLn' from Relude, it's already lifted\"\n    rhs: putLTextLn\n- warn:\n    lhs: \"(liftIO (putBS x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putBS' from Relude, it's already lifted\"\n    rhs: putBS\n- warn:\n    lhs: \"(liftIO (putBSLn x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putBSLn' from Relude, it's already lifted\"\n    rhs: putBSLn\n- warn:\n    lhs: \"(liftIO (putLBS x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putLBS' from Relude, it's already lifted\"\n    rhs: putLBS\n- warn:\n    lhs: \"(liftIO (putLBSLn x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'putLBSLn' from Relude, it's already lifted\"\n    rhs: putLBSLn\n- warn:\n    lhs: \"(liftIO (hFlush x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'hFlush' from Relude, it's already lifted\"\n    rhs: hFlush\n- warn:\n    lhs: \"(liftIO (hIsEOF x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'hIsEOF' from Relude, it's already lifted\"\n    rhs: hIsEOF\n- warn:\n    lhs: \"(liftIO (hSetBuffering x y))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'hSetBuffering' from Relude, it's already lifted\"\n    rhs: hSetBuffering\n- warn:\n    lhs: \"(liftIO (hGetBuffering x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'hGetBuffering' from Relude, it's already lifted\"\n    rhs: hGetBuffering\n- warn:\n    lhs: \"(liftIO (getArgs ))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'getArgs' from Relude, it's already lifted\"\n    rhs: getArgs\n- warn:\n    lhs: \"(liftIO (lookupEnv x))\"\n    name: \"'liftIO' is not needed\"\n    note: \"If you import 'lookupEnv' from Relude, it's already lifted\"\n    rhs: lookupEnv\n- hint:\n    lhs: \"fmap (bimap f g)\"\n    note: \"Use `bimapF` from `Relude.Extra.Bifunctor`\"\n    rhs: bimapF f g\n- hint:\n    lhs: \"bimap f g <$> x\"\n    note: \"Use `bimapF` from `Relude.Extra.Bifunctor`\"\n    rhs: bimapF f g x\n- hint:\n    lhs: \"fmap (first f)\"\n    note: \"Use `firstF` from `Relude.Extra.Bifunctor`\"\n    rhs: firstF f\n- hint:\n    lhs: fmap . first\n    note: \"Use `firstF` from `Relude.Extra.Bifunctor`\"\n    rhs: firstF\n- hint:\n    lhs: \"fmap (second f)\"\n    note: \"Use `secondF` from `Relude.Extra.Bifunctor`\"\n    rhs: secondF f\n- hint:\n    lhs: fmap . second\n    note: \"Use `secondF` from `Relude.Extra.Bifunctor`\"\n    rhs: secondF\n- hint:\n    lhs: \"[minBound .. maxBound]\"\n    note: \"Use `universe` from `Relude.Extra.Enum`\"\n    rhs: universe\n- hint:\n    lhs: succ\n    note: \"`succ` from `Prelude` is a pure function but it may throw exception. Consider using `next` from `Relude.Extra.Enum` instead.\"\n    rhs: next\n- hint:\n    lhs: pred\n    note: \"`pred` from `Prelude` is a pure function but it may throw exception. Consider using `prev` from `Relude.Extra.Enum` instead.\"\n    rhs: prev\n- hint:\n    lhs: toEnum\n    note: \"`toEnum` from `Prelude` is a pure function but it may throw exception. Consider using `safeToEnum` from `Relude.Extra.Enum` instead.\"\n    rhs: safeToEnum\n- hint:\n    lhs: sum xs / length xs\n    note: \"Use `average` from `Relude.Extra.Foldable`\"\n    rhs: average xs\n- hint:\n    lhs: \"\\\\a -> (a, a)\"\n    note: \"Use `dup` from `Relude.Extra.Tuple`\"\n    rhs: dup\n- hint:\n    lhs: \"\\\\a -> (f a, a)\"\n    note: \"Use `toFst` from `Relude.Extra.Tuple`\"\n    rhs: toFst f\n- hint:\n    lhs: \"\\\\a -> (a, f a)\"\n    note: \"Use `toSnd` from `Relude.Extra.Tuple`\"\n    rhs: toSnd f\n- hint:\n    lhs: fmap . toFst\n    note: \"Use `fmapToFst` from `Relude.Extra.Tuple`\"\n    rhs: fmapToFst\n- hint:\n    lhs: \"fmap (toFst f)\"\n    note: \"Use `fmapToFst` from `Relude.Extra.Tuple`\"\n    rhs: fmapToFst f\n- hint:\n    lhs: fmap . toSnd\n    note: \"Use `fmapToSnd` from `Relude.Extra.Tuple`\"\n    rhs: fmapToSnd\n- hint:\n    lhs: \"fmap (toSnd f)\"\n    note: \"Use `fmapToSnd` from `Relude.Extra.Tuple`\"\n    rhs: fmapToSnd f\n- hint:\n    lhs: map . toFst\n    note: \"Use `fmapToFst` from `Relude.Extra.Tuple`\"\n    rhs: fmapToFst\n- hint:\n    lhs: \"map (toFst f)\"\n    note: \"Use `fmapToFst` from `Relude.Extra.Tuple`\"\n    rhs: fmapToFst f\n- hint:\n    lhs: map . toSnd\n    note: \"Use `fmapToSnd` from `Relude.Extra.Tuple`\"\n    rhs: fmapToSnd\n- hint:\n    lhs: \"map (toSnd f)\"\n    note: \"Use `fmapToSnd` from `Relude.Extra.Tuple`\"\n    rhs: fmapToSnd f\n- hint:\n    lhs: \"fmap (,a) (f a)\"\n    note: \"Use `traverseToFst` from `Relude.Extra.Tuple`\"\n    rhs: traverseToFst f a\n- hint:\n    lhs: \"fmap (flip (,) a) (f a)\"\n    note: \"Use `traverseToFst` from `Relude.Extra.Tuple`\"\n    rhs: traverseToFst f a\n- hint:\n    lhs: \"(,a) <$> f a\"\n    note: \"Use `traverseToFst` from `Relude.Extra.Tuple`\"\n    rhs: traverseToFst f a\n- hint:\n    lhs: \"flip (,) a <$> f a\"\n    note: \"Use `traverseToFst` from `Relude.Extra.Tuple`\"\n    rhs: traverseToFst f a\n- hint:\n    lhs: \"fmap (a,) (f a)\"\n    note: \"Use `traverseToSnd` from `Relude.Extra.Tuple`\"\n    rhs: traverseToSnd f a\n- hint:\n    lhs: \"fmap ((,) a) (f a)\"\n    note: \"Use `traverseToSnd` from `Relude.Extra.Tuple`\"\n    rhs: traverseToSnd f a\n- hint:\n    lhs: \"(a,) <$> f a\"\n    note: \"Use `traverseToSnd` from `Relude.Extra.Tuple`\"\n    rhs: traverseToSnd f a\n- hint:\n    lhs: \"(,) a <$> f a\"\n    note: \"Use `traverseToSnd` from `Relude.Extra.Tuple`\"\n    rhs: traverseToSnd f a\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project (as seen by library users) will be documented in this file.\nThe CHANGELOG is available on [Github](https://github.com/luc-tielen/souffle-haskell.git/CHANGELOG.md).\n\n## [0.2.0] - Unreleased\n\n### Added\n\n- Logical negation (of a single rule clause)\n- Typed hole support\n- Comparison operators\n- Arithmetic operators (`+`, `-`, `*`, `/`)\n- Possibility to link in external functions\n- LSP support\n  - Document highlight\n  - Hover\n  - Diagnostics\n- Improved dead code elimination\n- Optimization passes:\n  - HoistConstraints (faster searches by narrowing search-space as early as possible)\n- CLI: Allow emitting initial and transformed RA IR\n- Support named fields in type definitions and extern definitions\n- Support transpiling to Souffle\n- Support running semantic analysis on multiple threads\n\n### Changed\n\n- Relations now can have additional qualifiers marking them as inputs or\n  outputs. Not providing any qualifier means it is now an internal fact.\n\n### Fixed\n\n- 0 is now parsed correctly as a number.\n- Type holes now correctly show all possible results in a rule.\n- BTree implementation is now better suited for large sets of facts\n\n## [0.1.0] - 2022-11-20\n\n### Added\n\n- WebAssembly support\n- Support for the `string` data type\n- Wildcards are now supported in rule bodies\n- Assignments are now supported in rule bodies\n- Support for multiple occurences of the same variable in a single clause of\n  a rule body\n- (UTF-8) strings in relations are now supported\n- Optimizations on the AST level:\n  - Copy propagation\n  - Dead code elimination\n\n### Changed\n\n- Improved error reporting\n- Parsing now continues after failure and reports multiple errors back to the\n  user at once.\n\n### Fixed\n\n- Rules with multiple equalities.\n- Edgecase in index selection algorithm. The algorithm now does not take\n  `NoElem` variants into account.\n\n## [0.0.1] - 2022-06-14\n\n### Added\n\n- First MVP of the compiler! The happy path should work as expected, unsupported\n  features or semantic errors should result in a (poorly formatted) error.\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nContact: luc.tielen@gmail.com\n\n## Why have a Code of Conduct?\n\nAs contributors and maintainers of this project, we are committed to providing a\nfriendly, safe and welcoming environment for all, regardless of age, disability,\ngender, nationality, race, religion, sexuality, or similar personal\ncharacteristic.\n\nThe goal of the Code of Conduct is to specify a baseline standard of behavior so\nthat people with different social values and communication styles can talk about\nEclair effectively, productively, and respectfully, even in face of\ndisagreements. The Code of Conduct also provides a mechanism for resolving\nconflicts in the community when they arise.\n\n## Our Values\n\nThese are the values Eclair developers should aspire to:\n\n- Be friendly and welcoming\n- Be kind\n  - Remember that people have varying communication styles and that not\n    everyone is using their native language. (Meaning and tone can be lost in\n    translation.)\n  - Interpret the arguments of others in good faith, do not seek to disagree.\n  - When we do disagree, try to understand why.\n- Be thoughtful\n  - Productive communication requires effort. Think about how your words will\n    be interpreted.\n  - Remember that sometimes it is best to refrain entirely from commenting.\n- Be respectful\n  - In particular, respect differences of opinion. It is important that we\n    resolve disagreements and differing views constructively.\n- Be constructive\n  - Avoid derailing: stay on topic; if you want to talk about something else,\n    start a new conversation.\n  - Avoid unconstructive criticism: don't merely decry the current state of\n    affairs; offer — or at least solicit — suggestions as to how things may be\n    improved.\n  - Avoid harsh words and stern tone: we are all aligned towards the\n    well-being of the community and the progress of the ecosystem. Harsh words\n    exclude, demotivate, and lead to unnecessary conflict.\n  - Avoid snarking (pithy, unproductive, sniping comments).\n  - Avoid microaggressions (brief and commonplace verbal, behavioral and\n    environmental indignities that communicate hostile, derogatory or negative\n    slights and insults towards a project, person or group).\n- Be responsible\n  - What you say and do matters. Take responsibility for your words and\n    actions, including their consequences, whether intended or otherwise.\n\nThe following actions are explicitly forbidden:\n\n- Insulting, demeaning, hateful, or threatening remarks.\n- Discrimination based on age, disability, gender, nationality, race,\n  religion, sexuality, or similar personal characteristic.\n- Bullying or systematic harassment.\n- Unwelcome sexual advances.\n- Incitement to any of these.\n\n## Where does the Code of Conduct apply?\n\nIf you participate in or contribute to the Eclair ecosystem in any way, you are\nencouraged to follow the Code of Conduct while doing so.\n\nExplicit enforcement of the Code of Conduct applies to the official mediums\noperated by the Eclair project:\n\n- The [official GitHub project][1] and code reviews.\n- The **[#Eclair][2]** Discord[2].\n\nOther Eclair activities (such as conferences, meetups, and unofficial forums)\nare encouraged to adopt this Code of Conduct. Such groups must provide their own\ncontact information.\n\nProject maintainers may block, remove, edit, or reject comments, commits, code,\nwiki edits, issues, and other contributions that are not aligned to this Code of\nConduct.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by emailing: luc.tielen@gmail.com. All complaints will\nbe reviewed and investigated and will result in a response that is deemed\nnecessary and appropriate to the circumstances. **All reports will be kept\nconfidential**.\n\n**The goal of the Code of Conduct is to resolve conflicts in the most harmonious\nway possible**. We hope that in most cases issues may be resolved through polite\ndiscussion and mutual agreement. Bannings and other forceful measures are to be\nemployed only as a last resort. **Do not** post about the issue publicly or try\nto rally sentiment against a particular individual or group.\n\n## Acknowledgements\n\nThis document was based on the Code of Conduct from the Elixir project (dated\nJul/2023), which in turn was based on the Go project (dated Sep/2021) and the\nContributor Covenant (v1.4).\n\n[1]: https://github.com/luc-tielen/eclair-lang\n[2]: https://discord.gg/mC2arUrxKg\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM primordus/souffle-ubuntu:2.3\nARG LLVM_VERSION=17\n\nSHELL [ \"/bin/bash\", \"-c\" ]\n\n# install packages\nRUN echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections \\\n    && echo 'tzdata tzdata/Zones/Europe select Paris' | debconf-set-selections \\\n    && apt-get update \\\n    && DEBIAN_FRONTEND=noninteractive apt-get install -y \\\n       wget software-properties-common gnupg curl libffi-dev make \\\n       python3 python3-pip libgmp-dev \\\n    && curl -o - https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash \\\n    && source /root/.nvm/nvm.sh \\\n    && nvm install 18.1.0 \\\n    && echo \"source /root/.ghcup/env\" >> ~/.bashrc \\\n    # install llvm 17\n    && mkdir -p /tmp/llvm-dir \\\n    && cd /tmp/llvm-dir \\\n    && wget https://apt.llvm.org/llvm.sh \\\n    && chmod +x llvm.sh \\\n    && ./llvm.sh $LLVM_VERSION \\\n    && cd /tmp \\\n    && rm -rf /tmp/llvm-dir \\\n    && cd /usr/bin \\\n    && ln -s /usr/lib/llvm-$LLVM_VERSION/bin/split-file \\\n    && ln -s /usr/lib/llvm-$LLVM_VERSION/bin/FileCheck \\\n    && ln -s clang-$LLVM_VERSION clang \\\n    && ln -s wasm-ld-$LLVM_VERSION wasm-ld \\\n    && cd - \\\n    && pip install lit==14.0.6 \\\n    # install ghcup, ghc-9.6.3 and cabal-3.10.1.0\n    && curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | \\\n    BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3 BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.1.0 \\\n    BOOTSTRAP_HASKELL_INSTALL_STACK=1 BOOTSTRAP_HASKELL_INSTALL_HLS=1 BOOTSTRAP_HASKELL_ADJUST_BASHRC=P sh \\\n    && source /root/.ghcup/env \\\n    && cabal install cabal-fmt \\\n    && cabal install hspec-discover \\\n    && apt-get autoremove -y \\\n    && apt-get purge -y --auto-remove \\\n    && rm -rf /var/lib/apt/lists/*\n\nVOLUME /code\nWORKDIR /app/build\nENV DATALOG_DIR=/app/build/cbits\n\nRUN echo -e '#!/bin/bash\\nsource /root/.ghcup/env\\nsource /root/.nvm/nvm.sh\\nexec \"$@\"\\n' > /app/build/entrypoint.sh \\\n    && chmod u+x /app/build/entrypoint.sh\n\n# The entrypoint script sources ghcup setup script so we can easily call cabal etc.\nENTRYPOINT [ \"/app/build/entrypoint.sh\" ]\n\nCOPY . .\n\nRUN source /root/.ghcup/env && make build \\\n    && echo -e '#!/bin/bash\\nsource /root/.ghcup/env\\n' > /usr/bin/eclair \\\n    && source /root/.ghcup/env \\\n    && echo -e \"`cabal list-bin eclair` \\\"\\$@\\\"\" >> /usr/bin/eclair \\\n    && chmod u+x /usr/bin/eclair\n\n# The default command to run, shows the help menu\nCMD [ \"eclair\", \"--help\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright Luc Tielen (c) 2022\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n\n    * Neither the name of Luc Tielen nor the names of other\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "build: configure\n\t@cabal build\n\nconfigure:\n\t@cabal configure -f eclair-debug --enable-tests\n\nclean:\n\t@cabal clean\n\ntest:\n\t@DATALOG_DIR=cbits/ cabal run eclair-test\n\t@lit tests/ -v\n\ncabal-file:\n\t@cabal-fmt --Werror -i eclair-lang.cabal\n\n.PHONY: build configure clean test cabal-file\n"
  },
  {
    "path": "README.md",
    "content": "<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"./logo_dark.png\"/>\n  <img\n    src=\"./logo_light.png\"\n    alt=\"Logo for the Eclair programming language\"\n    loading=\"lazy\"\n    decoding=\"async\"/>\n</picture>\n\n_An experimental and minimal Datalog implementation that compiles down to LLVM._\n\n[![Build](https://github.com/luc-tielen/eclair-lang/actions/workflows/build.yml/badge.svg)](https://github.com/luc-tielen/eclair-lang/actions/workflows/build.yml)\n\n## Features\n\nEclair is a minimal Datalog (for now). It supports the following features:\n\n- Facts containing literals\n- Rules consisting of one or more clauses.\n- Rules can be non-recursive, recursive or mutually recursive.\n\nRight now it compiles to LLVM but be aware there might still be bugs. \nSome edge cases might not be handled yet.\n\n## Motivating example\n\nLet's say we want to find out which points are reachable in a graph. We can\ndetermine which points are reachable using the following two logical rules:\n\n1. One point is reachable from another point, iff there is a direct edge between\n   those two points.\n2. One point is reachable from another point, iff there is a third point 'z' such\n   that there is a direct edge between 'x' and 'z', and between 'z' and 'y'.\n\nThe Eclair code below can be used to calculate the solution:\n\n```eclair\n@def edge(u32, u32).\n@def reachable(u32, u32).\n\nreachable(x, y) :-\n  edge(x, y).\n\nreachable(x, z) :-\n  edge(x, y),\n  reachable(y, z).\n```\n\nThe above code can be compiled to LLVM using the Docker image provided by this repo:\n\n```bash\n$ git clone git@github.com:luc-tielen/eclair-lang.git\n$ cd eclair-lang\n$ docker build . -t eclair\n# The next line assumes the eclair code is saved as \"example.dl\" in the current directory\n$ docker run -v $PWD:/code --rm -it eclair:latest compile /code/example.dl\n# NOTE: output can be redirected to a file using standard shell functionality: docker run ... > example.ll\n```\n\nThis will emit the generated LLVM IR to the stdout of the terminal. If we save\nthis generated LLVM IR to a file (e.g. `example.ll`), we can link it with the\nfollowing C code that calls into Eclair, using the following command:\n`clang -o program main.c example.ll`.\n\n```c\n// Save this file as \"main.c\".\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdint.h>\n\n\nstruct program;\n\nextern struct program* eclair_program_init();\nextern void eclair_program_destroy(struct program*);\nextern void eclair_program_run(struct program*);\nextern void eclair_add_facts(struct program*, uint16_t fact_type, uint32_t* data, size_t fact_count);\nextern void eclair_add_fact(struct program*, uint16_t fact_type, uint32_t* data);\nextern uint32_t* eclair_get_facts(struct program*, uint16_t fact_type);\nextern void eclair_free_buffer(uint32_t* data);\n\nint main(int argc, char** argv)\n{\n    struct program* prog = eclair_program_init();\n\n    // edge(1,2), edge(2,3)\n    uint32_t data[] = {\n        1, 2,\n        2, 3\n    };\n    eclair_add_facts(prog, 0, data, 2);\n\n    eclair_program_run(prog);\n\n    // NOTE: normally you call btree_size here to figure out the size, but I know there are only 3 facts\n    uint32_t* data_out = eclair_get_facts(prog, 1);\n    printf(\"REACHABLE: (%d, %d)\\n\", data_out[0], data_out[1]);  // (1,2)\n    printf(\"REACHABLE: (%d, %d)\\n\", data_out[2], data_out[3]);  // (2,3)\n    printf(\"REACHABLE: (%d, %d)\\n\", data_out[4], data_out[5]);  // (1,3)\n\n    eclair_free_buffer(data_out);\n\n    eclair_program_destroy(prog);\n    return 0;\n}\n```\n\nIf you run the resulting program, this should print the reachable node pairs\n`(1,2)`, `(2,3)` and `(1,3)` to the screen!\n\n## Roadmap\n\n- [x] LSP support\n- [x] Allow setting options on relations for performance finetuning\n- [x] Comparison operators, != operator\n- [x] Support arithmetic operators\n- [x] Generic, extensible primops\n- [x] Support logical negation\n- [ ] Release 0.2.0\n- [ ] Signed integer type (i32)\n- [ ] Unary negation\n- [ ] Optimizations on the AST / RA / LLVM level\n- [ ] Support other underlying data structures than btree\n- [ ] Syntactic sugar (disjunctions in rule bodies, multiple rule heads, ...)\n- [ ] Support Datalog programs spanning multiple files\n- [ ] ...\n\nThis roadmap is not set in stone, but it gives an idea on the direction of the\nproject. :smile:\n\n## Contributing to Eclair\n\nContributions are welcome! Take a look at the\n[getting started guide](./docs/getting_started.md) on how to set up your machine\nto build, run and test the project. Once setup, the Makefile contains the most\ncommonly used commands needed during development.\n\nYou can also use the `Dockerfile` in this repository if you want to experiment\nwith Eclair without installing the toolchain yourself. You can do that as\nfollows:\n\n```bash\n$ docker build -f Dockerfile . -t eclair\n$ touch test.eclair  # Here you can put your Eclair code\n$ docker run -v $PWD:/code --rm -it eclair eclair compile /code/test.eclair\n```\n\n## Documentation\n\nTake a look at our [docs folder](./docs/) for more information about Eclair.\n\n## Why the name?\n\nEclair is inspired by [Soufflé](https://souffle-lang.github.io/), a high\nperformance Datalog that compiles to C++. Because of the similarities, I chose a\ndifferent kind of food that I like. I mean, an eclair contains _both_ chocolate and\npudding, what's not to like!?\n\nLogo art by [Bruno Monts](https://www.instagram.com/bruno_monts/),\nwith special thanks to the [Fission](https://fission.codes) team.\nPlease contact Luc Tielen before using the logo for anything.\n"
  },
  {
    "path": "cabal.project",
    "content": "packages: .\n\nsource-repository-package\n    type: git\n    location: https://github.com/luc-tielen/llvm-codegen.git\n    tag: 83b04cb576208ea74ddd62016e4fa03f0df138ac\n\nsource-repository-package\n    type: git\n    location: https://github.com/luc-tielen/souffle-haskell.git\n    tag: e441c84f1d64890e31c92fbb278c074ae8bcaff5\n\nsource-repository-package\n    type: git\n    location: https://github.com/luc-tielen/diagnose.git\n    tag: 24a1d7a2b716d74c1fe44d47941a76c9f5a90c23\n"
  },
  {
    "path": "cbits/semantic_analysis.dl",
    "content": "// Input facts\n.decl lit_number(node_id: unsigned, value: unsigned)\n.decl lit_string(node_id: unsigned, value: symbol)\n.decl variable(node_id: unsigned, var_name: symbol)\n.decl constraint(node_id: unsigned, op: symbol, lhs_node_id: unsigned, rhs_node_id: unsigned)\n.decl binop(node_id: unsigned, op: symbol, lhs_node_id: unsigned, rhs_node_id: unsigned)\n.decl atom(node_id: unsigned, name: symbol)\n.decl atom_arg(atom_id: unsigned, atom_arg_pos: unsigned, atom_arg_id: unsigned)\n.decl rule(rule_id: unsigned, name: symbol)\n.decl rule_arg(rule_id: unsigned, rule_arg_pos: unsigned, rule_arg_id: unsigned)\n.decl rule_clause(rule_id: unsigned, rule_clause_pos: unsigned, rule_clause_id: unsigned)\n.decl negation(negation_node_id: unsigned, inner_node_id: unsigned)\n.decl input_relation(relation_name: symbol)\n.decl output_relation(relation_name: symbol)\n.decl internal_relation(relation_name: symbol)\n.decl extern_definition(node_id: unsigned, extern_name: symbol)\n.decl declare_type(node_id: unsigned, name: symbol)\n.decl module(node_id: unsigned)\n.decl module_declaration(module_id: unsigned, declaration_id: unsigned)\n.decl scoped_value(scope_id: unsigned, value_id: unsigned)\n\n// Internal rules\n.decl relation_atom(node_id: unsigned, name: symbol)\n.decl extern_atom(node_id: unsigned, name: symbol)\n.decl grounded_node(rule_node_id: unsigned, node_id: unsigned)\n.decl assign(node_id: unsigned, lhs_node_id: unsigned, rhs_node_id: unsigned) inline\n.decl inequality_op(op: symbol)\n.decl has_output_relation(node_id: unsigned)\n.decl literal_contradiction(lit_id1: unsigned, lit_id2: unsigned)\n.decl wildcard(node_id: unsigned) inline\n.decl rule_head_var(rule_id: unsigned, var_id: unsigned, var_name: symbol)\n.decl alias(rule_id: unsigned, id1: unsigned, id2: unsigned)\n.decl points_to(rule_id: unsigned, id1: unsigned, id2: unsigned)\n.decl depends_on(r1: symbol, r2: symbol)\n.decl transitive_depends_on(r1: symbol, r2: symbol)\n.decl source(r: symbol)\n.decl has_definitions(relation: symbol)\n.decl live_rule(relation: symbol)\n.decl dependency_cycle(relation: symbol)\n.decl rule_scope(rule_id: unsigned, scope_id: unsigned)\n.decl constrained_rule_var(rule_node_id: unsigned, var_node_id: unsigned, var_name: symbol)\n\n// Output facts / rules\n.decl grounded_variable(rule_id: unsigned, var_name: symbol)\n.decl ungrounded_variable(rule_id: unsigned, var_id: unsigned, var_name: symbol)\n.decl ungrounded_external_atom(rule_id: unsigned, atom_id: unsigned, atom_name: symbol)\n.decl wildcard_in_fact(fact_node_id: unsigned, fact_arg_id: unsigned, pos: unsigned)\n.decl wildcard_in_extern(atom_node_id: unsigned, atom_arg_id: unsigned, pos: unsigned)\n.decl wildcard_in_rule_head(rule_node_id: unsigned, rule_arg_id: unsigned, pos: unsigned)\n.decl wildcard_in_constraint(constraint_node_id: unsigned, wildcard_node_id: unsigned)\n.decl wildcard_in_binop(binop_node_id: unsigned, wildcard_node_id: unsigned)\n.decl unconstrained_rule_var(rule_node_id: unsigned, var_node_id: unsigned, var_name: symbol)\n.decl rule_with_contradiction(rule_id: unsigned)\n.decl dead_code(node_id: unsigned)\n.decl no_output_relation(node_id: unsigned)\n.decl dead_internal_relation(node_id: unsigned, relation_name: symbol)\n.decl conflicting_definitions(node_id1: unsigned, node_id2: unsigned, name: symbol)\n.decl extern_used_as_fact(node_id: unsigned, extern_node_id: unsigned, name: symbol)\n.decl extern_used_as_rule(node_id: unsigned, extern_node_id: unsigned, name: symbol)\n.decl cyclic_negation(negation_id: unsigned)\n\n.input lit_number\n.input lit_string\n.input variable\n.input constraint\n.input binop\n.input atom\n.input atom_arg\n.input rule\n.input rule_arg\n.input rule_clause\n.input negation\n.input input_relation\n.input output_relation\n.input internal_relation\n.input extern_definition\n.input declare_type\n.input module\n.input module_declaration\n.input scoped_value\n\n.output wildcard_in_fact\n.output wildcard_in_extern\n.output ungrounded_variable\n.output ungrounded_external_atom\n.output wildcard_in_rule_head\n.output wildcard_in_constraint\n.output wildcard_in_binop\n.output unconstrained_rule_var\n.output dead_code\n.output no_output_relation\n.output dead_internal_relation\n.output conflicting_definitions\n.output extern_used_as_fact\n.output extern_used_as_rule\n.output cyclic_negation\n\n// An atom that is not defined externally. This is an important distinction\n// since external atoms cannot ground variables!\nrelation_atom(node_id, name) :-\n  declare_type(_, name),\n  atom(node_id, name).\n\n// An atom that is defined externally.\nextern_atom(node_id, name) :-\n  extern_definition(_, name),\n  atom(node_id, name).\n\n// r1 depends on r2 if rule r1 refers to r2 in the body\ndepends_on(r1, r2) :-\n  rule(rule_id, r1),\n  rule_clause(rule_id, _, clause_id),\n  atom(clause_id, r2).\n\n// Variant for negated atoms.\ndepends_on(r1, r2) :-\n  rule(rule_id, r1),\n  rule_clause(rule_id, _, negation_id),\n  negation(negation_id, atom_id),\n  atom(atom_id, r2).\n\n// Variant for extern functions.\ndepends_on(r1, r2) :-\n  rule(rule_id, r1),\n  rule_clause(rule_id, _, assign_id),\n  assign(assign_id, lhs_node_id, rhs_node_id),\n  (\n    extern_atom(lhs_node_id, r2);\n    extern_atom(rhs_node_id, r2)\n  ).\n\ntransitive_depends_on(r1, r2) :-\n  depends_on(r1, r2).\n\ntransitive_depends_on(r1, r3) :-\n  depends_on(r1, r2),\n  transitive_depends_on(r2, r3).\n\n// Rules have cyclic dependencies if a rule depends on itself (transitively)\ndependency_cycle(r) :-\n  transitive_depends_on(r, r).\n\n// Negations are not allowed inside a rule if the negated atom is part of a\n// dependency cycle since this is not stratifiable.\ncyclic_negation(negation_id) :-\n  dependency_cycle(r1),\n  transitive_depends_on(r1, r2),\n  rule(rule_id, r2),\n  rule_clause(rule_id, _, negation_id),\n  negation(negation_id, atom_id),\n  atom(atom_id, r3),\n  transitive_depends_on(r3, r1).\n\n// An input can always be a source of data.\nsource(r) :-\n  input_relation(r).\n\n// An internal or output relation can be a source of data if they define top level facts.\nsource(r) :-\n  module_declaration(_, atom_id),\n  atom(atom_id, r),\n  !input_relation(r).  // internal or output relation\n\n// An output rule is live if it is a direct source of data.\nlive_rule(r) :-\n  output_relation(r),\n  source(r).\n\n// An output rule is live if there is a path from the source to this output.\nlive_rule(r1) :-\n  output_relation(r1),\n  transitive_depends_on(r1, r2),\n  source(r2).\n\n// A rule is live if it is depended on by another live rule.\nlive_rule(r2) :-\n  depends_on(r1, r2),\n  live_rule(r1).\n\n// Dead rules are the opposite set of all the live rules.\ndead_code(node_id) :-\n  rule(node_id, r),\n  !live_rule(r).\n\n// Type definitions also need to be marked as dead.\ndead_code(node_id) :-\n  declare_type(node_id, r),\n  !live_rule(r).\n\n// Extern definitions also need to be marked as dead.\ndead_code(node_id) :-\n  extern_definition(node_id, r),\n  !live_rule(r).\n\n// Atoms too.\ndead_code(node_id) :-\n  atom(node_id, r),\n  !live_rule(r).\n\n// Rules are dead if one of the clauses is statically known to produce no results.\ndead_code(rule_id) :-\n  rule_with_contradiction(rule_id).\n\n// A rule is dead if it depends on another dead rule.\n// Note that this only looks at one specific rule that contains the dead code, not the entire relation.\ndead_code(rule_id) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  dead_code(rule_clause_id).\n\nhas_definitions(r) :-\n  module_declaration(_, node_id),\n  (\n    atom(node_id, r);\n    rule(node_id, r)\n  ).\n\n// We consider an internal relation to be dead if there are no top level atoms or rules.\ndead_internal_relation(decl_id, r) :-\n  internal_relation(r),\n  declare_type(decl_id, r),\n  !has_definitions(r).\n\nhas_output_relation(node_id) :-\n  module_declaration(node_id, decl_id),\n  declare_type(decl_id, r),\n  output_relation(r).\n\nno_output_relation(node_id) :-\n  module_declaration(node_id, _),\n  !has_output_relation(node_id).\n\n// Top level facts: no variables allowed\nungrounded_variable(atom_id, var_id, var_name) :-\n  module_declaration(_, atom_id),\n  atom(atom_id, _),\n  scoped_value(atom_id, var_id),\n  variable(var_id, var_name),\n  var_name != \"_\".\n\n// Rules: no variables allowed in rule head if not used in a rule clause\nungrounded_variable(rule_id, var_id, var_name) :-\n  rule_head_var(rule_id, var_id, var_name),\n  var_name != \"_\",\n  !grounded_variable(rule_id, var_name).  // Only compare by variable name!\n\n// Variables used in a constraint (comparison, equality or inequality) need to be grounded.\nungrounded_variable(rule_id, var_id, var_name) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  constraint(rule_clause_id, op, var_id, _),\n  variable(var_id, var_name),\n  var_name != \"_\",\n  !grounded_variable(rule_id, var_name).\n\nungrounded_variable(rule_id, var_id, var_name) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  constraint(rule_clause_id, op, _, var_id),\n  variable(var_id, var_name),\n  var_name != \"_\",\n  !grounded_variable(rule_id, var_name).\n\n// Variables used in a binop need to be grounded.\nungrounded_variable(rule_id, var_id, var_name) :-\n  binop(_, _, var_id, _),\n  variable(var_id, var_name),\n  scoped_value(scope_id, var_id),\n  rule_scope(rule_id, scope_id),\n  !grounded_variable(rule_id, var_name).\n\nungrounded_variable(rule_id, var_id, var_name) :-\n  binop(_, _, _, var_id),\n  variable(var_id, var_name),\n  scoped_value(scope_id, var_id),\n  rule_scope(rule_id, scope_id),\n  !grounded_variable(rule_id, var_name).\n\n// Variables used in a negation need to be grounded.\nungrounded_variable(rule_id, var_id, var_name) :-\n  negation(negation_id, _),\n  rule_clause(rule_id, _, negation_id),\n  scoped_value(negation_id, var_id),\n  variable(var_id, var_name),\n  !grounded_node(rule_id, var_id).\n\n// External atoms used in a fact need to be grounded.\nungrounded_external_atom(fact_id, atom_id, atom_name) :-\n  module_declaration(_, fact_id),\n  atom_arg(fact_id, _, atom_id),\n  extern_atom(atom_id, atom_name),\n  !grounded_node(fact_id, atom_id).\n\n// External atoms used in a rule head need to be grounded.\nungrounded_external_atom(rule_id, atom_id, atom_name) :-\n  rule_arg(rule_id, _, atom_id),\n  atom(atom_id, atom_name),\n  scoped_value(rule_id, atom_id),\n  !grounded_node(rule_id, atom_id).\n\n// External atoms used in a comparison or inequality need to be grounded.\nungrounded_external_atom(rule_id, atom_id, atom_name) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  constraint(rule_clause_id, op, _, atom_id),\n  inequality_op(op),\n  atom(atom_id, atom_name),\n  scoped_value(rule_id, atom_id),\n  !grounded_node(rule_id, atom_id).\n\nungrounded_external_atom(rule_id, atom_id, atom_name) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  constraint(rule_clause_id, op, atom_id, _),\n  inequality_op(op),\n  atom(atom_id, atom_name),\n  scoped_value(rule_id, atom_id),\n  !grounded_node(rule_id, atom_id).\n\n// External atoms used in a binop need to be grounded.\nungrounded_external_atom(rule_id, atom_id, atom_name) :-\n  binop(_, _, _, atom_id),\n  atom(atom_id, atom_name),\n  scoped_value(rule_id, atom_id),\n  !grounded_node(rule_id, atom_id).\n\nungrounded_external_atom(rule_id, atom_id, atom_name) :-\n  binop(_, _, atom_id, _),\n  atom(atom_id, atom_name),\n  scoped_value(rule_id, atom_id),\n  !grounded_node(rule_id, atom_id).\n\nrule_scope(rule_id, rule_id) :-\n  rule(rule_id, _).\n\nrule_scope(rule_id, negation_id) :-\n  rule_clause(rule_id, _, negation_id),\n  negation(negation_id, _).\n\ninequality_op(\"!=\").\ninequality_op(\"<\").\ninequality_op(\"<=\").\ninequality_op(\">\").\ninequality_op(\">=\").\n\nwildcard(node_id) :-\n  variable(node_id, \"_\").\n\nwildcard_in_rule_head(rule_id, rule_arg_id, pos) :-\n  rule_arg(rule_id, pos, rule_arg_id),\n  wildcard(rule_arg_id).\n\nwildcard_in_fact(atom_id, atom_arg_id, pos) :-\n  module_declaration(_, atom_id),\n  atom_arg(atom_id, pos, atom_arg_id),\n  wildcard(atom_arg_id).\n\nwildcard_in_extern(atom_id, atom_arg_id, pos) :-\n  rule(rule_id, _),\n  scoped_value(rule_id, atom_id),\n  extern_atom(atom_id, _),\n  atom_arg(atom_id, pos, atom_arg_id),\n  wildcard(atom_arg_id).\n\nwildcard_in_constraint(constraint_node_id, lhs_node_id) :-\n  constraint(constraint_node_id, _, lhs_node_id, _),\n  wildcard(lhs_node_id).\n\nwildcard_in_constraint(constraint_node_id, rhs_node_id) :-\n  constraint(constraint_node_id, _, _, rhs_node_id),\n  wildcard(rhs_node_id).\n\nwildcard_in_binop(binop_node_id, lhs_node_id) :-\n  binop(binop_node_id, _, lhs_node_id, _),\n  wildcard(lhs_node_id).\n\nwildcard_in_binop(binop_node_id, rhs_node_id) :-\n  binop(binop_node_id, _, _, rhs_node_id),\n  wildcard(rhs_node_id).\n\n// A rule variable is unconstrained if there is no other occurrence of the variable in the rule.\n// (This works because groundedness of a variable is also checked..\nunconstrained_rule_var(rule_id, var_id, var_name) :-\n  rule_scope(rule_id, scope_id),\n  scoped_value(scope_id, var_id),\n  variable(var_id, var_name),\n  !constrained_rule_var(rule_id, var_id, var_name).\n\n// This could be done much simpler using count aggregate but this is not implemented in Eclair yet.\nconstrained_rule_var(rule_id, var_id1, var_name) :-\n  rule_scope(rule_id, scope_id1),\n  rule_scope(rule_id, scope_id2),\n  scoped_value(scope_id1, var_id1),\n  scoped_value(scope_id2, var_id2),\n  variable(var_id1, var_name),\n  variable(var_id2, var_name),\n  var_id1 != var_id2.\n\nassign(node_id, lhs_node_id, rhs_node_id) :-\n  constraint(node_id, \"=\", lhs_node_id, rhs_node_id).\n\n// All variables with the same name in a rule are aliases of each other.\nalias(rule_id, var_id1, var_id2) :-\n  scoped_value(rule_id, var_id1),\n  variable(var_id1, var_name),\n  scoped_value(rule_id, var_id2),\n  var_id1 != var_id2,\n  variable(var_id2, var_name).\n\n// Two values are aliases if they are used inside an equality.\n// NOTE: Datalog supports both x = 123 and 123 = x.\nalias(rule_id, id1, id2),\nalias(rule_id, id2, id1) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  assign(rule_clause_id, id1, id2).\n\n// Non-recursive case: what does a variable point to?\npoints_to(rule_id, id1, id2) :-\n  alias(rule_id, id1, id2),\n  variable(id1, _).\n\n// Recursive case: a = b, b = c results in a = c\npoints_to(rule_id, id1, id4) :-\n  points_to(rule_id, id1, id2),\n  variable(id2, var_name),\n  variable(id3, var_name),\n  alias(rule_id, id3, id4).\n\n// If we find two variables that point to different literal values,\n// then there is a contradiction.\nrule_with_contradiction(rule_id) :-\n  points_to(rule_id, start_id, id1),\n  points_to(rule_id, start_id, id2),\n  literal_contradiction(id1, id2).\n\n// This is also true for simple cases like '123 = 456'.\nrule_with_contradiction(rule_id) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  assign(rule_clause_id, id1, id2),\n  literal_contradiction(id1, id2).\n\nliteral_contradiction(id1, id2) :-\n  lit_number(id1, value1),\n  lit_number(id2, value2),\n  value1 != value2.\n\nliteral_contradiction(id1, id2) :-\n  lit_string(id1, value1),\n  lit_string(id2, value2),\n  value1 != value2.\n\n\n// Helper relation for getting all variables in head of a rule.\nrule_head_var(rule_id, var_id, var_name) :-\n  rule_arg(rule_id, _, var_id),\n  variable(var_id, var_name).\n\n// Helper relation for getting all grounded variables in body of a rule.\ngrounded_variable(rule_id, var_name) :-\n  grounded_node(rule_id, var_id),\n  variable(var_id, var_name).\n\n// Variables are grounded if they are used in an atom (defined using '@def').\ngrounded_node(rule_id, var_id) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  relation_atom(rule_clause_id, _),\n  atom_arg(rule_clause_id, _, var_id),\n  variable(var_id, _).\n\n// All variables with same name are grounded at the same time\ngrounded_node(rule_id, var_id2) :-\n  scoped_value(rule_id, var_id1),\n  variable(var_id1, var_name),\n  grounded_node(rule_id, var_id1),\n  scoped_value(rule_id, var_id2),\n  variable(var_id2, var_name).\n\n// Variables are grounded inside a negation if they are grounded in another clause.\ngrounded_node(rule_id, var_id2) :-\n  negation(negation_id, _),\n  rule_clause(rule_id, _, negation_id),\n  scoped_value(rule_id, var_id1),\n  variable(var_id1, var_name),\n  grounded_node(rule_id, var_id1),\n  scoped_value(negation_id, var_id2),\n  variable(var_id2, var_name).\n\n// Literals are always grounded.\ngrounded_node(rule_id, node_id) :-\n  scoped_value(rule_id, node_id),\n  lit_number(node_id, _).\n\ngrounded_node(rule_id, node_id) :-\n  scoped_value(rule_id, node_id),\n  lit_string(node_id, _).\n\n// A binop is grounded if both sides are grounded.\ngrounded_node(rule_id, node_id) :-\n  grounded_node(rule_id, lhs_node_id),\n  grounded_node(rule_id, rhs_node_id),\n  binop(node_id, _, lhs_node_id, rhs_node_id).\n\n// Assignment grounds one var, if the other side is already grounded.\ngrounded_node(rule_id, rhs_node_id) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  assign(rule_clause_id, lhs_node_id, rhs_node_id),\n  grounded_node(rule_id, lhs_node_id),\n  variable(rhs_node_id, _).\n\ngrounded_node(rule_id, lhs_node_id) :-\n  rule_clause(rule_id, _, rule_clause_id),\n  assign(rule_clause_id, lhs_node_id, rhs_node_id),\n  grounded_node(rule_id, rhs_node_id),\n  variable(lhs_node_id, _).\n\nconflicting_definitions(node_id, node_id2, name) :-\n  declare_type(node_id, name),\n  declare_type(node_id2, name),\n  node_id < node_id2.\n\nconflicting_definitions(node_id, node_id2, name) :-\n  extern_definition(node_id, name),\n  extern_definition(node_id2, name),\n  node_id < node_id2.\n\nconflicting_definitions(node_id, node_id2, name) :-\n  declare_type(node_id, name),\n  extern_definition(node_id2, name),\n  node_id < node_id2.\n\nconflicting_definitions(node_id, node_id2, name) :-\n  extern_definition(node_id, name),\n  declare_type(node_id2, name),\n  node_id < node_id2.\n\nextern_used_as_fact(node_id, extern_node_id, name) :-\n  extern_definition(extern_node_id, name),\n  module_declaration(_, node_id),\n  atom(node_id, name).\n\nextern_used_as_rule(node_id, extern_node_id, name) :-\n  extern_definition(extern_node_id, name),\n  rule(node_id, name).\n"
  },
  {
    "path": "docs/architecture_choices.md",
    "content": "# Architecture choices\n\nThis document contains all the high level choices that have been made with\nregards to the architecture of the compiler. Besides this document, there are\nalso [several blogposts](https://luctielen.com/) with deep dives on specific\nparts of the compiler.\n\n## Inspired by Souffle\n\nEclair's approach to compiling high level Datalog syntax to assembly is heavily\ninspired by Souffle Datalog (most notably: the compilation to relational algebra\nand the minimum index selection algorithm). At the top of the corresponding\nsource code in Eclair, there is a comment pointing to the paper so contributors\ncan consult the theoretic background behind the code easily.\n\nEclair does have some notable differences though. First and foremost: it is\nwritten in Haskell. This choice was made because Haskell makes it easy to\nexpress high level ideas and algorithms that you commonly run into when building\na compiler. As an additional benefit, Haskell already has a great ecosystem of\nlibraries for building a compiler.\nThe second big change compared to Souffle is that Eclair compiles down directly\nto LLVM instead of C++. This gives greater control of the generated assembly\n(assembly is generated using a monad / \"builder pattern\" instead of\nconcatenating strings to form a C++ program) and also makes it easily portable\nto other platforms (including WebAssembly). On top of that we can leverage\nexisting LLVM tools to analyze / transform the generated LLVM IR.\n\n## IR Design\n\nEclair makes use of four different intermediate representations (IRs). Each of\nthe IRs has a different focus / view of the program. By using multiple IRs, we\ncan also gradually lower the Datalog syntax to the assembly level.\n\nThe four different IRs are:\n\n1. AST\n2. RA\n3. EIR\n4. LLVM IR\n\nEach of these IRs is discussed in the subsections below. Every IR can be pretty\nprinted for inspection (when debugging a compiler issue or when writing Eclair\ncode).\n\nEach of the IRs are designed in a similar way: they all consist of a single data\ntype each. This might be a bit controversial for most Haskellers that value type\nsafety, but this ends up working out because:\n\n1. The parser, semantic analysis and typesystem steps halt when they determine\n   the program is invalid;\n2. Often we are only interested in a really small part of the IR anyway;\n3. Most Haskell libraries support one simple type best.\n\nThe singly-typed IR is a conciously made trade-off, but it gives us great\nbenefits. Transformations (a large part of the compiler!) can be written down\nsuccinctly thanks to the\n[recursion-schemes library](https://hackage.haskell.org/package/recursion-schemes).\nThe transforms are guaranteed to terminate when written this way, and are\nautomatically composable. Besides that, all algorithms can be written down as a\npattern-match that focuses on only one node of the IR.\n\nBesides having singly-typed IRs, each data constructor in the IR type has a\nunique node ID attached to it. This makes it possible to link data from outside\nthe IR to it, without having to keep modifying the IR over and over. This is\nespecially useful for the semantic analysis and typesystem parts of the\ncompiler, since they can refer to parts of the program via a node ID.\n\n### AST\n\nThe first IR is the `AST` type. AST stands for \"Abstract Syntax Tree\" and is a\ntree representation of the original source code.\n\nSemantic analysis and typechecking happens on this IR before any transformations\nare performed, so we can report back exact locations to users of Eclair.\n\nThe AST is the most \"high level\" / starting IR. AST compiles down to RA, which\nis described in the next section.\n\n### RA\n\n`RA` stands for \"Relational Algebra\". It represents a Eclair Datalog program as\na set of relational algebra operations. The data type is pretty much copied\ndirectly from the Souffle paper, with some minor modifications. By first\ntransforming the AST to RA, we can subsequently lower the code even further down\nto the assembly level (via EIR and LLVM IR).\n\n### EIR\n\n`EIR` is an abbreviation for \"Eclair IR\". It is a IR designed to be very close\nto LLVM IR, but with the focus that it is also easy to debug and inspect. It was\nmainly created to make the final lowering to LLVM IR as trivial as possible, but\nit ended up being also useful for stitching the various Eclair functions in the\nruntime together.\n\n### LLVM IR\n\nThe LLVM IR is the final IR this compiler makes use of and bears the closest\nresemblance to assembly. The\n[llvm-codegen library](https://github.com/luc-tielen/llvm-codegen) is used to\ngenerate LLVM instructions. From this point onwards, we can make use of the LLVM\ncompiler framework to get many optimizations and other tools all for free.\n\n## Query-based compiler\n\nEclair is a so-called \"query-based compiler\". What this means is:\n\n1. Each stage of the compiler builds on top of previous stages,\n2. You can query the results of each of these \"sub-computations\".\n\nThis kind of architecture ends up being very useful for a compiler since a\ncompiler often is not a \"pipeline\" as it is usually presented, but instead has\na graph structure where later stages can depend on earlier stages. On top of\nthat, it makes it simple to access information at each stage of the compiler,\nmaking it easy to write tools that can query the compiler as a database. (Useful\nfor developer tools such as LSP!)\n\nThe [rock library](https://hackage.haskell.org/package/rock) builds on top of\nthis idea and provides an API for describing your compiler in terms of build\nsystem rules.\n\n## The parser\n\nThe parser is written using parser combinators (from the `megaparsec` library).\nThis approach was chosen because now this parser is fully written in Haskell,\nmaking it trivial to integrate with the rest of the compiler. On top of that, it\ngives you full control over how the parsing happens. In the Eclair compiler the\nparser adds a unique node id to each parsed AST node (see section about IR\ndesign).\n\n## Semantic analysis\n\nEclair makes use of Souffle Datalog during semantic analysis. Using Datalog for\nsemantic analysis is great, because you can write all your logic really\nsuccinctly by writing down the \"patterns\" you are looking for in the AST and let\nDatalog deduce all results.\n\nThe fact that each AST node has a unique ID (see section about IR design) makes\nit easy to refer to certain parts of a program and also to make it easy to\nserialize data back and forth between Haskell and Datalog.\n\nEventually, Eclair will be a bootstrapped compiler, meaning all the parts where\nSouffle Datalog is currently used will be swapped out with an Eclair Datalog\ncounterpart. This will make it much easier to distribute and run the compiler\n(since there is one big dependency less required).\n\n## Typesystem\n\nThe typesystem is a bidirectional typesystem. This means that the typesystem\neither checks a term against an expected type, or it tries to infer a type.\nBidirectional typecheckers are great because they are \"straight-forward\" to\nimplement (you pretty much need to write two functions that pattern match on the\nsyntax and handle each node type correspondingly), and produce better error\nmessages than typesystems that make heavy use of constraint solving.\n\nOn top of this, the typesystem tries to report as many type errors as possible\nat once and with additional context how it came to these conclusions. This is\ndone to make the developer experience better.\n\n## Error rendering\n\nAn effort is made to make Eclair errors as clear as possible for developers\n(using Rust and Elm for inspiration). Right now we use the\n[diagnose library](https://github.com/Mesabloo/diagnose) for reporting the\nerrors since it allows us to focus on other parts of the compiler, but later\nthis error rendering system will be implemented in the Eclair codebase itself to\nallow for more customization.\n\n## Tranformations\n\nEclair has a general\n[Transform](https://github.com/luc-tielen/eclair-lang/blob/main/lib/Eclair/Transform.hs)\ntype that can be used for transforming the various IRs. Transformations can have\ntwo goals: they either simplify the IR, or they try to optimize it (or both).\n\nEclair is a nano-pass compiler. Transformations should be small, focused and\ncomposable; so that you can reason about them. It's better to have a few extra\npasses in the compiler instead of a lot of extra complexity. The `Transform`\ntype provides helper functions and typeclass instances to compose them into\nbigger transforms anyway.\n\nTransformations can have local state, but the way it is setup it is impossible\nfor this state to \"leak out\" to the outside world. This is again done to make it\neasier to reason about, while not limiting what's possible inside a transform.\n\nFinally, transforms always run in a `TransformM` monad. This monad offers a way\nof generating new unique node IDs in case extra IR nodes are generated.\n\n## Tests\n\nCurrently the Eclair test suite is divided into two parts:\n\n1. Unit tests written directly in Haskell\n2. \"Integration\" tests that are executed by the `lit` executable provided by LLVM.\n\nOver time, most of the tests will be integration tests, since they are more\nrigorous and test larger parts of the compiler. On top of that, these style of\ntests allow you to write an example directly in Eclair and compare the result\nagainst actual output of the compiler.\n"
  },
  {
    "path": "docs/getting_started.md",
    "content": "# Getting started\n\nEclair requires a Haskell toolchain, Souffle 2.3 and LLVM 14 to be installed on\nyour system.\n\nIf you notice that the installation instructions below are incomplete or\noutdated, please open a [Github issue](https://github.com/luc-tielen/eclair-lang/issues).\n\n## Pre-requisites\n\n### Ubuntu\n\nNOTE: These commands were tested with Ubuntu 20.04, they may not work with older\nversions.\n\n#### Installing the Haskell toolchain\n\nRun the following commands to install `ghcup`, `ghc` and `cabal`. `cabal-fmt`, \n`hspec-discover` and `hlint` are also installed but they are only needed when working on the\ncompiler.\n\n```bash\n$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh\n$ ghcup tui\n# In the terminal UI, select GHC 9.2.4, Haskell language server 1.8 and Cabal 3.6.\n# Important: both install + set them!\n$ cabal install cabal-fmt\n$ cabal install hspec-discover\n$ cabal install hlint\n```\n\nVerify you installed the correct versions by running the commands below, and\ncomparing them against the versions mentioned in the previous command:\n\n```bash\n$ ghc --version\n$ haskell-language-server-wrapper --version\n$ cabal --version\n```\n\n#### Installing Souffle\n\nRun the following commands to download and build Souffle from source:\n\n```bash\n$ sudo apt install bison build-essential cmake doxygen flex g++ git \\\n  libffi-dev libncurses5-dev libsqlite3-dev make mcpp python sqlite zlib1g-dev\n\n$ git clone git@github.com:souffle-lang/souffle.git\n$ cd souffle\n$ git checkout 2.3\n$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release\n$ cmake --build build -j\n$ sudo cmake --build build --target install\n```\n\nIf this went correctly, Souffle should now be globally installed on your system.\nCheck this by executing the following command; it should print out the version\nof Souffle (2.3).\n\n```bash\n$ souffle --version\n```\n\n#### Installing LLVM\n\nNext we need to install LLVM 14. Run the steps below to install it on your\nsystem.\n\n```bash\n# If these packages are not available on your system, try installing using this\n# link: https://apt.llvm.org/.\n$ sudo apt install llvm-14\n$ sudo apt install lld-14\n$ sudo apt install clang-14  # Optional, if you want to use clang instead of llc\n                             # to compile the LLVM IR\n# The following is only needed for development / testing\n$ cd ~/.local/bin  # Or any other directory that is on your $PATH\n$ ln -s /usr/lib/llvm-14/bin/split-file\n$ ln -s /usr/bin/FileCheck-14 FileCheck\n$ pip install lit==14.0.6\n```\n\n### Windows\n\nAssuming you have Windows subsytem for Linux, the commands above should also\nwork for Windows? (If somebody could verify this, that would be great!)\n\n### OSX\n\nNOTE: These commands were tested with Intel MacOS 13.0, they may or may not not work\nwith older versions or on an ARM-based machine.\n\n#### Installing the Haskell toolchain\n\nRun the following commands (see https://www.haskell.org/ghcup/) to install `ghcup`, `ghc` and `cabal`.\n\n```bash\n$ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh\n$ ghcup tui\n# In the terminal UI, select GHC 9.2.4, Haskell language server 1.8 and Cabal 3.6.\n# Important: both install + set them!\n```\n\nThe following commands are only needed when working on the compiler.\nRun the commands to install `cabal-fmt`, `hspec-discover`, and `hlint`.\n\n```bash\n$ cabal install cabal-fmt\n$ cabal install hspec-discover\n$ cabal install hlint\n```\n\nVerify you installed the correct versions by running the commands below, and\ncompare them against the versions mentioned in the previous command:\n\n```bash\n$ ghc --version\n$ haskell-language-server-wrapper --version\n$ cabal --version\n```\n\n#### Installing Souffle\n\nRun the following commands to download and build Souffle from source\n(instructions taken from [here](https://souffle-lang.github.io/build#mac-os-x-build)):\n\n```bash\n$ brew update\n$ brew install cmake bison libffi mcpp pkg-config\n$ brew reinstall gcc\n$ brew link bison --force\n$ brew link libffi --force\n$ brew install souffle-lang/souffle/souffle\n```\n\nIf this went correctly, Souffle should now be globally installed on your system.\nCheck this by executing the following command; it should print out the version\nof Souffle (2.3).\n\n```bash\n$ souffle --version\n```\n\n#### Installing LLVM\n\nNext we need to install LLVM 14. Run the steps below to install it on your\nsystem.\n\n```bash\n$ brew install llvm@14\n# The following is only needed for development / testing\n$ cd ~/.local/bin  # Or any other directory that is on your $PATH\n$ ln -s /usr/local/opt/llvm@14/bin/split-file\n$ ln -s /usr/local/opt/llvm@14/bin/FileCheck\n$ pip install lit==14.0.6\n$ brew install node\n```\n\n## Building Eclair\n\nNow that all the pre-requisites are built, you can build Eclair.\n\n```bash\n$ cabal build\n$ cabal run eclair-test  # Unit tests\n$ lit tests -v           # Integration tests\n$ cabal list-bin eclair  # <= returns path to the Eclair compiler executable\n```\n"
  },
  {
    "path": "eclair-lang.cabal",
    "content": "cabal-version:      2.2\nname:               eclair-lang\nversion:            0.2.0\nsynopsis:\n  Eclair: an experimental and minimal Datalog that compiles to LLVM.\n\ndescription:\n  Eclair: an experimental and minimal Datalog that compiles to LLVM.\n\ncategory:           Compiler\nhomepage:           https://github.com/luc-tielen/eclair-lang\nauthor:             Luc Tielen\nmaintainer:         luc.tielen@gmail.com\ncopyright:          Luc Tielen, 2023\nlicense:            BSD-3-Clause\nlicense-file:       LICENSE\nbuild-type:         Simple\nextra-source-files:\n  cbits/semantic_analysis.dl\n  CHANGELOG.md\n  LICENSE\n  README.md\n\nflag debug\n  description: Enables stack traces.\n  manual:      True\n  default:     False\n\nlibrary\n  -- cabal-fmt: expand lib\n  exposed-modules:\n    Eclair\n    Eclair.ArgParser\n    Eclair.AST.Analysis\n    Eclair.AST.Codegen\n    Eclair.AST.IR\n    Eclair.AST.Lower\n    Eclair.AST.Transforms\n    Eclair.AST.Transforms.ConstantFolding\n    Eclair.AST.Transforms.DeadCodeElimination\n    Eclair.AST.Transforms.NormalizeRules\n    Eclair.AST.Transforms.RemoveAliases\n    Eclair.AST.Transforms.ReplaceStrings\n    Eclair.Common.Config\n    Eclair.Common.Extern\n    Eclair.Common.Id\n    Eclair.Common.Literal\n    Eclair.Common.Location\n    Eclair.Common.Operator\n    Eclair.Common.Pretty\n    Eclair.Comonads\n    Eclair.EIR.IR\n    Eclair.EIR.Lower\n    Eclair.EIR.Lower.API\n    Eclair.EIR.Lower.Codegen\n    Eclair.EIR.Lower.Externals\n    Eclair.Error\n    Eclair.JSON\n    Eclair.LLVM.Allocator.Arena\n    Eclair.LLVM.Allocator.Common\n    Eclair.LLVM.Allocator.Malloc\n    Eclair.LLVM.Allocator.Page\n    Eclair.LLVM.BTree\n    Eclair.LLVM.BTree.Bounds\n    Eclair.LLVM.BTree.Compare\n    Eclair.LLVM.BTree.Create\n    Eclair.LLVM.BTree.Destroy\n    Eclair.LLVM.BTree.Find\n    Eclair.LLVM.BTree.Insert\n    Eclair.LLVM.BTree.Iterator\n    Eclair.LLVM.BTree.Size\n    Eclair.LLVM.BTree.Types\n    Eclair.LLVM.Codegen\n    Eclair.LLVM.Config\n    Eclair.LLVM.Externals\n    Eclair.LLVM.Hash\n    Eclair.LLVM.HashMap\n    Eclair.LLVM.Metadata\n    Eclair.LLVM.Symbol\n    Eclair.LLVM.SymbolTable\n    Eclair.LLVM.Table\n    Eclair.LLVM.Template\n    Eclair.LLVM.Vector\n    Eclair.LSP\n    Eclair.LSP.Handlers\n    Eclair.LSP.Handlers.Diagnostics\n    Eclair.LSP.Handlers.DocumentHighlight\n    Eclair.LSP.Handlers.Hover\n    Eclair.LSP.JSON\n    Eclair.LSP.Monad\n    Eclair.LSP.Types\n    Eclair.LSP.VFS\n    Eclair.Parser\n    Eclair.RA.Codegen\n    Eclair.RA.IndexSelection\n    Eclair.RA.IR\n    Eclair.RA.Lower\n    Eclair.RA.Transforms\n    Eclair.RA.Transforms.HoistConstraints\n    Eclair.Souffle.IR\n    Eclair.Transform\n    Eclair.TypeSystem\n    Prelude\n\n  hs-source-dirs:     lib\n  default-extensions:\n    DataKinds\n    DeriveAnyClass\n    DeriveFoldable\n    DeriveFunctor\n    DeriveGeneric\n    DeriveTraversable\n    DerivingStrategies\n    DerivingVia\n    FlexibleContexts\n    FlexibleInstances\n    KindSignatures\n    LambdaCase\n    OverloadedStrings\n    PatternSynonyms\n    RankNTypes\n    RecursiveDo\n    ScopedTypeVariables\n    TupleSections\n    TypeFamilies\n    ViewPatterns\n\n  ghc-options:\n    -Wall -Wincomplete-patterns -fhide-source-paths\n    -fno-show-valid-hole-fits -fno-sort-valid-hole-fits\n\n  cxx-options:        -std=c++17 -D__EMBEDDED_SOUFFLE__ -Wall\n  build-depends:\n    , algebraic-graphs             <1\n    , base                         >=4.7   && <5\n    , bytestring                   >=0.11  && <0.12\n    , comonad                      >=5     && <6\n    , containers                   <1\n    , dependent-sum                >=0.6   && <1\n    , diagnose                     >=2.3   && <2.4\n    , directory                    >=1     && <2\n    , dlist                        >=1     && <2\n    , exceptions                   >=0.10  && <0.11\n    , extra                        >=1     && <2\n    , ghc-prim                     <1\n    , hermes-json                  <1\n    , llvm-codegen\n    , megaparsec                   >=9     && <10\n    , mmorph                       >=1     && <2\n    , mtl                          >=2     && <3\n    , optparse-applicative         >=0.16  && <0.17\n    , parser-combinators           >=1.3   && <1.4\n    , prettyprinter                >=1.7   && <1.8\n    , prettyprinter-ansi-terminal  >=1     && <2\n    , recursion-schemes            >=5     && <6\n    , relude                       >=1.2   && <1.3\n    , rock                         >=0.3   && <0.4\n    , souffle-haskell              ==4.0.0\n    , text                         >=2     && <3\n    , text-builder-linear          <1\n    , transformers                 <1\n    , vector                       >=0.12  && <0.13\n\n  mixins:             base hiding (Prelude)\n  default-language:   Haskell2010\n\n  if os(osx)\n    extra-libraries: c++\n\n  if flag(debug)\n    ghc-options:   -fplugin=StackTrace.Plugin\n    build-depends: haskell-stack-trace-plugin ==0.1.3.0\n\n  if os(linux)\n    extra-libraries: stdc++\n\nexecutable eclair\n  main-is:            Main.hs\n  other-modules:      Paths_eclair_lang\n  autogen-modules:    Paths_eclair_lang\n  hs-source-dirs:     src/eclair\n  default-extensions:\n    DataKinds\n    DeriveAnyClass\n    DeriveFoldable\n    DeriveFunctor\n    DeriveGeneric\n    DeriveTraversable\n    DerivingStrategies\n    DerivingVia\n    FlexibleContexts\n    FlexibleInstances\n    KindSignatures\n    LambdaCase\n    OverloadedStrings\n    PatternSynonyms\n    RankNTypes\n    RecursiveDo\n    ScopedTypeVariables\n    TupleSections\n    TypeFamilies\n    ViewPatterns\n\n  ghc-options:\n    -Wall -Wincomplete-patterns -fhide-source-paths\n    -fno-show-valid-hole-fits -fno-sort-valid-hole-fits -threaded\n    -rtsopts -with-rtsopts=-N\n\n  cxx-options:        -std=c++17 -D__EMBEDDED_SOUFFLE__\n  build-depends:\n    , algebraic-graphs             <1\n    , base                         >=4.7   && <5\n    , bytestring                   >=0.11  && <0.12\n    , comonad                      >=5     && <6\n    , containers                   <1\n    , dependent-sum                >=0.6   && <1\n    , diagnose                     >=2.3   && <2.4\n    , directory                    >=1     && <2\n    , dlist                        >=1     && <2\n    , eclair-lang\n    , exceptions                   >=0.10  && <0.11\n    , extra                        >=1     && <2\n    , llvm-codegen\n    , megaparsec                   >=9     && <10\n    , mmorph                       >=1     && <2\n    , mtl                          >=2     && <3\n    , optparse-applicative         >=0.16  && <0.17\n    , parser-combinators           >=1.3   && <1.4\n    , prettyprinter                >=1.7   && <1.8\n    , prettyprinter-ansi-terminal  >=1     && <2\n    , process                      >=1.6   && <1.7\n    , recursion-schemes            >=5     && <6\n    , relude                       >=1.2   && <1.3\n    , rock                         >=0.3   && <0.4\n    , souffle-haskell              ==4.0.0\n    , text                         >=2     && <3\n    , transformers                 <1\n    , vector                       >=0.12  && <0.13\n\n  mixins:             base hiding (Prelude)\n  default-language:   Haskell2010\n\n  if os(osx)\n    extra-libraries: c++\n\n  if flag(debug)\n    ghc-options:   -fplugin=StackTrace.Plugin\n    build-depends: haskell-stack-trace-plugin ==0.1.3.0\n\ntest-suite eclair-test\n  type:               exitcode-stdio-1.0\n  main-is:            test.hs\n\n  -- cabal-fmt: expand tests/eclair\n  other-modules:\n    Paths_eclair_lang\n    Test.Eclair.ArgParserSpec\n    Test.Eclair.JSONSpec\n    Test.Eclair.LLVM.Allocator.MallocSpec\n    Test.Eclair.LLVM.Allocator.PageSpec\n    Test.Eclair.LLVM.Allocator.Utils\n    Test.Eclair.LLVM.BTreeSpec\n    Test.Eclair.LLVM.HashMapSpec\n    Test.Eclair.LLVM.HashSpec\n    Test.Eclair.LLVM.SymbolSpec\n    Test.Eclair.LLVM.SymbolTableSpec\n    Test.Eclair.LLVM.SymbolUtils\n    Test.Eclair.LLVM.VectorSpec\n    Test.Eclair.LSP.HandlersSpec\n    Test.Eclair.LSP.JSONSpec\n    Test.Eclair.RA.IndexSelectionSpec\n\n  autogen-modules:    Paths_eclair_lang\n  hs-source-dirs:     tests/eclair\n  default-extensions:\n    DataKinds\n    DeriveAnyClass\n    DeriveFoldable\n    DeriveFunctor\n    DeriveGeneric\n    DeriveTraversable\n    DerivingStrategies\n    DerivingVia\n    FlexibleContexts\n    FlexibleInstances\n    KindSignatures\n    LambdaCase\n    OverloadedStrings\n    PatternSynonyms\n    RankNTypes\n    RecursiveDo\n    ScopedTypeVariables\n    TupleSections\n    TypeFamilies\n    ViewPatterns\n\n  ghc-options:\n    -Wall -Wincomplete-patterns -fhide-source-paths\n    -fno-show-valid-hole-fits -fno-sort-valid-hole-fits\n\n  cxx-options:        -std=c++17 -D__EMBEDDED_SOUFFLE__\n  build-depends:\n    , algebraic-graphs             <1\n    , array                        >=0.5   && <1\n    , base                         >=4.7   && <5\n    , bytestring                   >=0.11  && <0.12\n    , comonad                      >=5     && <6\n    , containers                   <1\n    , dependent-sum                >=0.6   && <1\n    , diagnose                     >=2.3   && <2.4\n    , dlist                        >=1     && <2\n    , eclair-lang\n    , exceptions                   >=0.10  && <0.11\n    , extra                        >=1     && <2\n    , filepath                     >=1     && <2\n    , hedgehog                     >=1     && <2\n    , hermes-json                  <1\n    , hspec                        >=2.6.1 && <3.0.0\n    , hspec-hedgehog               <1\n    , libffi                       >=0.2   && <1\n    , llvm-codegen\n    , megaparsec                   >=9     && <10\n    , mmorph                       >=1     && <2\n    , mtl                          >=2     && <3\n    , neat-interpolation           <1\n    , optparse-applicative         >=0.16  && <0.17\n    , parser-combinators           >=1.3   && <1.4\n    , prettyprinter                >=1.7   && <1.8\n    , prettyprinter-ansi-terminal  >=1     && <2\n    , random                       >=1.2   && <2\n    , recursion-schemes            >=5     && <6\n    , relude                       >=1.2   && <1.3\n    , rock                         >=0.3   && <0.4\n    , silently                     >=1.2   && <1.3\n    , souffle-haskell              ==4.0.0\n    , text                         >=2     && <3\n    , transformers                 <1\n    , unix                         >=2.8   && <3\n    , vector                       >=0.12  && <0.13\n\n  mixins:             base hiding (Prelude)\n  default-language:   Haskell2010\n\n  if os(osx)\n    extra-libraries: c++\n\n  if flag(debug)\n    ghc-options:   -fplugin=StackTrace.Plugin\n    build-depends: haskell-stack-trace-plugin ==0.1.3.0\n"
  },
  {
    "path": "hie.yaml",
    "content": "cradle:\n  cabal:\n    - path: \"lib\"\n      component: \"lib:eclair-lang\"\n\n    - path: \"src/eclair/Main.hs\"\n      component: \"eclair-lang:exe:eclair\"\n\n    - path: \"src/eclair/Paths_eclair_lang.hs\"\n      component: \"eclair-lang:exe:eclair\"\n\n    - path: \"src/lsp\"\n      component: \"eclair-lang:exe:eclair-lsp-server\"\n\n    - path: \"tests/eclair\"\n      component: \"eclair-lang:test:eclair-test\"\n\n    - path: \"tests/lsp/Main.hs\"\n      component: \"eclair-lang:test:eclair-lsp-test\"\n"
  },
  {
    "path": "lib/Eclair/AST/Analysis.hs",
    "content": "{-# LANGUAGE UndecidableInstances #-}\n\nmodule Eclair.AST.Analysis\n  ( Result(..)\n  , SemanticInfo(..)\n  , SemanticErrors(..)\n  , hasSemanticErrors\n  , runAnalysis\n  , UngroundedVar(..)\n  , WildcardInFact(..)\n  , WildcardInRuleHead(..)\n  , WildcardInConstraint(..)\n  , WildcardInBinOp(..)\n  , WildcardInExtern(..)\n  , UnconstrainedRuleVar(..)\n  , DeadCode(..)\n  , DeadInternalRelation(..)\n  , NoOutputRelation(..)\n  , ConflictingDefinitionGroup(..)\n  , ExternUsedAsFact(..)\n  , ExternUsedAsRule(..)\n  , CyclicNegation(..)\n  , NodeId(..)\n  , Container\n  , computeUsageMapping\n  ) where\n\nimport qualified Data.List.NonEmpty as NE\nimport Data.List.Extra (nubOrdOn)\nimport qualified Language.Souffle.Interpreted as S\nimport qualified Language.Souffle.Analysis as S\nimport qualified Eclair.AST.IR as IR\nimport qualified Data.Map as Map\nimport Eclair.Common.Id\nimport Eclair.Common.Location (NodeId(..))\n\n\ntype Position = Word32\n\n-- The facts submitted to Datalog closely follow the AST structure,\n-- but are denormalized so that Datalog can easily process it.\n\ndata LitNumber\n  = LitNumber NodeId Word32\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions LitNumber \"lit_number\" 'S.Input\n\ndata LitString\n  = LitString NodeId Text\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions LitString \"lit_string\" 'S.Input\n\ndata Var\n  = Var NodeId Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions Var \"variable\" 'S.Input\n\nnewtype Hole\n  = Hole NodeId\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions Hole \"hole\" 'S.Input\n\ndata Constraint\n  = Constraint\n  { constraintId :: NodeId\n  , constraintOperator :: Text\n  , constraintLhsId :: NodeId\n  , constraintRhsId :: NodeId\n  }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions Constraint \"constraint\" 'S.Input\n\ndata BinOp\n  = BinOp\n  { binOpId :: NodeId\n  , op :: Text\n  , binOpLhsId :: NodeId\n  , binOpRhsId :: NodeId\n  }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions BinOp \"binop\" 'S.Input\n\ndata Atom\n  = Atom NodeId Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions Atom \"atom\" 'S.Input\n\ndata AtomArg\n  = AtomArg { atomId :: NodeId, atomArgPos :: Word32, atomArgId :: NodeId }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions AtomArg \"atom_arg\" 'S.Input\n\ndata Rule\n  = Rule NodeId Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions Rule \"rule\" 'S.Input\n\ndata RuleArg\n  = RuleArg { raRuleId :: NodeId, raArgPos :: Word32, raArgId :: NodeId }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions RuleArg \"rule_arg\" 'S.Input\n\ndata RuleClause\n  = RuleClause { rcRuleId :: NodeId, rcClausePos :: Word32, rcClauseId :: NodeId }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions RuleClause \"rule_clause\" 'S.Input\n\ndata Negation\n  = Negation\n  { negationNodeId :: NodeId\n  , negationInnerNodeId :: NodeId\n  }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions Negation \"negation\" 'S.Input\n\n-- NOTE: not storing types right now, but might be useful later?\ndata DeclareType\n  = DeclareType NodeId Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions DeclareType \"declare_type\" 'S.Input\n\ndata ExternDefinition\n  = ExternDefinition NodeId Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions ExternDefinition \"extern_definition\" 'S.Input\n\nnewtype InputRelation\n  = InputRelation Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions InputRelation \"input_relation\" 'S.Input\n\nnewtype OutputRelation\n  = OutputRelation Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions OutputRelation \"output_relation\" 'S.Input\n\nnewtype InternalRelation\n  = InternalRelation Id\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions InternalRelation \"internal_relation\" 'S.Input\n\nnewtype Module\n  = Module NodeId\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions Module \"module\" 'S.Input\n\ndata ModuleDecl\n  = ModuleDecl { moduleId :: NodeId, declId :: NodeId }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions ModuleDecl \"module_declaration\" 'S.Input\n\ndata ScopedValue\n  = ScopedValue { svScopeId :: NodeId, svNodeId :: NodeId }\n  deriving stock Generic\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions ScopedValue \"scoped_value\" 'S.Input\n\ndata UngroundedVar loc\n  = UngroundedVar\n  { ungroundedRuleLoc :: loc\n  , ungroundedVarLoc :: loc\n  , ungroundedVarName :: Id\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (UngroundedVar loc) \"ungrounded_variable\" 'S.Output\n\ndata WildcardInFact loc\n  = WildcardInFact\n  { factLoc :: loc\n  , factArgLoc :: loc\n  , wildcardFactPos :: Position\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (WildcardInFact loc) \"wildcard_in_fact\" 'S.Output\n\ndata WildcardInRuleHead loc\n  = WildcardInRuleHead\n  { wildcardRuleLoc :: loc\n  , wildcardRuleArgLoc :: loc\n  , wildcardRuleHeadPos :: Position\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (WildcardInRuleHead loc) \"wildcard_in_rule_head\" 'S.Output\n\ndata WildcardInConstraint loc\n  = WildcardInConstraint\n  { wildcardConstraintLoc :: loc\n  , wildcardConstraintPos :: loc\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (WildcardInConstraint loc) \"wildcard_in_constraint\" 'S.Output\n\ndata WildcardInBinOp loc\n  = WildcardInBinOp\n  { wildcardBinOpLoc :: loc\n  , wildcardBinOpPos :: loc\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (WildcardInBinOp loc) \"wildcard_in_binop\" 'S.Output\n\ndata WildcardInExtern loc\n  = WildcardInExtern\n  { wildcardExternAtomLoc :: loc\n  , wildcardExternAtomArgLoc :: loc\n  , wildcardExternArgPos :: Position\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (WildcardInExtern loc) \"wildcard_in_extern\" 'S.Output\n\ndata UnconstrainedRuleVar loc\n  = UnconstrainedRuleVar\n  { urvRuleLoc :: loc\n  , urvVarLoc :: loc\n  , urvVarName :: Id\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (UnconstrainedRuleVar loc) \"unconstrained_rule_var\" 'S.Output\n\nnewtype DeadCode\n  = DeadCode { unDeadCode :: NodeId }\n  deriving stock (Generic, Eq)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions DeadCode \"dead_code\" 'S.Output\n\nnewtype NoOutputRelation loc\n  = NoOutputRelation loc\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (NoOutputRelation loc) \"no_output_relation\" 'S.Output\n\ndata DeadInternalRelation loc\n  = DeadInternalRelation loc Id\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (DeadInternalRelation loc) \"dead_internal_relation\" 'S.Output\n\ndata ConflictingDefinitions loc\n  = ConflictingDefinitions\n  { cdFirstLoc :: loc\n  , cdSecondLoc :: loc\n  , cdName :: Id\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (ConflictingDefinitions loc) \"conflicting_definitions\" 'S.Output\n\ndata ConflictingDefinitionGroup loc\n  = ConflictingDefinitionGroup\n  { cdgName :: Id\n  , cdgLocs :: NonEmpty loc\n  } deriving stock (Eq, Functor)\n\ndata ExternUsedAsFact loc\n  = ExternUsedAsFact\n  { externAsFactLoc :: loc\n  , externAsFactExternLoc :: loc\n  , externAsFactName :: Id\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (ExternUsedAsFact loc) \"extern_used_as_fact\" 'S.Output\n\ndata ExternUsedAsRule loc\n  = ExternUsedAsRule\n  { externAsRuleLoc :: loc\n  , externAsRuleExternLoc :: loc\n  , externAsRuleName :: Id\n  }\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (ExternUsedAsRule loc) \"extern_used_as_rule\" 'S.Output\n\nnewtype CyclicNegation loc\n  = CyclicNegation loc\n  deriving stock (Generic, Eq, Functor)\n  deriving anyclass S.Marshal\n  deriving S.Fact via S.FactOptions (CyclicNegation loc) \"cyclic_negation\" 'S.Output\n\ndata SemanticAnalysis\n  = SemanticAnalysis\n  deriving S.Program\n  via S.ProgramOptions SemanticAnalysis \"semantic_analysis\"\n      '[ LitNumber\n       , LitString\n       , Var\n       , Hole\n       , Constraint\n       , BinOp\n       , Atom\n       , AtomArg\n       , Rule\n       , RuleArg\n       , RuleClause\n       , Negation\n       , DeclareType\n       , ExternDefinition\n       , InputRelation\n       , OutputRelation\n       , InternalRelation\n       , Module\n       , ModuleDecl\n       , ScopedValue\n       , UngroundedVar NodeId\n       , WildcardInRuleHead NodeId\n       , WildcardInFact NodeId\n       , WildcardInConstraint NodeId\n       , WildcardInBinOp NodeId\n       , WildcardInExtern NodeId\n       , UnconstrainedRuleVar NodeId\n       , DeadCode\n       , NoOutputRelation NodeId\n       , DeadInternalRelation NodeId\n       , ConflictingDefinitions NodeId\n       , ExternUsedAsFact NodeId\n       , ExternUsedAsRule NodeId\n       , CyclicNegation NodeId\n       ]\n\n-- TODO: change to Vector when finished for performance\ntype Container = []\n\nnewtype SemanticInfo\n  = SemanticInfo\n  { deadCodeIds :: Container DeadCode\n  } deriving Eq\n\ndata Result\n  = Result\n  { semanticInfo :: SemanticInfo\n  , semanticErrors :: SemanticErrors NodeId\n  }\n  deriving Eq\n\ndata SemanticErrors loc\n  = SemanticErrors\n  { ungroundedVars :: Container (UngroundedVar loc)\n  , wildcardsInFacts :: Container (WildcardInFact loc)\n  , wildcardsInRuleHeads :: Container (WildcardInRuleHead loc)\n  , wildcardsInConstraints :: Container (WildcardInConstraint loc)\n  , wildcardsInBinOps :: Container (WildcardInBinOp loc)\n  , wildcardsInExternAtoms :: Container (WildcardInExtern loc)\n  , unconstrainedVars :: Container (UnconstrainedRuleVar loc)\n  , deadInternalRelations :: Container (DeadInternalRelation loc)\n  , noOutputRelations :: Container (NoOutputRelation loc)\n  , conflictingDefinitions :: Container (ConflictingDefinitionGroup loc)\n  , externsUsedAsFact :: Container (ExternUsedAsFact loc)\n  , externsUsedAsRule :: Container (ExternUsedAsRule loc)\n  , cyclicNegations :: Container (CyclicNegation loc)\n  }\n  deriving (Eq, Functor)\n\nhasSemanticErrors :: Result -> Bool\nhasSemanticErrors result =\n  isNotNull ungroundedVars ||\n  isNotNull wildcardsInFacts ||\n  isNotNull wildcardsInRuleHeads ||\n  isNotNull wildcardsInConstraints ||\n  isNotNull wildcardsInBinOps ||\n  isNotNull wildcardsInExternAtoms ||\n  isNotNull unconstrainedVars ||\n  isNotNull deadInternalRelations ||\n  isNotNull noOutputRelations ||\n  isNotNull conflictingDefinitions ||\n  isNotNull externsUsedAsFact ||\n  isNotNull cyclicNegations\n  where\n    errs = semanticErrors result\n    isNotNull :: (SemanticErrors NodeId -> [a]) -> Bool\n    isNotNull f = not . null $ f errs\n\nanalysis :: Word -> S.Handle SemanticAnalysis -> S.Analysis S.SouffleM IR.AST Result\nanalysis numCores prog = S.mkAnalysis addFacts run getFacts\n  where\n    addFacts :: IR.AST -> S.SouffleM ()\n    addFacts ast = usingReaderT Nothing $ flip (zygo IR.getNodeIdF) ast $ \\case\n      IR.LitF nodeId lit -> do\n        mScopeId <- ask\n        for_ mScopeId $ \\scopeId ->\n          S.addFact prog $ ScopedValue scopeId nodeId\n        case lit of\n          IR.LNumber x ->\n            S.addFact prog $ LitNumber nodeId x\n          IR.LString x ->\n            S.addFact prog $ LitString nodeId x\n      IR.PWildcardF nodeId ->\n        S.addFact prog $ Var nodeId (Id \"_\")\n      IR.VarF nodeId var -> do\n        S.addFact prog $ Var nodeId var\n        mScopeId <- ask\n        for_ mScopeId $ \\scopeId ->\n          S.addFact prog $ ScopedValue scopeId nodeId\n      IR.HoleF nodeId ->\n        S.addFact prog $ Hole nodeId\n      IR.BinOpF nodeId arithOp (lhsId', lhsAction) (rhsId', rhsAction) -> do\n        let textualOp = case arithOp of\n              IR.Plus -> \"+\"\n              IR.Minus -> \"-\"\n              IR.Multiply -> \"*\"\n              IR.Divide -> \"/\"\n        mScopeId <- ask\n        for_ mScopeId $ \\scopeId ->\n          S.addFact prog $ ScopedValue scopeId nodeId\n        S.addFact prog $ BinOp nodeId textualOp lhsId' rhsId'\n        lhsAction\n        rhsAction\n      IR.ConstraintF nodeId constraintOp (lhsId', lhsAction) (rhsId', rhsAction) -> do\n        let textualOp = case constraintOp of\n              IR.Equals -> \"=\"\n              IR.NotEquals -> \"!=\"\n              IR.LessThan -> \"<\"\n              IR.LessOrEqual -> \"<=\"\n              IR.GreaterThan -> \"<\"\n              IR.GreaterOrEqual -> \"<=\"\n        S.addFact prog $ Constraint nodeId textualOp lhsId' rhsId'\n        lhsAction\n        rhsAction\n      IR.NotF nodeId (innerNodeId, action) -> do\n        S.addFact prog $ Negation nodeId innerNodeId\n        local (const $ Just nodeId) action\n      IR.AtomF nodeId atom (unzip -> (argNodeIds, actions)) -> do\n        S.addFact prog $ Atom nodeId atom\n        mScopeId <- ask\n        S.addFacts prog $ mapWithPos (AtomArg nodeId) argNodeIds\n\n        for_ mScopeId $ \\scopeId ->\n          S.addFact prog $ ScopedValue scopeId nodeId\n\n        let maybeAddScope =\n              if isJust mScopeId\n                then id\n                else local (const $ Just nodeId)\n        maybeAddScope $ sequence_ actions\n      IR.RuleF nodeId rule ruleArgs ruleClauses -> do\n        let (argNodeIds, argActions) = unzip ruleArgs\n            (clauseNodeIds, clauseActions) = unzip ruleClauses\n        S.addFact prog $ Rule nodeId rule\n        S.addFacts prog $ mapWithPos (RuleArg nodeId) argNodeIds\n        S.addFacts prog $ mapWithPos (RuleClause nodeId) clauseNodeIds\n        local (const $ Just nodeId) $ do\n          sequence_ argActions\n          sequence_ clauseActions\n      IR.ExternDefinitionF nodeId name _ _ -> do\n        S.addFact prog $ ExternDefinition nodeId name\n      IR.DeclareTypeF nodeId name _ usageMode -> do\n        S.addFact prog $ DeclareType nodeId name\n\n        case usageMode of\n          IR.Input ->\n            S.addFact prog $ InputRelation name\n          IR.Output ->\n            S.addFact prog $ OutputRelation name\n          IR.InputOutput -> do\n            S.addFact prog $ InputRelation name\n            S.addFact prog $ OutputRelation name\n          IR.Internal ->\n            S.addFact prog $ InternalRelation name\n      IR.ModuleF nodeId (unzip -> (declNodeIds, actions)) -> do\n        S.addFact prog $ Module nodeId\n        S.addFacts prog $ map (ModuleDecl nodeId) declNodeIds\n        sequence_ actions\n\n    run :: S.SouffleM ()\n    run = do\n      S.setNumThreads prog (fromIntegral numCores)\n      S.run prog\n\n    getFacts :: S.SouffleM Result\n    getFacts = do\n      info <- SemanticInfo <$> S.getFacts prog\n      errs <- SemanticErrors <$> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> (groupConflicts <$> S.getFacts prog)\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n                             <*> S.getFacts prog\n      pure $ Result info errs\n\n    mapWithPos :: (Word32 -> a -> b) -> [a] -> [b]\n    mapWithPos g = zipWith g [0..]\n\ngroupConflicts :: Container (ConflictingDefinitions NodeId) -> Container (ConflictingDefinitionGroup NodeId)\ngroupConflicts conflicts =\n  conflicts\n  & sortWith sameConflict\n  & groupBy ((==) `on` sameConflict)\n  & map (\\cg ->\n    let firstConflict = head cg\n        declName = cdName firstConflict\n        locs = NE.cons (cdFirstLoc firstConflict) (map cdSecondLoc cg)\n     in ConflictingDefinitionGroup declName locs\n  )\n  & nubOrdOn cdgName\n  where\n    sameConflict = cdName &&& cdFirstLoc\n\nrunAnalysis :: Word -> IR.AST -> IO Result\nrunAnalysis numCores ast = S.runSouffle SemanticAnalysis $ \\case\n  Nothing -> panic \"Failed to load Souffle during semantic analysis!\"\n  Just prog -> S.execAnalysis (analysis numCores prog) ast\n\ncomputeUsageMapping :: IR.AST -> Map Id IR.UsageMode\ncomputeUsageMapping ast =\n  Map.fromList pairs\n  where\n    pairs = flip cata ast $ \\case\n      IR.DeclareTypeF _ name _ mode ->\n        one (name, mode)\n      astf ->\n        fold astf\n"
  },
  {
    "path": "lib/Eclair/AST/Codegen.hs",
    "content": "{-# LANGUAGE DerivingVia #-}\n\nmodule Eclair.AST.Codegen\n  ( CodegenM\n  , Env(..)\n  , runCodegen\n  , toTerm\n  , project\n  , search\n  , loop\n  , parallel\n  , merge\n  , swap\n  , purge\n  , exit\n  , noElemOf\n  , if'\n  ) where\n\nimport Prelude hiding (swap, project)\nimport Data.DList (DList)\nimport qualified Data.DList as DList\nimport qualified Eclair.RA.IR as RA\nimport qualified Eclair.AST.IR as AST\nimport Eclair.Common.Location\nimport Eclair.Common.Literal\nimport Eclair.Common.Operator\nimport Eclair.Common.Id\nimport Eclair.Common.Extern\n\n\ntype AST = AST.AST\ntype RA = RA.RA\ntype Relation = RA.Relation\ntype Alias = RA.Alias\ntype Variable = Id\n\ntype Column = Int\n\nnewtype Row = Row { unRow :: Int }\n  deriving (Eq, Ord)\n\ndata InLoop\n  = InLoop\n  deriving Eq\n\ndata Env\n  = Env\n  { envRow :: Row\n  , envExterns :: [Extern]\n  , envLoopContext :: Maybe InLoop\n  }\n\ndata LowerState\n  = LowerState\n  { nextNodeId :: Word32  -- NOTE: Unrelated to NodeIDs used in AST!\n  -- Constraints that can be resolved directly, but need to be emitted later.\n  , directConstraints :: CodegenM RA -> CodegenM RA\n  -- We keep track of which alias + column maps to which variables for later\n  -- generation of constraints.\n  , varMapping :: DList (Alias, Column, Variable)\n  }\n\nnewtype CodegenM a\n  = CodegenM (RWS Env () LowerState a)\n  deriving (Functor, Applicative, Monad, MonadReader Env, MonadState LowerState)\n  via RWS Env () LowerState\n\nrunCodegen :: [Extern] -> CodegenM a -> a\nrunCodegen externs (CodegenM m) =\n  -- NOTE: NodeId starts at 1, since module is manually created, and has NodeId 0\n  let beginState = LowerState 1 id mempty\n   in fst $ evalRWS m (Env (Row 0) externs Nothing) beginState\n\nfreshNodeId :: CodegenM NodeId\nfreshNodeId = do\n  next <- gets nextNodeId\n  modify $ \\s -> s { nextNodeId = next + 1 }\n  pure $ NodeId next\n\nproject :: Relation -> [CodegenM RA] -> CodegenM RA\nproject r terms = do\n  nodeId <- freshNodeId\n  (addDirectConstraints, mapping) <- gets (directConstraints &&& varMapping)\n  let grouped =\n        mapping\n        & toList\n        & sortOn varNameOf\n        & groupBy ((==) `on` varNameOf)\n      eqs = map toIndirectConstraint grouped\n      addIndirectConstraints = foldl' (.) id eqs\n\n  noElemConstraint <- lookupNoElemConstraint\n  addDirectConstraints . addIndirectConstraints . noElemConstraint $\n    RA.Project nodeId r <$> sequence terms\n  where\n    varNameOf (_, _, v) = v\n\n    resolveAliasValue (a, col, _) = do\n      nodeId <- freshNodeId\n      pure $ RA.ColumnIndex nodeId a col\n\n    toIndirectConstraint bindingGroup m = case bindingGroup of\n      initial :| rest -> do\n        aliasValue <- resolveAliasValue initial\n        aliasValues <- traverse resolveAliasValue rest\n        let constraints = map (if' Equals aliasValue) aliasValues\n            wrapConstraints = foldl' (.) id constraints\n        wrapConstraints m\n\n    lookupNoElemConstraint = do\n      loopCtx <- asks envLoopContext\n      if loopCtx == Just InLoop\n        then pure $ noElemOf (stripIdPrefixes r) terms\n        else pure id\n\nsearch :: Relation -> [AST] -> CodegenM RA -> CodegenM RA\nsearch r terms inner = do\n  nodeId <- freshNodeId\n  -- Potentially reset var mapping when we reach the first search,\n  -- this makes it possible to easily support multiple project statements.\n  maybeResetSearchState\n\n  a <- relationToAlias r\n  zipWithM_ (\\col t -> emitSearchTerm t a col) [0..] terms\n  action <- local nextRow inner\n  pure $ RA.Search nodeId r a [] action\n  where\n    nextRow s = s { envRow = Row . (+1) . unRow $ envRow s }\n    maybeResetSearchState = do\n      Row row <- asks envRow\n      when (row == 0) $ do\n        modify $ \\s -> s { varMapping = mempty }\n\n    emitSearchTerm :: AST -> Alias -> Column -> CodegenM ()\n    emitSearchTerm ast a col = do\n      -- Based on what the term resolved to, we might need to create additional\n      -- constraints. Literals can directly be converted to a constraint, variables\n      -- are solved at the end (in the project statement).\n      case ast of\n        AST.Lit {} ->\n          addDirectConstraint ast a col\n        AST.BinOp {} ->\n          addDirectConstraint ast a col\n        AST.PWildcard _ ->\n          pass\n        AST.Var _ v ->\n          -- We append new constraints at the end.\n          -- This will cause indices to always trigger as soon as possible,\n          -- which narrows down the search space and speeds up the query.\n          modify $ \\s -> s { varMapping = DList.snoc (varMapping s) (a, col, v) }\n        _ ->\n          pass\n\n    addDirectConstraint ast a col = do\n      ra <- toTerm ast\n      nodeId <- freshNodeId\n      let aliasValue = RA.ColumnIndex nodeId a col\n          constraint = if' Equals aliasValue ra\n      modify $ \\s -> s { directConstraints = directConstraints s . constraint }\n\nloop :: [CodegenM RA] -> CodegenM RA\nloop ms = local (\\env -> env { envLoopContext = Just InLoop}) $ do\n  nodeId <- freshNodeId\n  RA.Loop nodeId <$> sequence ms\n\nparallel :: [CodegenM RA] -> CodegenM RA\nparallel = \\case\n  [m] -> m\n  ms -> do\n    nodeId <- freshNodeId\n    RA.Par nodeId <$> sequence ms\n\nmerge :: Relation -> Relation -> CodegenM RA\nmerge from' to' = do\n  nodeId <- freshNodeId\n  pure $ RA.Merge nodeId from' to'\n\nswap :: Relation -> Relation -> CodegenM RA\nswap r1 r2 = do\n  nodeId <- freshNodeId\n  pure $ RA.Swap nodeId r1 r2\n\npurge :: Relation -> CodegenM RA\npurge r = do\n  nodeId <- freshNodeId\n  pure $ RA.Purge nodeId r\n\nexit :: [Relation] -> CodegenM RA\nexit rs = do\n  nodeId <- freshNodeId\n  pure $ RA.Exit nodeId rs\n\nnoElemOf :: Relation -> [CodegenM RA] -> CodegenM RA -> CodegenM RA\nnoElemOf r ts inner = do\n  notElemNodeId <- freshNodeId\n  ifNodeId <- freshNodeId\n  cond <- RA.NotElem notElemNodeId r <$> sequence ts\n  RA.If ifNodeId cond <$> inner\n\nif' :: LogicalOp -> RA -> RA -> CodegenM RA -> CodegenM RA\nif' op lhs rhs body = do\n  cmpNodeId <- freshNodeId\n  ifNodeId <- freshNodeId\n  let cond = RA.CompareOp cmpNodeId op lhs rhs\n  RA.If ifNodeId cond <$> body\n\ntoTerm :: AST -> CodegenM RA\ntoTerm ast = do\n  nodeId <- freshNodeId\n  case ast of\n    AST.Lit _ (LNumber lit) ->\n      pure $ RA.Lit nodeId lit\n    AST.PWildcard _ ->\n      pure $ RA.Undef nodeId\n    AST.Var _ v -> do\n      gets (find (\\(_, _, v') -> v == v') . varMapping) >>= \\case\n        Just (alias, col, _) -> do\n          pure $ RA.ColumnIndex nodeId alias col\n        Nothing ->\n          panic $ \"Found ungrounded variable '\" <> unId v <> \"' in 'toTerm'!\"\n    AST.BinOp _ op lhs rhs -> do\n      lhsTerm <- toTerm lhs\n      rhsTerm <- toTerm rhs\n      pure $ RA.PrimOp nodeId (RA.BuiltinOp op) [lhsTerm, rhsTerm]\n    AST.Atom _ name args -> do\n      RA.PrimOp nodeId (RA.ExternOp name) <$> traverse toTerm args\n    _ ->\n      panic \"Unexpected case in 'toTerm'!\"\n\nrelationToAlias :: Relation -> CodegenM Alias\nrelationToAlias r =\n  asks (appendToId r . show . unRow . envRow)\n"
  },
  {
    "path": "lib/Eclair/AST/IR.hs",
    "content": "{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}\n\nmodule Eclair.AST.IR\n  ( AST(.., PWildcard)\n  , ASTF(.., PWildcardF)\n  , Value\n  , Clause\n  , Decl\n  , Literal(..)\n  , Type(..)\n  , ArithmeticOp(..)\n  , LogicalOp(..)\n  , isEqualityOp\n  , getNodeId\n  , getNodeIdF\n  , getExternDefs\n  , UsageMode(..)\n  , Attributes\n  ) where\n\nimport Prettyprinter\nimport Eclair.Common.Id\nimport Eclair.Common.Operator\nimport Eclair.Common.Extern\nimport Eclair.Common.Literal\nimport Eclair.Common.Pretty\nimport Eclair.Common.Location\n\ntype Value = AST\ntype Clause = AST\ntype Decl = AST\n\ndata Type\n  = U32\n  | Str\n  | TUnknown Int  -- NOTE: unification variable, only used internally!\n  deriving (Eq, Ord, Show)\n\ndata UsageMode\n  = Input\n  | Output\n  | InputOutput\n  | Internal  -- This variant is only used internally (pun intended).\n  deriving (Eq, Show)\n\n-- Later this will also contain (Maybe StorageType), ...\ntype Attributes = UsageMode\n\n-- NOTE: There is no explicit \"AND\" node, conjunctions are inlined into other\n-- nodes (as lists of clauses).\ndata AST\n  -- Expressions\n  = Lit NodeId Literal\n  | Var NodeId Id\n  | Hole NodeId\n  | BinOp NodeId ArithmeticOp AST AST\n  -- Statements\n  | Constraint NodeId LogicalOp AST AST\n  | Rule NodeId Id [Value] [Clause]\n  | Not NodeId Clause\n  | Atom NodeId Id [Value]  -- Can be both a Datalog relation, or a externally defined function / constraint\n  | ExternDefinition NodeId Id [(Maybe Id, Type)] (Maybe Type)\n  | DeclareType NodeId Id [(Maybe Id, Type)] Attributes\n  | Module NodeId [Decl]\n  deriving (Eq, Show)\n\npattern PWildcard :: NodeId -> AST\npattern PWildcard nodeId\n  = Var nodeId (Id \"_\")\n\nmakeBaseFunctor ''AST\n\npattern PWildcardF :: NodeId -> ASTF r\npattern PWildcardF nodeId\n  = VarF nodeId (Id \"_\")\n\ngetNodeId :: AST -> NodeId\ngetNodeId = \\case\n  Module nodeId _ -> nodeId\n  DeclareType nodeId _ _ _ -> nodeId\n  ExternDefinition nodeId _ _ _ -> nodeId\n  Rule nodeId _ _ _ -> nodeId\n  Not nodeId _ -> nodeId\n  Atom nodeId _ _ -> nodeId\n  BinOp nodeId _ _ _ -> nodeId\n  Constraint nodeId _ _ _ -> nodeId\n  Lit nodeId _ -> nodeId\n  Var nodeId _ -> nodeId\n  Hole nodeId -> nodeId\n\ngetNodeIdF :: ASTF a -> NodeId\ngetNodeIdF = \\case\n  ModuleF nodeId _ -> nodeId\n  DeclareTypeF nodeId _ _ _ -> nodeId\n  ExternDefinitionF nodeId _ _ _ -> nodeId\n  RuleF nodeId _ _ _ -> nodeId\n  NotF nodeId _ -> nodeId\n  AtomF nodeId _ _ -> nodeId\n  BinOpF nodeId _ _ _ -> nodeId\n  ConstraintF nodeId _ _ _ -> nodeId\n  LitF nodeId _ -> nodeId\n  VarF nodeId _ -> nodeId\n  HoleF nodeId -> nodeId\n\ngetExternDefs :: AST -> [Extern]\ngetExternDefs = cata $ \\case\n  ExternDefinitionF _ name argTys mRetTy ->\n    let extKind = if isJust mRetTy then ExternFunction else ExternConstraint\n     in one $ Extern name (length argTys) extKind\n  astf ->\n    fold astf\n\ninstance Pretty Type where\n  pretty = \\case\n    U32 -> \"u32\"\n    Str -> \"string\"\n    TUnknown x -> \"ty\" <> show x\n\ndata RenderPosition = TopLevel | Nested\n\ninstance Pretty AST where\n  pretty ast = runReader (pretty' ast) TopLevel\n    where\n      pretty' = \\case\n        Lit _ x ->\n          pure $ pretty x\n        Var _ v ->\n          pure $ pretty v\n        Hole _ ->\n          pure \"?\"\n        BinOp _ op lhs rhs -> do\n          lhs' <- pretty' lhs\n          rhs' <- pretty' rhs\n          pure $ parens $ lhs' <+> pretty op <+> rhs'\n        Constraint _ op lhs rhs -> do\n          lhs' <- pretty' lhs\n          rhs' <- pretty' rhs\n          pure $ lhs' <+> pretty op <+> rhs'\n        Not _ clause ->\n          (\"!\" <>) <$> pretty' clause\n        Atom _ name values -> do\n          end <- ask <&> \\case\n            TopLevel -> \".\"\n            Nested -> mempty\n          values' <- traverse pretty' values\n          pure $ pretty name <> parens (withCommas values') <> end\n        Rule _ name values clauses -> do\n          (values', clauses') <- local (const Nested) $ do\n            (,) <$> traverse pretty' values <*> traverse pretty' clauses\n          let separators = replicate (length clauses - 1) \",\" ++ [\".\"]\n          pure $ pretty name <> parens (withCommas values') <+> \":-\" <> hardline <>\n                indent 2 (vsep (zipWith (<>) clauses' separators))\n        ExternDefinition _ name args mRetTy -> do\n          let prettyRetTy = case mRetTy of\n                Just retTy -> \" \" <> pretty retTy\n                Nothing    -> mempty\n          pure $ \"@extern\" <+> pretty name <> parens (withCommas $ map prettyArg args)\n                    <> prettyRetTy <> \".\"\n        DeclareType _ name tys attrs ->\n          pure $ \"@def\"\n            <+> pretty name\n             <> parens (withCommas $ map prettyArg tys)\n             <> prettyAttrs\n             <> \".\"\n          where\n            prettyAttrs = case attrs of\n              Internal -> \"\"\n              Input -> \" input\"\n              Output -> \" output\"\n              InputOutput -> \" input output\"\n        Module _ decls -> do\n          decls' <- traverse pretty' decls\n          pure $ vsep $ intersperse mempty decls'\n\n      prettyArg (mName, ty) =\n        maybe (pretty ty) (\\fieldName -> pretty fieldName <> \":\" <+> pretty ty) mName\n"
  },
  {
    "path": "lib/Eclair/AST/Lower.hs",
    "content": "module Eclair.AST.Lower\n  ( compileToRA\n  ) where\n\nimport Prelude hiding (swap, project)\nimport qualified Data.Graph as G\nimport qualified Data.Map as M\nimport Eclair.AST.Codegen\nimport Eclair.AST.IR hiding (Clause)\nimport Eclair.Common.Id\nimport Eclair.Common.Location (NodeId(..))\nimport qualified Eclair.RA.IR as RA\nimport Eclair.Common.Extern\n\n\ntype RA = RA.RA\ntype Relation = RA.Relation\n\ncompileToRA :: [Extern] -> AST -> RA\ncompileToRA externs ast =\n  RA.Module (NodeId 0) $ concatMap processDecls sortedDecls\n  where\n    sortedDecls = scc ast\n\n    processDecls :: [AST] -> [RA]\n    processDecls = \\case\n      [Atom _ name values] -> runCodegen externs $\n        let literals = map toTerm values\n        in one <$> project name literals\n      [Rule _ name args clauses] ->\n        let terms = map toTerm args\n        in runCodegen externs $ processSingleRule [name] name terms clauses\n      rules ->  -- case for multiple mutually recursive rules\n        let sccNames = rules & mapMaybe (\\case\n              Rule _ name _ _ -> Just name\n              _ -> Nothing)\n        in runCodegen externs $ processMultipleRules sccNames rules\n\n    scc :: AST -> [[AST]]\n    scc = \\case\n      Module _ decls -> map G.flattenSCC sortedDecls'\n        where\n          relevantDecls = filter isRelevant decls\n          sortedDecls' = G.stronglyConnComp $ zipWith (\\i d -> (d, i, refersTo d)) [0..] relevantDecls\n          declLineMapping = M.fromListWith (<>) $ zipWith (\\i d -> (nameFor d, [i])) [0..] relevantDecls\n          isRelevant = \\case\n            Atom {} -> True\n            Rule {} -> True\n            Not {} -> True\n            _ -> False\n          nameFor = \\case\n            Atom _ name _ -> name\n            Rule _ name _ _ -> name\n            _ ->  unreachable  -- Because of \"isRelevant\"\n          refersTo :: AST -> [Int]\n          refersTo = \\case\n            Rule _ _ _ clauses ->\n              -- If no top level facts are defined, no entry exists in declLine mapping -> default to -1\n              concatMap (fromMaybe [-1] . flip M.lookup declLineMapping . dependsOn) $ filter isRelevant clauses\n            _ -> []\n          dependsOn = \\case\n            Atom _ name _ -> name\n            Rule _ name _ _ -> name\n            Not _ (Atom _ name _) -> name\n            _ ->  unreachable  -- Because of \"isRelevant\"\n      _ -> unreachable         -- Because rejected by parser\n      where unreachable = panic \"Unreachable code in 'scc'\"\n\n-- NOTE: These rules can all be evaluated in parallel inside the fixpoint loop\nprocessMultipleRules :: [Relation] -> [AST] -> CodegenM [RA]\nprocessMultipleRules sccNames rules = sequence stmts where\n  stmts = mergeStmts <> [loop (purgeStmts <> ruleStmts <> [exitStmt] <> endLoopStmts)]\n  mergeStmts = map (\\r -> merge r (deltaRelationOf r)) uniqRelations\n  purgeStmts = map (purge . newRelationOf) uniqRelations\n  ruleStmts = [parallel $ map lowerRule rulesInfo]\n  exitStmt = exit $ map newRelationOf uniqRelations\n  endLoopStmts = concatMap toMergeAndSwapStmts uniqRelations\n  toMergeAndSwapStmts r =\n    let newRelation = newRelationOf r\n        deltaRelation = deltaRelationOf r\n     in [merge newRelation r, swap newRelation deltaRelation]\n  rulesInfo = mapMaybe extractRuleData rules\n  relations = map (\\(r, _, _) -> r) rulesInfo\n  uniqRelations = uniqOrderPreserving relations\n  -- TODO: better func name\n  lowerRule (r, map toTerm -> ts, clauses) =\n    recursiveRuleToStmts sccNames r ts clauses\n\nprocessSingleRule :: [Relation] -> Relation -> [CodegenM RA] -> [AST] -> CodegenM [RA]\nprocessSingleRule sccNames relation terms clauses\n  | isRecursive sccNames clauses =\n    let deltaRelation = deltaRelationOf relation\n        newRelation = newRelationOf relation\n        stmts =\n          [ merge relation deltaRelation\n          , loop\n            [ purge newRelation\n            , ruleToStmt sccNames relation terms clauses\n            , exit [newRelation]\n            , merge newRelation relation\n            , swap newRelation deltaRelation\n            ]\n          ]\n      in sequence stmts\n  | otherwise = one <$> ruleToStmt sccNames relation terms clauses\n\nruleToStmt :: [Relation] -> Relation -> [CodegenM RA] -> [AST] -> CodegenM RA\nruleToStmt sccNames relation terms clauses\n  | isRecursive sccNames clauses =\n    recursiveRuleToStmts sccNames relation terms clauses\n  | otherwise = nestedSearchAndProject relation terms clauses mempty\n\nrecursiveRuleToStmts :: [Relation] -> Relation -> [CodegenM RA] -> [AST] -> CodegenM RA\nrecursiveRuleToStmts sccNames relation terms clauses =\n  parallel $\n    [ stmt\n    | i <- [0..sccClauseCount - 1]\n    , let sccAtom = maybeAt i sccAtoms\n          clauses' = map (maybeToDeltaClause sccAtom) clauses\n          sccAtoms' = drop (i + 1) sccAtoms\n          stmt = nestedSearchAndProject newRelation terms clauses' sccAtoms'\n    ]\n  where\n    newRelation = newRelationOf relation\n    sccAtoms = clauses & filter isPartOfScc & mapMaybe (\\case\n      Atom _ name args -> Just (name, args)\n      _ -> Nothing)\n    sccClauseCount = length sccAtoms\n    isPartOfScc = \\case\n      Atom _ name _  -> name `elem` sccNames\n      _ -> False\n    maybeToDeltaClause sccAtom = \\case\n      Atom nodeId clauseName args | sccAtom == Just (clauseName, args) ->\n        Atom nodeId (deltaRelationOf clauseName) args\n      clause -> clause\n\nnestedSearchAndProject\n  :: Relation\n  -> [CodegenM RA]\n  -> [AST]\n  -> [(Relation, [AST])]\n  -> CodegenM RA\nnestedSearchAndProject intoRelation terms clauses sccAtoms =\n  flip (foldr processRuleClause) clauses $\n    addNegatedDeltaAtoms sccAtoms $\n      project intoRelation terms\n  where\n    processRuleClause :: AST -> CodegenM RA -> CodegenM RA\n    processRuleClause clause inner = case clause of\n      Not _ (Atom _ clauseName args) -> do\n        -- No starts with check here, since cyclic negation is not allowed.\n        let terms' = map toTerm args\n        noElemOf clauseName terms' inner\n\n      Constraint _ op lhs rhs -> do\n        lhsTerm <- toTerm lhs\n        rhsTerm <- toTerm rhs\n        if' op lhsTerm rhsTerm inner\n\n      Atom _ clauseName args -> do\n        externs <- asks envExterns\n        let isExtern = isJust $ find (\\(Extern name _ _) -> clauseName == name) externs\n        if isExtern\n          then do\n            clause' <- toTerm clause\n            zero <- toTerm (Lit (NodeId 0) $ LNumber 0)\n            if' NotEquals clause' zero inner\n          else search clauseName args inner\n      _ ->\n        panic \"Unexpected rule clause in 'nestedSearchAndProject'!\"\n\n    addNegatedDeltaAtoms =\n      foldr (\\(clauseName, args) wrapper -> wrapper . addNegatedDeltaAtom clauseName args) id\n\naddNegatedDeltaAtom :: Relation -> [AST] -> CodegenM RA -> CodegenM RA\naddNegatedDeltaAtom clauseName args =\n  noElemOf (deltaRelationOf clauseName) (map toTerm args)\n\nisRecursive :: [Relation] -> [AST] -> Bool\nisRecursive sccNames clauses =\n  let atomNames = flip mapMaybe clauses $ \\case\n        Atom _ name _ -> Just name\n        _ -> Nothing\n   in any (`elem` atomNames) sccNames\n\nextractRuleData :: AST -> Maybe (Relation, [AST], [AST])\nextractRuleData = \\case\n  Rule _ name args clauses -> Just (name, args, clauses)\n  _ -> Nothing\n\nnewRelationOf, deltaRelationOf :: Relation -> Relation\ndeltaRelationOf = prependToId deltaPrefix\nnewRelationOf = prependToId newPrefix\n"
  },
  {
    "path": "lib/Eclair/AST/Transforms/ConstantFolding.hs",
    "content": "module Eclair.AST.Transforms.ConstantFolding\n  ( transform\n  ) where\n\nimport Eclair.AST.IR\nimport Eclair.Transform\n\ntransform :: Transform AST AST\ntransform = pureTransform $ cata $ \\case\n  BinOpF nodeId op (Lit _ (LNumber lhs)) (Lit _ (LNumber rhs)) ->\n    let opFn = case op of\n          Plus -> (+)\n          Minus -> (-)\n          Multiply -> (*)\n          Divide -> div\n    in Lit nodeId $ LNumber $ opFn lhs rhs\n  ast ->\n    embed ast\n"
  },
  {
    "path": "lib/Eclair/AST/Transforms/DeadCodeElimination.hs",
    "content": "module Eclair.AST.Transforms.DeadCodeElimination\n  ( transform\n  ) where\n\nimport Eclair.Transform\nimport Eclair.AST.Analysis\nimport Eclair.AST.IR\n\n\ntransform :: Container DeadCode -> Transform AST AST\ntransform analysis =\n  pureTransform $ cata $ \\case\n    ModuleF nodeId decls ->\n      Module nodeId $ filter (not . isDead) decls\n    RuleF nodeId name args clauses ->\n      Rule nodeId name args $\n        filter (\\c -> not (isDead c || isRedundantAssign c)) clauses\n    astf ->\n      embed astf\n  where\n    deadCodeNodeIds =\n      map unDeadCode analysis\n\n    isDead ast =\n      getNodeId ast `elem` deadCodeNodeIds\n\n    isRedundantAssign = \\case\n      Constraint _ Equals lhs rhs -> lhs == rhs\n      _ -> False\n"
  },
  {
    "path": "lib/Eclair/AST/Transforms/NormalizeRules.hs",
    "content": "module Eclair.AST.Transforms.NormalizeRules\n  ( transform\n  ) where\n\nimport Data.List (partition)\nimport Eclair.Transform\nimport Eclair.AST.IR\n\n-- This transform prepares the AST for lowering to RA by:\n-- 1. Shifting all constraints and negations to the end.\n\ntransform :: Transform AST AST\ntransform =\n  pureTransform $ cata rewriteVars\n  where\n    rewriteVars = \\case\n      RuleF nodeId name values clauses -> do\n        let (matching, rest) = partition isConstraintOrNegation clauses\n            (constraints, negations) = partition isConstraint matching\n        Rule nodeId name values $ rest <> constraints <> negations\n      astf ->\n        embed astf\n\n    isConstraintOrNegation :: AST -> Bool\n    isConstraintOrNegation = \\case\n      Constraint {} -> True\n      Not {} -> True\n      _ -> False\n\n    isConstraint :: AST -> Bool\n    isConstraint = \\case\n      Constraint {} -> True\n      _ -> False\n"
  },
  {
    "path": "lib/Eclair/AST/Transforms/RemoveAliases.hs",
    "content": "module Eclair.AST.Transforms.RemoveAliases\n  ( transform\n  ) where\n\nimport qualified Data.Map as M\nimport Eclair.Transform\nimport Eclair.AST.IR\nimport Eclair.Comonads\nimport Eclair.Common.Extern\n\n-- This transform reduces the amount of helper variables used in assignments.\n\ntransform :: [Extern] -> Transform AST AST\ntransform externs =\n  Transform $ usingReaderT Nothing . gcata (distribute directlyGroundedVars equatedVars) rewrite\n  where\n    distribute :: Corecursive t\n               => (Base t (t, a) -> a)\n               -> (Base t (t, b) -> b)\n               -> Base t (Quad t a b c) -> Quad t a b (Base t c)\n    distribute f g m =\n      let base_t_t = map qFirst m\n          base_t_ta = map (qFirst &&& qSecond) m\n          base_t_tb = map (qFirst &&& qThird) m\n          base_t_c = map qFourth m\n       in Quad (embed base_t_t) (f base_t_ta) (g base_t_tb) base_t_c\n\n    externNames = map (\\(Extern name _ _) -> name) externs\n\n    -- Finds all vars directly inside a relation atom (not in a negation).\n    directlyGroundedVars = \\case\n      AtomF _ name args | name `notElem` externNames ->\n        flip mapMaybe args $ \\case\n          (Var _ v, _) -> Just v\n          _ -> Nothing\n      NotF {} -> mempty\n      astf ->\n        foldMap snd astf\n\n    -- Find all vars used in equalities\n    equatedVars = \\case\n      ConstraintF _ Equals lhs rhs -> case (fst lhs, fst rhs) of\n        (lhs'@(Var _ v1), rhs'@(Var _ v2)) ->\n          [(v1, rhs'), (v2, lhs')]\n        (Var _ v, rhs') ->\n          one (v, rhs')\n        (lhs', Var _ v) ->\n          one (v, lhs')\n        _ ->\n          mempty\n      astf ->\n        foldMap snd astf\n\n    -- Aliases = equated vars - directly grounded vars\n    findAliases dgVars = filter ((`notElem` dgVars) . fst)\n\n    rewrite = \\case\n      RuleF nodeId name args clauses -> do\n        let dgVars = concatMap qSecond clauses\n            eqs = concatMap qThird clauses\n            subst = M.fromList $ filter (not . occursCheck) $ findAliases dgVars eqs\n        local (const $ Just subst) $\n          Rule nodeId name\n            <$> traverse extract args\n            <*> traverse extract clauses\n      VarF nodeId v -> do\n        let var = Var nodeId v\n        -- Because of how the substitution is constructed,\n        -- it's always safe to try and replace variables.\n        maybe var (`resolveAliases` var) <$> ask\n      astf ->\n        embed <$> traverse extract astf\n\n    resolveAliases subst =\n      ana $ \\case\n        Var nodeId v ->\n          maybe (VarF nodeId v) project $ M.lookup v subst\n        ast -> project ast\n\n    -- Occurs check is needed to prevent aliases from growing larger and larger.\n    occursCheck (v, ast) =\n      let vars = flip cata ast $ \\case\n            VarF _ var -> [var]\n            astf -> fold astf\n      in v `elem` vars\n"
  },
  {
    "path": "lib/Eclair/AST/Transforms/ReplaceStrings.hs",
    "content": "module Eclair.AST.Transforms.ReplaceStrings\n  ( StringMap\n  , transform\n  ) where\n\nimport qualified Data.Map as Map\nimport Eclair.AST.IR\nimport Eclair.Common.Id\nimport Eclair.Transform\n\n\n-- NOTE: \"String\" here means an eclair string!\n\ntype StringMap = Map Text Word32\n\ntransform :: Transform AST (AST, StringMap)\ntransform =\n  Transform $ usingStateT mempty . cata rewrite\n  where\n    rewrite :: RewriteRuleT (StateT StringMap) AST\n    rewrite = \\case\n      DeclareTypeF nodeId name tys attrs -> do\n        -- By putting the relation names in the symbol table, we can easily\n        -- create a unique integer constant for the fact type mapping.\n        _ <- replaceString (unId name)\n        pure $ DeclareType nodeId name tys attrs\n      LitF nodeId (LString s) ->\n        Lit nodeId . LNumber <$> replaceString s\n      astf ->\n        embed <$> sequence astf\n\nreplaceString :: Monad m => Text -> StateT StringMap m Word32\nreplaceString str = do\n  value <- gets $ \\strMap ->\n    let count = fromIntegral $ length strMap\n     in Map.findWithDefault count str strMap\n\n  modify $ Map.insert str value\n  pure value\n\n"
  },
  {
    "path": "lib/Eclair/AST/Transforms.hs",
    "content": "module Eclair.AST.Transforms\n  ( simplify\n  , ReplaceStrings.StringMap\n  ) where\n\nimport Eclair.AST.IR\nimport Eclair.AST.Analysis\nimport Eclair.Transform\nimport qualified Eclair.AST.Transforms.ConstantFolding as ConstantFolding\nimport qualified Eclair.AST.Transforms.RemoveAliases as RemoveAliases\nimport qualified Eclair.AST.Transforms.DeadCodeElimination as DCE\nimport qualified Eclair.AST.Transforms.ReplaceStrings as ReplaceStrings\nimport qualified Eclair.AST.Transforms.NormalizeRules as NormalizeRules\nimport Eclair.Common.Extern\n\n\n-- Transforms can be grouped into 3 parts:\n--\n-- 1. transforms that need to run a single time, before optimizations\n-- 2. main optimization pipeline (runs until fixpoint is reached)\n-- 3. transforms that need to run a single time, after optimizations\n\n\nsimplify :: NodeId -> [Extern] -> SemanticInfo -> AST -> (AST, ReplaceStrings.StringMap)\nsimplify nodeId externs analysis =\n  runTransform nodeId\n    -- Transforms before optimizations:\n      $ ConstantFolding.transform\n\n    -- Optimizations that run until fixpoint is reached:\n    >>> RemoveAliases.transform externs\n    >>> ConstantFolding.transform\n    >>> DCE.transform (deadCodeIds analysis)\n\n    -- Transforms after optimizations:\n    >>> NormalizeRules.transform\n    >>> ReplaceStrings.transform\n"
  },
  {
    "path": "lib/Eclair/ArgParser.hs",
    "content": "module Eclair.ArgParser\n  ( parseArgs\n  , parser\n  , Config(..)\n  , CompileConfig(..)\n  , EmitKind(..)\n  , Target(..)\n  ) where\n\nimport Eclair.Common.Config\nimport Options.Applicative\nimport qualified Data.List.Extra as L\n\n\nparseArgs :: [String] -> IO Config\nparseArgs = handleParseResult . execParserPure parserPrefs parserInfo\n  where\n    desc = fullDesc <> progDesc \"The Eclair Datalog compiler.\"\n    parserPrefs = prefs $ showHelpOnError <> showHelpOnEmpty\n    parserInfo = info (parser <**> helper) desc\n\nparser :: Parser Config\nparser = hsubparser (longCompileCommand <> shortCompileCommand)\n      <|> hsubparser lspCommand\n  where\n    longCompileCommand = command \"compile\" compileCommand\n    shortCompileCommand = command \"c\" compileCommand\n    compileCommand = info compileParser compileDesc\n    compileDesc = fullDesc <> header \"eclair compile\" <> progDesc \"Compiles Datalog files.\"\n    lspCommand = command \"lsp\" $ info (pure LSP) lspDesc\n    lspDesc = fullDesc <> header \"eclair lsp\" <> progDesc \"Runs the Eclair LSP server.\"\n\ncompileParser :: Parser Config\ncompileParser = Compile <$> compileParser'\n  where\n    compileParser' =\n      CompileConfig <$> argument str (metavar \"FILE\" <> help \"The main Datalog file to compile.\")\n                    <*> emitKindParser\n                    <*> optional targetParser\n                    <*> numCoresParser\n\ntargetParser :: Parser Target\ntargetParser =\n  option (maybeReader parseTarget) $ metavar \"TARGET\" <> long \"target\" <> short 't' <> help desc\n  where\n    desc = \"Select the target CPU architecture. Default is to use the host architecture. Supported options: 'wasm32'.\"\n    parseTarget = \\case\n      \"wasm32\" -> Just Wasm32\n      _ -> Nothing\n\nemitKindParser :: Parser EmitKind\nemitKindParser =\n  option (maybeReader readEmitKind) (long \"emit\" <> value EmitLLVM <> help desc)\n  where\n    readEmitKind opt = case L.lower opt of\n      \"ast-transformed\" -> Just EmitTransformedAST\n      \"ra\" -> Just EmitRA\n      \"ra-transformed\" -> Just EmitTransformedRA\n      \"eir\" -> Just EmitEIR\n      \"llvm\" -> Just EmitLLVM\n      \"souffle\" -> Just EmitSouffle\n      _ -> Nothing\n    desc = \"Compile to a specific format. Defaults to LLVM IR. Supported options: 'ast-transformed, 'ra', 'ra-transformed', 'eir', 'llvm' and 'souffle'.\"\n\nnumCoresParser :: Parser Word\nnumCoresParser = option auto $\n  long \"jobs\"\n    <> metavar \"JOBS\"\n    <> short 'j'\n    <> value 1\n    <> help \"Number of threads used for compilation.\"\n"
  },
  {
    "path": "lib/Eclair/Common/Config.hs",
    "content": "module Eclair.Common.Config\n  ( EmitKind(..)\n  , CompileConfig(..)\n  , Target(..)\n  , Config(..)\n  ) where\n\ndata EmitKind\n  = EmitTransformedAST\n  | EmitRA\n  | EmitTransformedRA\n  | EmitEIR\n  | EmitLLVM\n  | EmitSouffle\n  -- TODO: object file, WASM, ...\n  deriving (Eq, Show)\n\n-- TODO: optimization levels (-Ox), include dirs (-I), logging level (-q, -v), timing, ...\ndata CompileConfig\n  = CompileConfig\n  { mainFile :: FilePath\n  , emitKind :: EmitKind\n  , cpuTarget :: Maybe Target  -- Nothing = compile to host architecture\n  , numCores :: Word -- Maximum number of cores Eclair is allowed to use\n  } deriving (Eq, Show)\n\ndata Target\n  = Wasm32\n  deriving (Eq, Show)\n\ndata Config\n  = Compile CompileConfig\n  | LSP\n  deriving (Eq, Show)\n"
  },
  {
    "path": "lib/Eclair/Common/Extern.hs",
    "content": "module Eclair.Common.Extern\n  ( Extern(..)\n  , ExternKind(..)\n  ) where\n\nimport Eclair.Common.Id\n\n\ndata Extern = Extern Id Int ExternKind\n  deriving (Eq, Show)\n\ndata ExternKind\n  = ExternConstraint\n  | ExternFunction\n  deriving (Eq, Show)\n\n"
  },
  {
    "path": "lib/Eclair/Common/Id.hs",
    "content": "module Eclair.Common.Id\n  ( Id(..)\n  , prependToId\n  , appendToId\n  , startsWithId\n  , stripIdPrefixes\n  , startsWithIdPrefix\n  , deltaPrefix\n  , newPrefix\n  ) where\n\nimport qualified Data.Text as T\nimport qualified Language.Souffle.Marshal as S\nimport Prettyprinter\n\n\nnewtype Id = Id { unId :: Text }\n  deriving (Eq, Ord, Show, Generic)\n  deriving anyclass S.Marshal\n\ninstance Pretty Id where\n  pretty = pretty . unId\n\n\nappendToId :: Id -> Text -> Id\nappendToId (Id x) y = Id (x <> y)\n\nprependToId :: Text -> Id -> Id\nprependToId x (Id y) = Id (x <> y)\n\nstartsWithId :: Id -> Id -> Bool\nstartsWithId (Id x) (Id start) =\n  start `T.isPrefixOf` x\n\nstripIdPrefixes :: Id -> Id\nstripIdPrefixes (Id x) = Id $ stripPrefixes x where\n  stripPrefixes t = foldl' stripPrefix t [deltaPrefix, newPrefix]\n  stripPrefix acc prefix = fromMaybe acc (T.stripPrefix prefix acc)\n\n-- TODO: make all prefixes starts with special symbol, invalid in syntax\ndeltaPrefix, newPrefix :: Text\ndeltaPrefix = \"delta_\"\nnewPrefix = \"new_\"\n\nstartsWithIdPrefix :: Id -> Bool\nstartsWithIdPrefix (Id x) =\n  any (`T.isPrefixOf` x) [deltaPrefix, newPrefix]\n"
  },
  {
    "path": "lib/Eclair/Common/Literal.hs",
    "content": "module Eclair.Common.Literal\n  ( Literal(..)\n  ) where\nimport Prettyprinter (Pretty (pretty), dquotes)\n\ndata Literal\n  = LNumber Word32\n  | LString Text\n  deriving (Eq, Show)\n\ninstance Pretty Literal where\n  pretty = \\case\n    LNumber x -> pretty x\n    LString x -> dquotes $ pretty x\n"
  },
  {
    "path": "lib/Eclair/Common/Location.hs",
    "content": "module Eclair.Common.Location\n  ( NodeId(..)\n  , Span(..)\n  , SpanMap(..)\n  , SourcePos(..)\n  , SourceSpan(..)\n  , insertSpan\n  , lookupSpan\n  , lookupNodeId\n  , spanToSourceSpan\n  ) where\n\nimport qualified Text.Megaparsec as P\nimport qualified Data.Map as M\nimport qualified Language.Souffle.Marshal as S\nimport Data.Maybe (fromJust)\n\nnewtype NodeId\n  = NodeId\n  { unNodeId :: Word32\n  } deriving (Eq, Ord, Show, Generic)\n  deriving S.Marshal\n\n-- A source span (begin and end position)\ndata Span\n  = Span\n  { beginPos :: {-# UNPACK #-} !Int\n  , endPos :: {-# UNPACK #-} !Int\n  } deriving Show\n\ndata SpanMap =\n  SpanMap\n  { spanMapPath :: !FilePath\n  , spanMapSpans :: !(Map Word32 Span)\n  }\n  deriving Show\n\ninsertSpan :: NodeId -> Span -> SpanMap -> SpanMap\ninsertSpan nodeId span' (SpanMap path m) =\n  SpanMap path (M.insert (unNodeId nodeId) span' m)\n\n-- NOTE: this assumes the node ID is generated by parsing the same file that resulted in the SpanMap.\nlookupSpan :: SpanMap -> NodeId -> Span\nlookupSpan (SpanMap _path m) nodeId =\n  fromJust $ M.lookup (unNodeId nodeId) m\n\n-- Finds the most specific NodeId (that corresponds with the smallest span)\nlookupNodeId :: SpanMap -> Int -> Maybe NodeId\nlookupNodeId (SpanMap _ m) offset =\n  m & M.toList\n    & filter (containsOffset . snd)\n    -- Just sorting by span size is not enough, sometimes we have two spans\n    -- with identical widths (e.g. with parentheses). The last one will always\n    -- be the node ID that belongs to the smallest (most specific) node.\n    & sortWith (spanSize . snd &&& negate . fst)\n    & viaNonEmpty head\n    & map (NodeId . fst)\n  where\n    containsOffset span' =\n      offset >= beginPos span' && offset < endPos span'\n\n    spanSize span' =\n      endPos span' - beginPos span'\n\n-- Helpers for producing error messages:\n\n-- Line and column information. 0-based!\ndata SourcePos\n  = SourcePos\n  { sourcePosLine :: {-# UNPACK #-} !Int\n  , sourcePosColumn :: {-# UNPACK #-} !Int\n  } deriving (Eq, Ord, Show)\n\ndata SourceSpan\n  = SourceSpan\n  { sourceSpanFile :: FilePath\n  , sourceSpanBegin :: {-# UNPACK #-} !SourcePos\n  , sourceSpanEnd :: {-# UNPACK #-} !SourcePos\n  } deriving (Eq, Show)\n\nspanToSourceSpan :: FilePath -> Text -> Span -> SourceSpan\nspanToSourceSpan path text span'@(Span begin end) =\n  either raiseError id parseResult\n  where\n    parseResult = P.runParser parser path text\n\n    parser :: P.Parsec Void Text SourceSpan\n    parser = do\n      _ <- P.takeP Nothing begin\n      beginPos' <- P.getSourcePos\n      _ <- P.takeP Nothing diff\n      endPos' <- P.getSourcePos\n      let beginSourcePos = SourcePos (line beginPos' - 1) (column beginPos' - 1)\n          endSourcePos = SourcePos (line endPos' - 1) (column endPos' - 1)\n      pure $ SourceSpan path beginSourcePos endSourcePos\n      where\n        diff = end - begin\n        line = P.unPos . P.sourceLine\n        column = P.unPos . P.sourceColumn\n\n    raiseError =\n      const $ panic $ \"Failed to get source location for file '\" <> toText path <> \"' and span \" <> show span'\n"
  },
  {
    "path": "lib/Eclair/Common/Operator.hs",
    "content": "module Eclair.Common.Operator\n  ( ArithmeticOp(..)\n  , LogicalOp(..)\n  , isEqualityOp\n  , invertLogicalOp\n  ) where\n\nimport Eclair.Common.Pretty\n\ndata ArithmeticOp\n  = Plus\n  | Minus\n  | Multiply\n  | Divide  -- NOTE: integer division\n  deriving (Eq, Show)\n\ndata LogicalOp\n  = Equals          -- =\n  | NotEquals       -- !=\n  | LessThan        -- <\n  | LessOrEqual     -- <=\n  | GreaterThan     -- >\n  | GreaterOrEqual  -- >=\n  deriving (Eq, Show)\n\ninvertLogicalOp :: LogicalOp -> LogicalOp\ninvertLogicalOp = \\case\n  Equals -> NotEquals\n  NotEquals -> Equals\n  LessThan -> GreaterOrEqual\n  GreaterThan -> LessOrEqual\n  LessOrEqual -> GreaterThan\n  GreaterOrEqual -> LessThan\n\nisEqualityOp :: LogicalOp -> Bool\nisEqualityOp = \\case\n  Equals -> True\n  NotEquals -> True\n  _ -> False\n\ninstance Pretty ArithmeticOp where\n  pretty = \\case\n    Plus -> \"+\"\n    Minus -> \"-\"\n    Multiply -> \"*\"\n    Divide -> \"/\"\n\ninstance Pretty LogicalOp where\n  pretty = \\case\n    Equals         -> \"=\"\n    NotEquals      -> \"!=\"\n    LessThan       -> \"<\"\n    LessOrEqual    -> \"<=\"\n    GreaterThan    -> \">\"\n    GreaterOrEqual -> \">=\"\n"
  },
  {
    "path": "lib/Eclair/Common/Pretty.hs",
    "content": "module Eclair.Common.Pretty\n  ( module Eclair.Common.Pretty\n  , module Prettyprinter\n  , module Prettyprinter.Render.Text\n  ) where\n\nimport Prettyprinter\nimport Prettyprinter.Render.Text\n\n\nprintDoc :: Pretty a => a -> Text\nprintDoc = renderStrict . layoutSmart defaultLayoutOptions . pretty\n\nindentation :: Int\nindentation = 2\n\ninterleaveWith :: Doc ann -> [Doc ann] -> Doc ann\ninterleaveWith d = hsep . punctuate d\n\nwithCommas :: [Doc ann] -> Doc ann\nwithCommas = interleaveWith comma\n\nwithAnds :: [Doc ann] -> Doc ann\nwithAnds = interleaveWith (space <> \"and\")\n\nbetween :: Doc ann -> Doc ann -> Doc ann -> Doc ann\nbetween begin end doc =\n  begin <> doc <> end\n\n"
  },
  {
    "path": "lib/Eclair/Comonads.hs",
    "content": "module Eclair.Comonads\n  ( module Eclair.Comonads\n  ) where\n\n\ndata Triple a b c\n  = Triple\n  { tFst :: a\n  , tSnd :: b\n  , tThd :: c\n  } deriving Functor\n\ninstance Comonad (Triple a b) where\n  extract (Triple _ _ c) = c\n\n  duplicate (Triple a b c) =\n    Triple a b (Triple a b c)\n\ndata Quad a b c d\n  = Quad\n  { qFirst :: a\n  , qSecond :: b\n  , qThird :: c\n  , qFourth :: d\n  } deriving Functor\n\ninstance Comonad (Quad a b c) where\n  extract (Quad _ _ _ d) = d\n\n  duplicate (Quad a b c d) =\n    Quad a b c (Quad a b c d)\n\n"
  },
  {
    "path": "lib/Eclair/EIR/IR.hs",
    "content": "{-# LANGUAGE TemplateHaskell #-}\n\nmodule Eclair.EIR.IR\n  ( EIR(..)\n  , EIRF(..)\n  , Relation\n  , Op(..)\n  , LogicalOp(..)\n  , ArithmeticOp(..)\n  , Type(..)\n  , Function(..)\n  , LabelId(..)\n  , Visibility(..)\n  ) where\n\nimport Eclair.Common.Id\nimport Eclair.Common.Operator\nimport Eclair.Common.Literal\nimport Eclair.Common.Pretty\nimport Eclair.RA.IndexSelection (Index)\nimport Eclair.LLVM.Metadata\n\n\ntype Relation = Id\n\ndata Type\n  = Program\n  | Value\n  | Iter\n  | Pointer Type\n  | Void\n  deriving (Eq, Show)\n\ndata Function\n  = InitializeEmpty\n  | Destroy\n  | Purge\n  | Swap\n  | InsertRange Relation Index  -- InsertRange specialized for this relation and index\n  | IsEmpty\n  | Size\n  | Contains\n  | Insert\n  | IterCurrent\n  | IterNext\n  | IterIsEqual\n  | IterLowerBound\n  | IterUpperBound\n  | IterBegin\n  | IterEnd\n  deriving (Eq, Show)\n\nnewtype LabelId\n  = LabelId Text\n  deriving (Eq, Show)\n\ninstance IsString LabelId where\n  fromString = LabelId . fromString\n\ndata Op\n  = RelationOp Relation Index Function  -- a primop for operations on relations\n  | SymbolTableInit\n  | SymbolTableDestroy\n  | SymbolTableInsert\n  | ComparisonOp LogicalOp\n  | ArithOp ArithmeticOp\n  | ExternOp Id\n  deriving (Eq, Show)\n\ndata Visibility\n  = Public\n  | Private\n  deriving (Eq, Show)\n\ndata EIR\n  = Module [EIR]  -- A module is the same as a block, but is rendered in a different way.\n  | Block [EIR]\n  | Function Visibility Text [Type] Type EIR\n  | FunctionArg Int\n  | DeclareProgram [(Relation, Metadata)]\n  | FieldAccess EIR Int\n  | Var Text\n  | Assign EIR EIR\n  | PrimOp Op [EIR]                     -- A primitive operation, these tend to be simple function calls or operators\n  | HeapAllocateProgram\n  | FreeProgram EIR\n  | StackAllocate Relation Index Type\n  | Par [EIR]\n  | Loop [EIR]\n  | If EIR EIR\n  | Not EIR\n  | And EIR EIR\n  | Jump LabelId\n  | Label LabelId\n  | Return EIR\n  | Lit Literal\n  deriving (Eq, Show)\n\nmakeBaseFunctor ''EIR\n\n\nindentBlock :: Doc ann -> Doc ann -> Doc ann -> Doc ann\nindentBlock begin end blk =\n  nest indentation (begin <> hardline <> blk) <> hardline <> end\n\nbraceBlock :: Doc ann -> Doc ann\nbraceBlock = indentBlock \"{\" \"}\"\n\nstatementBlock :: Pretty a => [a] -> Doc ann\nstatementBlock = braceBlock . vsep . map pretty\n\ninstance Pretty Type where\n  pretty = \\case\n    Program -> \"Program\"\n    Value -> \"Value\"\n    Iter -> \"Iter\"\n    Pointer ty -> \"*\" <> pretty ty\n    Void -> \"Void\"\n\ninstance Pretty Function where\n  pretty = \\case\n    InitializeEmpty -> \"init_empty\"\n    Destroy -> \"destroy\"\n    Purge -> \"purge\"\n    Swap -> \"swap\"\n    InsertRange r idx -> \"insert_range\" <> angles (pretty r <> pretty idx)\n    IsEmpty -> \"is_empty\"\n    Contains -> \"contains\"\n    Insert -> \"insert\"\n    IterCurrent -> \"iter_current\"\n    IterNext -> \"iter_next\"\n    IterIsEqual -> \"iter_is_equal\"\n    IterLowerBound -> \"iter_lower_bound\"\n    IterUpperBound -> \"iter_upper_bound\"\n    IterBegin -> \"iter_begin\"\n    IterEnd -> \"iter_end\"\n    Size -> \"size\"\n\ninstance Pretty LabelId where\n  pretty (LabelId label) = pretty label\n\ninstance Pretty Op where\n  pretty = \\case\n    SymbolTableInit ->\n      \"symbol_table.init\"\n    SymbolTableDestroy ->\n      \"symbol_table.destroy\"\n    SymbolTableInsert ->\n      \"symbol_table.insert\"\n    RelationOp r _idx fn ->\n      pretty r <> \".\" <> pretty fn\n    ComparisonOp op ->\n      -- Since `=` is already used for assignment in EIR, we use `==` for comparison.\n      if op == Equals then \"==\" else pretty op\n    ArithOp op ->\n      pretty op\n    ExternOp op ->\n      pretty op\n\ninstance Pretty EIR where\n  pretty = \\case\n    Module stmts ->\n      -- This adds newlines in between top level EIR statements\n      vsep $ intersperse mempty $ map pretty stmts\n    Block stmts ->\n      statementBlock stmts\n    Function visibility name tys retTy body ->\n      let fn = if visibility == Public\n                 then \"export fn\"\n                 else \"fn\"\n       in vsep [ fn <+> pretty name <> parens (withCommas $ map pretty tys) <+> \"->\" <+> pretty retTy\n               , pretty body -- Note: This is already a Block\n               ]\n    FunctionArg pos -> \"FN_ARG\" <> brackets (pretty pos)\n    DeclareProgram metadatas ->\n      vsep [ \"declare_type\" <+> \"Program\"\n           , braceBlock . vsep $\n               \"symbol_table\" : map (\\(r, meta) -> pretty r <+> pretty meta) metadatas\n           ]\n    FieldAccess ptr pos ->\n      pretty ptr <> \".\" <> pretty pos\n    Var v -> pretty v\n    Assign var value ->\n      pretty var <+> \"=\" <+> pretty value\n    PrimOp op [arg1, arg2] | isInfixPrimOp op ->\n      parens $ pretty arg1 <+> pretty op <+> pretty arg2\n    PrimOp op args ->\n      pretty op <> parens (withCommas $ map pretty args)\n    HeapAllocateProgram ->\n      \"heap_allocate_program\"\n    FreeProgram ptr ->\n      \"free_program\" <> parens (pretty ptr)\n    StackAllocate r _idx ty ->\n      pretty r <> \".\" <> \"stack_allocate\" <+> pretty ty\n    Par stmts ->\n      vsep [\"parallel\", statementBlock stmts]\n    Loop stmts ->\n      vsep [\"loop\", statementBlock stmts]\n    If cond body ->\n      let wrap = case body of\n            Block _ -> identity\n            _ -> braceBlock\n       in vsep [\"if\" <+> parens (pretty cond), wrap (pretty body)]\n    Not bool' ->\n      \"not\" <+> pretty bool'\n    And bool1 bool2 ->\n      pretty bool1 <+> \"&&\" <+> pretty bool2\n    Jump label ->\n      \"goto\" <+> pretty label\n    Label label ->\n      pretty label <> colon\n    Return value ->\n      \"return\" <+> pretty value\n    Lit x -> pretty x\n\nisInfixPrimOp :: Op -> Bool\nisInfixPrimOp = \\case\n  ComparisonOp {} -> True\n  ArithOp {} -> True\n  _ -> False\n"
  },
  {
    "path": "lib/Eclair/EIR/Lower/API.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.EIR.Lower.API\n  ( CodegenInOutT\n  , mkInOutState\n  , codegenAPI\n  , apiFunction\n  ) where\n\nimport Prelude hiding (void)\nimport Control.Monad.Morph\nimport Data.Traversable (for)\nimport Data.Maybe (fromJust)\nimport qualified Data.Set as S\nimport qualified Data.Map as M\nimport qualified Data.List as L\nimport qualified Eclair.EIR.IR as EIR\nimport qualified Eclair.LLVM.Symbol as Symbol\nimport qualified Eclair.LLVM.SymbolTable as SymbolTable\nimport Eclair.LLVM.Codegen as LLVM\nimport Eclair.LLVM.Metadata\nimport Eclair.EIR.Lower.Codegen\nimport Eclair.AST.IR (UsageMode(..))\nimport Eclair.RA.IndexSelection\nimport Eclair.Common.Id\n\n\ntype Relation = EIR.Relation\n\n-- Helper data type that pre-computes most of the important data.\ndata InOutState\n  = InOutState\n  { relationMapping :: Map Relation Word32\n  , relationNumColumns :: Map Relation Int\n  , relations :: Set Relation\n  , indicesByRelation :: Map Relation [Index]\n  , offsetsByRelationAndIndex :: Map (Relation, Index) Int\n  , inOutLowerState :: LowerState\n  }\n\ntype CodegenInOutT = ReaderT InOutState\n\ncodegenAPI :: Map Id Word32 -> Map Relation UsageMode -> [(Relation, Metadata)] -> LowerState -> ModuleBuilderT IO ()\ncodegenAPI relMapping usageMapping metas lowerState = do\n  usingReaderT (mkInOutState relMapping metas lowerState) $ do\n    addFactsFn <- generateAddFactsFn usageMapping\n    _ <- generateAddFact addFactsFn\n    _ <- generateGetFactsFn usageMapping\n    _ <- generateFreeBufferFn\n    _ <- generateFactCountFn usageMapping\n    _ <- generateEncodeStringFn\n    _ <- generateDecodeStringFn\n    pass\n\ngenerateAddFact :: MonadFix m => Operand -> CodegenInOutT (ModuleBuilderT m) Operand\ngenerateAddFact addFactsFn = do\n  lowerState <- asks inOutLowerState\n  let args = [ (ptr (programType lowerState), ParameterName \"eclair_program\")\n             , (i32, ParameterName \"fact_type\")\n             , (ptr i32, ParameterName \"memory\")\n             ]\n      returnType = void\n\n  apiFunction \"eclair_add_fact\" args returnType $ \\[program, factType, memory] -> do\n    _ <- call addFactsFn [program, factType, memory, int32 1]\n    retVoid\n\ngenerateAddFactsFn :: MonadFix m => Map Id UsageMode -> CodegenInOutT (ModuleBuilderT m) Operand\ngenerateAddFactsFn usageMapping = do\n  inOutState <- ask\n  let lowerState = inOutLowerState inOutState\n      rels = S.filter (isInputRelation usageMapping) $ relations inOutState\n      args = [ (ptr (programType lowerState), ParameterName \"eclair_program\")\n             , (i32, ParameterName \"fact_type\")\n             , (ptr i32, ParameterName \"memory\")\n             , (i32, ParameterName \"fact_count\")\n             ]\n      returnType = void\n  apiFunction \"eclair_add_facts\" args returnType $ \\[program, factType, memory, factCount] -> mdo\n    switchOnFactType rels (relationMapping inOutState) retVoid factType $ \\r -> do\n      indexes <- indicesForRelation r\n      for_ indexes $ \\idx -> do\n        numCols <- fromIntegral <$> numColsForRelation r\n        treeOffset <- int32 . toInteger <$> offsetForRelationAndIndex r idx\n        relationPtr <- gep program [int32 0, treeOffset]\n        -- TODO: don't re-calculate this type, do this based on value datatype created in each runtime data structure\n        let arrayPtr = ptrcast (ArrayType numCols i32) memory\n\n        loopFor (int32 0) (`ult` factCount) (add (int32 1)) $ \\i -> do\n          valuePtr <- gep arrayPtr [i]\n          fn <- toCodegenInOut lowerState $ lookupFunction r idx EIR.Insert\n          call fn [relationPtr, valuePtr]\n\n      br end -- early return\n\n    end <- blockNamed \"end\"\n    retVoid\n\ngenerateGetFactsFn :: MonadFix m => Map Relation UsageMode -> CodegenInOutT (ModuleBuilderT m) Operand\ngenerateGetFactsFn usageMapping = do\n  inOutState <- ask\n  let lowerState = inOutLowerState inOutState\n      rels = S.filter (isOutputRelation usageMapping) $ relations inOutState\n      args = [ (ptr (programType lowerState), ParameterName \"eclair_program\")\n             , (i32, ParameterName \"fact_type\")\n             ]\n      returnType = ptr i32\n      mallocFn = extMalloc $ externals lowerState\n  apiFunction \"eclair_get_facts\" args returnType $ \\[program, factType] -> do\n    switchOnFactType rels (relationMapping inOutState) (ret $ nullPtr i32) factType $ \\r -> do\n      indexes <- indicesForRelation r\n      let idx = fromJust $ findLongestIndex indexes\n          doCall op args' = do\n            fn <- toCodegenInOut lowerState $ lookupFunction r idx op\n            call fn args'\n      numCols <- numColsForRelation r\n      let valueSize = 4 * numCols  -- TODO: should use LLVM \"valueSize\" instead of re-calculating here\n      treeOffset <- int32 . toInteger <$> offsetForRelationAndIndex r idx\n      relationPtr <- gep program [int32 0, treeOffset]\n      relationSize <- doCall EIR.Size [relationPtr] >>= (`trunc` i32)\n      memorySize <- mul relationSize (int32 $ toInteger valueSize)\n      memory <- call mallocFn [memorySize]\n      let arrayPtr = ptrcast (ArrayType (fromIntegral numCols) i32) memory\n\n      iPtr <- alloca i32 (Just (int32 1)) 0\n      store iPtr 0 (int32 0)\n      let iterTy = evalState (toLLVMType r idx EIR.Iter) lowerState\n      currIter <- alloca iterTy (Just (int32 1)) 0\n      endIter <- alloca iterTy (Just (int32 1)) 0\n      _ <- doCall EIR.IterBegin [relationPtr, currIter]\n      _ <- doCall EIR.IterEnd [relationPtr, endIter]\n      let loopCondition = do\n            isEqual <- doCall EIR.IterIsEqual [currIter, endIter]\n            not' isEqual\n      loopWhile loopCondition $ do\n        i <- load iPtr 0\n        valuePtr <- gep arrayPtr [i]\n        currentVal <- doCall EIR.IterCurrent [currIter]\n        copy (mkPath []) currentVal valuePtr\n        i' <- add i (int32 1)\n        store iPtr 0 i'\n        doCall EIR.IterNext [currIter]\n\n      ret $ ptrcast i32 memory\n\ngenerateFreeBufferFn :: Monad m => CodegenInOutT (ModuleBuilderT m) Operand\ngenerateFreeBufferFn = do\n  lowerState <- asks inOutLowerState\n  let freeFn = extFree $ externals lowerState\n      args = [(ptr i32, ParameterName \"buffer\")]\n      returnType = void\n  apiFunction \"eclair_free_buffer\" args returnType $ \\[buf] -> mdo\n    let memory = ptrcast i8 buf\n    _ <- call freeFn [memory]\n    retVoid\n\ngenerateFactCountFn :: MonadFix m => Map Id UsageMode -> CodegenInOutT (ModuleBuilderT m) Operand\ngenerateFactCountFn usageMapping = do\n  inOutState <- ask\n  let lowerState = inOutLowerState inOutState\n      rels = S.filter (isOutputRelation usageMapping) $ relations inOutState\n      args = [ (ptr (programType lowerState), ParameterName \"eclair_program\")\n             , (i32, ParameterName \"fact_type\")\n             ]\n      returnType = i32\n  apiFunction \"eclair_fact_count\" args returnType $ \\[program, factType] -> do\n    switchOnFactType rels (relationMapping inOutState) (ret $ int32 0) factType $ \\r -> do\n      indexes <- indicesForRelation r\n      let idx = fromJust $ findLongestIndex indexes\n          doCall op args' = do\n            fn <- toCodegenInOut lowerState $ lookupFunction r idx op\n            call fn args'\n      treeOffset <- int32 . toInteger <$> offsetForRelationAndIndex r idx\n      relationPtr <- gep program [int32 0, treeOffset]\n      relationSize <- doCall EIR.Size [relationPtr]\n      ret =<< trunc relationSize i32\n\n\n-- NOTE: string does not need to be 0-terminated, length field is used to determine length (in bytes).\n-- Eclair makes an internal copy of the string, for simpler memory management.\ngenerateEncodeStringFn :: MonadFix m => CodegenInOutT (ModuleBuilderT m) Operand\ngenerateEncodeStringFn = do\n  lowerState <- asks inOutLowerState\n  let args = [ (ptr (programType lowerState), \"eclair_program\")\n             , (i32, \"string_length\")\n             , (ptr i8, \"string_data\")\n             ]\n      (symbolTable, symbol) = (symbolTableFns &&& symbolFns) lowerState\n      exts = externals lowerState\n\n  apiFunction \"eclair_encode_string\" args i32 $ \\[program, len, stringData] -> do\n    stringDataCopy <- call (extMalloc exts) [len]\n    lenBytes <- zext len i64\n    _ <- call (extMemcpy exts) [stringDataCopy, stringData, lenBytes, bit 0]\n\n    symbolPtr <- alloca (Symbol.tySymbol symbol) (Just (int32 1)) 0\n    _ <- call (Symbol.symbolInit symbol) [symbolPtr, len, stringDataCopy]\n\n    symbolTablePtr <- getSymbolTablePtr program\n    index <- call (SymbolTable.symbolTableLookupIndex symbolTable) [symbolTablePtr, symbolPtr]\n    alreadyContainsSymbol <- index `ne` int32 0xFFFFFFFF\n    if' alreadyContainsSymbol $ do\n      -- Since the string was not added to the table, the memory pointed to by\n      -- the symbol is not managed by the symbol table, so we need to manually free the data.\n      _ <- call (extFree exts) [stringDataCopy]\n      ret index\n\n    -- No free needed here, automatically called when symbol table is cleaned up.\n    ret =<< call (SymbolTable.symbolTableFindOrInsert symbolTable) [symbolTablePtr, symbolPtr]\n\n-- NOTE: do not free the returned string/byte array,\n-- this happens automatically when eclair_destroy is called\ngenerateDecodeStringFn :: MonadFix m => CodegenInOutT (ModuleBuilderT m) Operand\ngenerateDecodeStringFn = do\n  lowerState <- asks inOutLowerState\n  let args = [ (ptr (programType lowerState), \"eclair_program\")\n             , (i32, \"string_index\")\n             ]\n      symbolTable = symbolTableFns lowerState\n\n  apiFunction \"eclair_decode_string\" args (ptr i8) $ \\[program, idx] -> do\n    symbolTablePtr <- getSymbolTablePtr program\n    containsIndex <- call (SymbolTable.symbolTableContainsIndex symbolTable) [symbolTablePtr, idx]\n    if' containsIndex $ do\n      symbolPtr <- call (SymbolTable.symbolTableLookupSymbol symbolTable) [symbolTablePtr, idx]\n      ret $ ptrcast i8 symbolPtr\n\n    ret $ nullPtr i8\n\ntoCodegenInOut :: Monad m => LowerState -> CodegenT m Operand -> IRBuilderT (CodegenInOutT (ModuleBuilderT m)) Operand\ntoCodegenInOut lowerState m =\n  hoist lift $ runCodegenM m lowerState\n\nmkInOutState :: Map Relation Word32 -> [(Relation, Metadata)] -> LowerState -> InOutState\nmkInOutState relMapping metas ls =\n  InOutState relMapping relNumCols rels indicesByRel offsetsByRelAndIndex ls\n    where\n      rs = map fst metas\n      -- NOTE: disregards all \"special\" relations, since they should not be visible to the end user!\n      rels = S.fromList $ filter (not . startsWithIdPrefix) rs\n      relNumCols =\n        M.fromList [ (r, numCols)\n                   | r <- rs\n                   , let numCols = getNumColumns (snd $ fromJust $ L.find ((== r) . fst) metas)\n                   ]\n      relInfos = M.keys $ fnsMap ls\n      indicesByRel =\n        M.fromAscListWith (<>) $ map (second one) $ sortWith fst relInfos\n      offsetsByRelAndIndex =\n        M.fromDistinctAscList\n          [ (ri, offset)\n          | ri <- sortNub relInfos\n          -- + 1 due to symbol table at position 0 in program struct\n          , let offset = 1 + fromJust (L.elemIndex ri relInfos)\n          ]\n\nindicesForRelation :: MonadReader InOutState m => Relation -> m [Index]\nindicesForRelation r = do\n  inOutState <- ask\n  pure . fromJust . M.lookup r $ indicesByRelation inOutState\n\noffsetForRelationAndIndex :: MonadReader InOutState m => Relation -> Index -> m Int\noffsetForRelationAndIndex r idx = do\n  inOutState <- ask\n  pure . fromJust . M.lookup (r, idx) $ offsetsByRelationAndIndex inOutState\n\nnumColsForRelation :: MonadReader InOutState m => Relation -> m Int\nnumColsForRelation r =\n  fromJust . M.lookup r . relationNumColumns <$> ask\n\nswitchOnFactType :: MonadFix m\n                 => Set Relation\n                 -> Map Relation Word32\n                 -> IRBuilderT m ()\n                 -> Operand\n                 -> (Relation -> IRBuilderT m ())\n                 -> IRBuilderT m ()\nswitchOnFactType rels stringMap defaultCase factType generateCase = mdo\n    switch factType end caseBlocks\n    caseBlocks <- for (M.toList relMapping) $ \\(r, factNum) -> do\n      caseBlock <- blockNamed (unId r)\n      generateCase r\n      pure (int32 $ toInteger factNum, caseBlock)\n\n    end <- blockNamed \"switch.default\"\n    defaultCase\n  where\n    relMapping =\n      M.restrictKeys stringMap rels\n\n-- A helper function for easily finding the \"longest\" index.\n-- This is important for when you need to retrieve facts, since this index will\n-- contain the most facts.\nfindLongestIndex :: [Index] -> Maybe Index\nfindLongestIndex =\n  -- TODO use NonEmpty\n  viaNonEmpty head . sortOn (Dual . length . unIndex)\n\nisOutputRelation :: Map Relation UsageMode -> Relation -> Bool\nisOutputRelation usageMapping r =\n  case M.lookup r usageMapping of\n    Just Output -> True\n    Just InputOutput -> True\n    _ -> False\n\nisInputRelation :: Map Relation UsageMode -> Relation -> Bool\nisInputRelation usageMapping r =\n  case M.lookup r usageMapping of\n    Just Input -> True\n    Just InputOutput -> True\n    _ -> False\n\ngetSymbolTablePtr :: (MonadModuleBuilder m, MonadIRBuilder m)\n                  => Operand -> m Operand\ngetSymbolTablePtr program =\n  gep program [int32 0, int32 0]\n\napiFunction :: (MonadModuleBuilder m, HasSuffix m)\n            => Name\n            -> [(LLVM.Type, ParameterName)]\n            -> LLVM.Type\n            -> ([Operand] -> IRBuilderT m a)\n            -> m Operand\napiFunction fnName args retTy body =\n  withFunctionAttributes (WasmExportName (unName fnName):) $\n    function fnName args retTy body\n"
  },
  {
    "path": "lib/Eclair/EIR/Lower/Codegen.hs",
    "content": "module Eclair.EIR.Lower.Codegen\n  ( CodegenT\n  , runCodegenM\n  , LowerState(..)\n  , Table(..)\n  , Externals(..)\n  , labelToName\n  , lookupFunction\n  , lookupPrimOp\n  , toLLVMType\n  , lookupVar\n  , addVarBinding\n  , newGlobalVarName\n  , loadIfNeeded\n  ) where\n\nimport Prelude hiding (void)\nimport Control.Monad.Morph\nimport Data.Maybe (fromJust)\nimport qualified Data.Map as M\nimport Eclair.LLVM.Codegen\nimport Eclair.LLVM.Table\nimport Eclair.LLVM.Externals\nimport qualified Eclair.LLVM.Symbol as Symbol\nimport qualified Eclair.LLVM.SymbolTable as SymbolTable\nimport qualified Eclair.EIR.IR as EIR\nimport Eclair.RA.IndexSelection\nimport Eclair.Common.Id\n\n\ntype Relation = EIR.Relation\ntype EIR = EIR.EIR\n\ntype VarMap = Map Text Operand\ntype TableMap = Map (Relation, Index) Table\n\ndata LowerState\n  = LowerState\n  { programType :: Type\n  , programSizeBytes :: Word64\n  , symbolTableFns :: SymbolTable.SymbolTable\n  , symbolFns :: Symbol.Symbol\n  , fnsMap :: TableMap\n  , varMap :: VarMap\n  , globalVarCounter :: Int\n  , externals :: Externals\n  , externFns :: Map Id Operand\n  }\n\ntype CodegenT m = StateT LowerState (IRBuilderT (ModuleBuilderT m))\n\nrunCodegenM :: Monad m => CodegenT m a -> LowerState -> IRBuilderT (ModuleBuilderT m) a\nrunCodegenM = evalStateT\n\nlabelToName :: EIR.LabelId -> Name\nlabelToName (EIR.LabelId lbl) =\n  Name lbl\n\n-- This is a function mostly used by `lookupPrimOp`, but also for calling functions during fact IO\nlookupFunction :: Monad m => Relation -> Index -> EIR.Function -> CodegenT m Operand\nlookupFunction r idx fn = do\n  tableMap <- gets fnsMap\n  let table = unsafeLookup r idx tableMap\n  extractFn tableMap table\n  where\n    extractFn tableMap table = case fn of\n      EIR.InitializeEmpty -> pure $ fnInitEmpty table\n      EIR.Destroy -> pure $ fnDestroy table\n      EIR.Purge -> pure $ fnPurge table\n      EIR.Swap -> pure $ fnSwap table\n      EIR.InsertRange r2 idx2 -> do\n        let templatedFn = fnInsertRangeTemplate table\n            suffix = unId r <> \"_\" <> unId r2\n            table2 = unsafeLookup r2 idx2 tableMap\n            iterParams = IteratorParams\n              { ipIterCurrent = fnIterCurrent table2\n              , ipIterNext = fnIterNext table2\n              , ipIterIsEqual = fnIterIsEqual table2\n              , ipTypeIter = typeIter table2\n              }\n        -- I think we don't need to cache instantiations here (to avoid duplicate functions)\n        lift . lift $ hoist generalize $ instantiate suffix iterParams templatedFn\n      EIR.IsEmpty -> pure $ fnIsEmpty table\n      EIR.Size -> pure $ fnSize table\n      EIR.Contains -> pure $ fnContains table\n      EIR.Insert -> pure $ fnInsert table\n      EIR.IterCurrent -> pure $ fnIterCurrent table\n      EIR.IterNext -> pure $ fnIterNext table\n      EIR.IterIsEqual -> pure $ fnIterIsEqual table\n      EIR.IterLowerBound -> pure $ fnLowerBound table\n      EIR.IterUpperBound -> pure $ fnUpperBound table\n      EIR.IterBegin -> pure $ fnBegin table\n      EIR.IterEnd -> pure $ fnEnd table\n\n    unsafeLookup r' idx' = fromJust . M.lookup (r', idx')\n\ntype PrimOp m = Either Operand (Operand -> Operand -> CodegenT m Operand)\nlookupPrimOp :: Monad m => EIR.Op -> CodegenT m (PrimOp m)\nlookupPrimOp = \\case\n  EIR.SymbolTableInit ->\n    toSymbolTableOp SymbolTable.symbolTableInit\n  EIR.SymbolTableDestroy ->\n    toSymbolTableOp SymbolTable.symbolTableDestroy\n  EIR.SymbolTableInsert ->\n    toSymbolTableOp SymbolTable.symbolTableFindOrInsert\n  EIR.RelationOp r idx fn ->\n    Left <$> lookupFunction r idx fn\n  EIR.ComparisonOp op ->\n    pure $ Right $ case op of\n      EIR.Equals -> eq\n      EIR.NotEquals -> ne\n      -- NOTE: this will result in issues for signed integers in the future, but ignoring that for now..\n      -- We can pass along the type then?\n      EIR.LessThan -> ult\n      EIR.LessOrEqual -> ule\n      EIR.GreaterThan -> ugt\n      EIR.GreaterOrEqual -> uge\n  EIR.ArithOp op ->\n    -- NOTE: this will result in issues for signed integers in the future, but ignoring that for now..\n    -- We can pass along the type then?\n    pure $ Right $ case op of\n      EIR.Plus -> add\n      EIR.Minus -> sub\n      EIR.Multiply -> mul\n      EIR.Divide -> udiv\n  EIR.ExternOp opName -> do\n    Left <$> gets (fromJust . M.lookup opName . externFns)\n  where\n    toSymbolTableOp llvmOp = Left <$> do\n      symbolTable <- gets symbolTableFns\n      pure $ llvmOp symbolTable\n\ntoLLVMType :: MonadState LowerState m => Relation -> Index -> EIR.Type -> m Type\ntoLLVMType r idx = go\n  where\n    go = \\case\n      EIR.Program ->\n        programType <$> get\n      EIR.Iter ->\n        typeIter . fromJust . M.lookup (r, idx) <$> gets fnsMap\n      EIR.Value ->\n        typeValue . fromJust . M.lookup (r, idx) <$> gets fnsMap\n      EIR.Void ->\n        pure void\n      EIR.Pointer ty ->\n        ptr <$> go ty\n\n-- Only called internally, should always be called on a var that exists.\nlookupVar :: MonadState LowerState m => Text -> m Operand\nlookupVar v = gets (fromJust . M.lookup v . varMap)\n\naddVarBinding :: MonadState LowerState m => Text -> Operand -> m ()\naddVarBinding var value =\n  modify $ \\s -> s { varMap = M.insert var value (varMap s) }\n\nnewGlobalVarName :: MonadState LowerState m => Text -> m Name\nnewGlobalVarName name = do\n  count <- gets globalVarCounter\n  modify $ \\s -> s { globalVarCounter = count + 1 }\n  pure $ Name $ name <> \"_\" <> show count\n\n-- NOTE: this is for the case when we are assigning 1 field of a struct/array\n-- to another of the same kind, where the right side needs to be loaded before\n-- storing it to the left side of the equation.\nloadIfNeeded :: MonadIRBuilder m => m Operand -> EIR -> m Operand\nloadIfNeeded operand = \\case\n  EIR.FieldAccess _ _ -> flip load 0 =<< operand\n  _ -> operand\n"
  },
  {
    "path": "lib/Eclair/EIR/Lower/Externals.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.EIR.Lower.Externals\n  ( createExternals\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.EIR.Lower.Codegen\nimport Eclair.LLVM.Codegen as LLVM\nimport Eclair.Common.Config\n\n\ncreateExternals :: ConfigT (ModuleBuilderT IO) Externals\ncreateExternals = do\n  target <- cfgTargetTriple <$> getConfig\n  mallocFn <- lift $ generateMallocFn target\n  freeFn <- lift $ generateFreeFn target\n  memsetFn <- extern \"llvm.memset.p0i8.i64\" [ptr i8, i8, i64, i1] void\n  memcpyFn <- extern \"llvm.memcpy.p0i8.p0i8.i64\" [ptr i8, ptr i8, i64, i1] void\n  memcmpFn <- if target == Just Wasm32\n                then lift generateMemCmpFn\n                else extern \"memcmp\" [ptr i8, ptr i8, i64] i32\n  -- TODO write alternatives for WASM\n  mmapFn <- extern \"mmap\" [ptr void, i64, i32, i32, i32, i32] (ptr void)\n  munmapFn <- extern \"munmap\" [ptr void, i64] i32\n  pure $ Externals mallocFn freeFn memsetFn memcpyFn memcmpFn mmapFn munmapFn\n\ngenerateMallocFn :: Monad m => Maybe Target -> ModuleBuilderT m Operand\ngenerateMallocFn target = do\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  when (target == Just Wasm32) $ do\n    _ <- withFunctionAttributes (const [WasmExportName \"eclair_malloc\"]) $\n      function \"eclair_malloc\" [(i32, \"byte_count\")] (ptr i8) $ \\[byteCount] ->\n        ret =<< call mallocFn [byteCount]\n    pass\n\n  pure mallocFn\n\ngenerateFreeFn :: Monad m => Maybe Target -> ModuleBuilderT m Operand\ngenerateFreeFn target = do\n  freeFn <- extern \"free\" [ptr i8] void\n  when (target == Just Wasm32) $ do\n    _ <- withFunctionAttributes (const [WasmExportName \"eclair_free\"]) $\n      function \"eclair_free\" [(ptr i8, \"memory\")] void $ \\[memoryPtr] ->\n        call freeFn [memoryPtr]\n    pass\n\n  pure freeFn\n\n-- NOTE: we only care about 0 if they are equal!\ngenerateMemCmpFn :: MonadFix m => ModuleBuilderT m Operand\ngenerateMemCmpFn = do\n  let args = [(ptr i8, \"array1\"), (ptr i8, \"array2\"), (i64, \"byte_count\")]\n  function \"memcmp_wasm32\" args i32 $ \\[array1, array2, byteCount] -> do\n    i64Count <- byteCount `udiv` int64 8\n    restCount <- byteCount `and` int64 7  -- modulo 8\n    let i64Array1 = ptrcast i64 array1\n    let i64Array2 = ptrcast i64 array2\n\n    loopFor (int64 0) (`ult` i64Count) (add (int64 1)) $ \\i -> do\n      valuePtr1 <- gep i64Array1 [i]\n      valuePtr2 <- gep i64Array2 [i]\n      value1 <- load valuePtr1 0\n      value2 <- load valuePtr2 0\n\n      isNotEqual <- value1 `ne` value2\n      if' isNotEqual $\n        ret $ int32 1\n\n    startIdx <- mul i64Count (int64 8)\n    loopFor (int64 0) (`ult` restCount) (add (int64 1)) $ \\i -> do\n      idx <- add i startIdx\n      valuePtr1 <- gep array1 [idx]\n      valuePtr2 <- gep array2 [idx]\n      value1 <- load valuePtr1 0\n      value2 <- load valuePtr2 0\n\n      isNotEqual <- value1 `ne` value2\n      if' isNotEqual $\n        ret $ int32 1\n\n    ret $ int32 0\n"
  },
  {
    "path": "lib/Eclair/EIR/Lower.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.EIR.Lower\n  ( compileToLLVM\n  ) where\n\nimport Prelude hiding (void)\nimport qualified Prelude\nimport qualified Relude (swap)\nimport Control.Monad.Morph hiding (embed)\nimport qualified Data.ByteString as BS\nimport qualified Data.Map as M\nimport Data.List ((!!))\nimport Foreign.ForeignPtr\nimport qualified Eclair.EIR.IR as EIR\nimport qualified Eclair.LLVM.BTree as BTree\nimport qualified Eclair.LLVM.Symbol as Symbol\nimport qualified Eclair.LLVM.Vector as Vector\nimport qualified Eclair.LLVM.HashMap as HashMap\nimport qualified Eclair.LLVM.SymbolTable as SymbolTable\nimport Eclair.EIR.Lower.Codegen\nimport Eclair.EIR.Lower.Externals\nimport Eclair.EIR.Lower.API\nimport Eclair.LLVM.Codegen as LLVM\nimport qualified LLVM.C.API as LibLLVM\nimport Eclair.LLVM.Metadata\nimport Eclair.LLVM.Hash\nimport Eclair.RA.IndexSelection\nimport Eclair.Common.Config\nimport Eclair.Comonads\nimport Eclair.AST.IR\nimport Eclair.AST.Transforms.ReplaceStrings (StringMap)\nimport Eclair.Common.Id\nimport Eclair.Common.Extern\n\n\ntype EIR = EIR.EIR\ntype EIRF = EIR.EIRF\ntype Relation = EIR.Relation\n\ncompileToLLVM :: Maybe Target -> StringMap -> Map Relation UsageMode -> [Extern] -> EIR -> IO Module\ncompileToLLVM target stringMapping usageMapping externDefs eir = do\n  ctx <- LibLLVM.mkContext\n  llvmMod <- LibLLVM.mkModule ctx \"eclair\"\n  if target == Just Wasm32\n    then do\n      -- layout string found in Rust compiler (wasm32_unknown_unknown.rs)\n      let wasmDataLayout = \"e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20\"\n      td <- LibLLVM.mkTargetData wasmDataLayout\n      LibLLVM.setTargetData llvmMod td\n      withForeignPtr td $ \\tdPtr -> do\n        compile (Config target ctx tdPtr) eir\n    else do\n      -- use host layout\n      td <- LibLLVM.getTargetData llvmMod\n      compile (Config target ctx td) eir\n  where\n    compile cfg = \\case\n      EIR.Module (EIR.DeclareProgram metas : decls) -> runModuleBuilderT $ runConfigT cfg $ do\n        let ctx = cfgLLVMContext cfg\n            td = cfgTargetData cfg\n        exts <- createExternals\n        (metaMapping, fnss) <- runCacheT $ traverse (codegenRuntime exts . snd) metas\n        codegenDebugInfos metaMapping\n        (symbolTable, symbol) <- codegenSymbolTable exts\n        let symbolTableTy = SymbolTable.tySymbolTable symbolTable\n            fnsInfo = zip (map (map getIndexFromMeta) metas) fnss\n            fnsMap' = M.fromList fnsInfo\n        -- TODO: add hash based on filepath of the file we're compiling?\n        programTy <- typedef \"program\" Off (symbolTableTy : map typeObj fnss)\n        programSize <- withLLVMTypeInfo ctx $ llvmSizeOf ctx td programTy\n        lift $ do\n          externs <- traverse (processExtern symbolTableTy) externDefs\n          let externMap = M.fromList externs\n              lowerState = LowerState programTy programSize symbolTable symbol fnsMap' mempty 0 exts externMap\n          traverse_ (processDecl lowerState) decls\n          codegenAPI relMapping usageMapping metas lowerState\n      _ ->\n        panic \"Unexpected top level EIR declarations when compiling to LLVM!\"\n\n    relMapping =\n      M.mapKeys Id stringMapping\n\n    processExtern symbolTableTy (Extern fnName argCount extKind) = do\n      let argTys = ptr symbolTableTy : replicate argCount i32\n          retTy = if extKind == ExternConstraint then i1 else i32  -- i1, or i8 for bool?\n      fn <- extern (Name $ unId fnName) argTys retTy\n      pure (fnName, fn)\n\n    processDecl lowerState = \\case\n      EIR.Function visibility name tys retTy body -> do\n        let unusedRelation = panic \"Unexpected use of relation for function type when lowering EIR to LLVM.\"\n            unusedIndex = panic \"Unexpected use of index for function type when lowering EIR to LLVM.\"\n            getType ty = evalStateT (toLLVMType unusedRelation unusedIndex ty) lowerState\n        argTypes <- liftIO $ traverse getType tys\n        returnType <- liftIO $ getType retTy\n        let args = map (, ParameterName \"arg\") argTypes\n            fn = if visibility == EIR.Public then apiFunction else function\n        -- Only public functions are exposed, rest is only used internally\n        fn (Name name) args returnType $ \\args' -> do\n          runCodegenM (fnBodyToLLVM args' body) lowerState\n      _ ->\n        panic \"Unexpected top level EIR declaration when compiling to LLVM!\"\n\nfnBodyToLLVM :: MonadFix m => [Operand] -> EIR -> CodegenT m ()\nfnBodyToLLVM args = lowerM instrToOperand instrToUnit\n  where\n    instrToOperand :: Monad m => EIRF (EIR, CodegenT m Operand) -> CodegenT m Operand\n    instrToOperand = \\case\n      EIR.FunctionArgF pos ->\n        pure $ args !! pos\n      EIR.FieldAccessF (snd -> structOrVar) pos -> do\n        -- NOTE: structOrVar is always a pointer to a heap-/stack-allocated\n        -- value so we need to first deref the pointer, and then index into the\n        -- fields of the value ('addr' does this for us). On top of that, we\n        -- can only compute the address here and not do a load as well, since\n        -- sometimes this pointer is used in a \"store\" instruction.\n        addr (mkPath [int32 $ toInteger pos]) =<< structOrVar\n      EIR.VarF v ->\n        lookupVar v\n      EIR.NotF (snd -> bool') ->\n        not' =<< bool'\n      EIR.AndF (snd -> bool1) (snd -> bool2) -> do\n        b1 <- bool1\n        b2 <- bool2\n        and b1 b2\n      EIR.PrimOpF op args' ->\n        invokePrimOp op args'\n      EIR.HeapAllocateProgramF -> do\n        (malloc, (programTy, programSize)) <- gets (extMalloc . externals &&& programType &&& programSizeBytes)\n        let memorySize = int32 $ fromIntegral programSize\n        pointer <- call malloc [memorySize]\n        pure $ ptrcast programTy pointer\n      EIR.StackAllocateF r idx ty -> do\n        theType <- toLLVMType r idx ty\n        alloca theType (Just (int32 1)) 0\n      EIR.LitF (LNumber value) ->\n        pure $ int32 (fromIntegral value)\n      EIR.LitF (LString value) -> do\n        -- We create a global variable to statically store the string,\n        -- but we malloc and copy the string over since the symbol table\n        -- frees all symbols at the end.\n        varName <- newGlobalVarName \"string_literal\"\n        globalStringPtr <- globalUtf8StringPtr value varName\n        let utf8Length = int32 $ toInteger $ BS.length $ encodeUtf8 value\n        numBytes <- utf8Length `zext` i64\n        exts <- gets externals\n        stringPtr <- call (extMalloc exts) [utf8Length]\n        _ <- call (extMemcpy exts) [stringPtr, globalStringPtr, numBytes, bit 0]\n        symFns <- gets symbolFns\n        let tySymbol = Symbol.tySymbol symFns\n        symbolPtr <- alloca tySymbol (Just (int32 1)) 0\n        _ <- call (Symbol.symbolInit symFns) [symbolPtr, utf8Length, stringPtr]\n        pure symbolPtr\n      _ ->\n        panic \"Unhandled pattern match case in 'instrToOperand' while lowering EIR to LLVM!\"\n\n    instrToUnit :: MonadFix m => (EIRF (Triple EIR (CodegenT m Operand) (CodegenT m ())) -> CodegenT m ())\n    instrToUnit = \\case\n      EIR.BlockF stmts -> do\n        traverse_ toInstrs stmts\n      EIR.ParF stmts ->\n        -- NOTE: this is just sequential evaluation for now\n        traverse_ toInstrs stmts\n      EIR.AssignF (toOperandWithContext -> (operand, eirLHS))\n                  (toOperandWithContext -> (val, eirRHS)) -> do\n        case eirLHS of\n          EIR.Var varName -> do\n            -- Assigning to a variable: evaluate the value, and add to the varMap.\n            -- This allows for future lookups of a variable.\n            value <- val\n            addVarBinding varName value\n          _ -> do\n            -- NOTE: here we assume we are assigning to an operand (of a struct field)\n            -- \"operand\" will contain a pointer, \"val\" will contain the actual value\n            -- We need to store the result to the address the pointer is pointing to.\n            address <- operand\n            value <- loadIfNeeded val eirRHS\n            store address 0 value\n      EIR.FreeProgramF (toOperand -> programVar) -> do\n        freeFn <- gets (extFree . externals)\n        program <- programVar\n        let memory = ptrcast i8 program\n        Prelude.void $ call freeFn [memory]\n      EIR.PrimOpF op (map (Relude.swap . toOperandWithContext) -> args') ->\n        Prelude.void $ invokePrimOp op args'\n      EIR.LoopF stmts ->\n        loop $ traverse_ toInstrs stmts\n      EIR.IfF (toOperand -> cond) (toInstrs -> body) -> do\n        condition <- cond\n        if' condition body\n      EIR.JumpF lbl ->\n        br (labelToName lbl)\n      EIR.LabelF lbl ->\n        -- NOTE: the label should be globally unique thanks to the RA -> EIR lowering pass\n        emitBlockStart $ labelToName lbl\n      EIR.ReturnF (toOperand -> value) ->\n        ret =<< value\n      _ ->\n        panic \"Unhandled pattern match case in 'instrToUnit' while lowering EIR to LLVM!\"\n    toOperand (Triple _ operand _) = operand\n    toOperandWithContext (Triple eir operand _) =\n      (operand, eir)\n    toInstrs (Triple _ _ instrs) = instrs\n    invokePrimOp :: Monad m => EIR.Op -> [(EIR, CodegenT m Operand)] -> CodegenT m Operand\n    invokePrimOp op args' = do\n      lookupPrimOp op >>= \\case\n        Left fn ->\n          call fn =<< traverse snd args'\n        Right compareInstr -> case args' of\n          [(a, lhs), (b, rhs)] -> do\n            valueA <- loadIfNeeded lhs a\n            valueB <- loadIfNeeded rhs b\n            compareInstr valueA valueB\n          _ ->\n            panic \"Unexpected amount of arguments in 'invokePrimOp'!\"\n\n-- lowerM is a recursion-scheme that behaves like a zygomorphism, but it is\n-- enhanced in the sense that both functions passed to the zygomorphism have\n-- access to the original subtree.\n--\n-- NOTE: zygo effect is kind of abused here, since due to lazyness we can choose what\n-- we need to compile to LLVM: instructions either return \"()\" or an \"Operand\".\n-- para effect is needed since we need access to the original subtree in the\n-- assignment case to check if we are assigning to a variable or not, allowing\n-- us to easily transform an \"expression-oriented\" EIR to statement based LLVM IR.\nlowerM :: (EIRF (EIR, CodegenT m Operand) -> CodegenT m Operand)\n       -> (EIRF (Triple EIR (CodegenT m Operand) (CodegenT m ())) -> CodegenT m ())\n       -> EIR\n       -> CodegenT m ()\nlowerM f = gcata (distribute f)\n  where\n    distribute\n      :: Corecursive t\n      => (Base t (t, b) -> b)\n      -> (Base t (Triple t b a) -> Triple t b (Base t a))\n    distribute g m =\n      let base_t_t = map tFst m\n          base_t_tb = map (tFst &&& tSnd) m\n          base_t_a = map tThd m\n       in Triple (embed base_t_t) (g base_t_tb) base_t_a\n\n-- We need an Int somewhere later on during codegen.\n-- So we don't convert to a 'Suffix' at this point yet.\ntype IntSuffix = Int\ntype CacheT = StateT (Map Metadata (IntSuffix, Table))\n\nrunCacheT :: Monad m => CacheT m a -> m (Map Metadata IntSuffix, a)\nrunCacheT m = do\n  (a, s) <- runStateT m mempty\n  pure (map fst s, a)\n\ncodegenRuntime :: Externals -> Metadata -> CacheT (ConfigT (ModuleBuilderT IO)) Table\ncodegenRuntime exts meta = gets (M.lookup meta) >>= \\case\n  Nothing -> do\n    suffix <- gets length\n    fns <- cgRuntime suffix\n    modify $ M.insert meta (suffix, fns)\n    pure fns\n  Just (_, cachedFns) -> pure cachedFns\n  where\n    cgRuntime suffix = lift $ case meta of\n      BTree meta' -> hoist (instantiate (show suffix) meta') $ BTree.codegen exts\n\ncodegenDebugInfos :: MonadModuleBuilder m => Map Metadata Int -> m ()\ncodegenDebugInfos metaMapping =\n  traverse_ (uncurry codegenDebugInfo) $ M.toList metaMapping\n  where\n    codegenDebugInfo meta i =\n      let hash = getHash meta\n          name = Name $ (\"specialize_debug_info.\" <>) $ unHash hash\n       in global name i32 (Int 32 $ toInteger i)\n\ncodegenSymbolTable :: Externals -> ConfigT (ModuleBuilderT IO) (SymbolTable.SymbolTable, Symbol.Symbol)\ncodegenSymbolTable exts = do\n  symbol <- lift $ hoist intoIO $ Symbol.codegen exts\n\n  let tySymbol = Symbol.tySymbol symbol\n      symbolDestructor iterPtr = do\n        _ <- call (Symbol.symbolDestroy symbol) [iterPtr]\n        pass\n\n  -- Only this vector does the cleanup of all the symbols, to prevent double frees\n  vec <- hoist (instantiate \"symbol\" tySymbol) $ Vector.codegen exts (Just symbolDestructor)\n  hashMap <- HashMap.codegen symbol exts\n  symbolTable <- lift $ hoist intoIO $ SymbolTable.codegen tySymbol vec hashMap\n  pure (symbolTable, symbol)\n  where\n    intoIO = pure . runIdentity\n\ngetIndexFromMeta :: Metadata -> Index\ngetIndexFromMeta = \\case\n  BTree meta -> Index $ BTree.index meta\n"
  },
  {
    "path": "lib/Eclair/Error.hs",
    "content": "{-# LANGUAGE MultiParamTypeClasses #-}\n{-# OPTIONS_GHC -fno-warn-orphans #-}\n\nmodule Eclair.Error\n  ( EclairError(..)\n  , Issue(..)\n  , Location(..)\n  , Pos(..)\n  , posToSourcePos\n  , locationToSourceSpan\n  , handleErrorsCLI\n  , errorToIssues\n  , renderIssueMessage\n  ) where\n\nimport qualified Data.Map as M\nimport qualified Text.Megaparsec as P\nimport Data.List (partition)\nimport Eclair.AST.Analysis\nimport Eclair.TypeSystem\nimport Eclair.Parser\nimport Eclair.Common.Id\nimport Eclair.Common.Location\nimport Eclair.Souffle.IR\nimport Prettyprinter\nimport Prettyprinter.Render.Terminal\nimport Error.Diagnose hiding (stderr)\n\ndata EclairError\n  = ParseErr FilePath ParsingError\n  | TypeErr FilePath SpanMap [TypeError NodeId]\n  | SemanticErr FilePath SpanMap (SemanticErrors NodeId)\n  | ConversionErr FilePath SpanMap (ConversionError NodeId)\n\n-- TODO refactor using an error reporting monad?\n\n-- Handle errors when running in the CLI.\nhandleErrorsCLI :: EclairError -> IO ()\nhandleErrorsCLI e = do\n  useColorEnvVar <- fromMaybe \"1\" <$> lookupEnv \"ECLAIR_USE_COLOR\"\n  let useColors = if useColorEnvVar /= \"0\"\n                    then Just UseColor\n                    else Nothing\n  hPutDoc stderr =<< errToDoc useColors e\n  where\n    errToDoc useColor = \\case\n      ParseErr file' err' -> do\n        case err' of\n          FileNotFound {} ->\n            pure $ \"File not found: \" <> pretty file' <> \".\\n\"\n          ParsingError parseError -> do\n            content <- decodeUtf8 <$> readFileBS file'\n            let reports = map fst $ errReportsWithLocationsFromBundle \"Failed to parse file\" parseError\n                diagnostic = foldl' addReport def reports\n                diagnostic' = addFile diagnostic file' content\n             in pure $ prettyError useColor diagnostic'\n\n      TypeErr file' spanMap errs -> do\n        content <- decodeUtf8 <$> readFileBS file'\n        let errsWithPositions = getSourcePosCLI file' content spanMap <<$>> errs\n            reports = map typeErrorToReport errsWithPositions\n            diagnostic = foldl' addReport def reports\n            diagnostic' = addFile diagnostic file' (toString content)\n         in pure $ prettyError useColor diagnostic'\n\n      SemanticErr file' spanMap semanticErr -> do\n        content <- decodeUtf8 <$> readFileBS file'\n        let semanticErrsWithPositions = map (getSourcePosCLI file' content spanMap) semanticErr\n            reports = map fst $ semanticErrorsToReportsWithLocations semanticErrsWithPositions\n            diagnostic = foldl' addReport def reports\n            diagnostic' = addFile diagnostic file' (toString content)\n         in pure $ prettyError useColor diagnostic'\n\n      ConversionErr file' spanMap conversionErr -> do\n        content <- decodeUtf8 <$> readFileBS file'\n        let errWithPosition = map (getSourcePosCLI file' content spanMap) conversionErr\n            report = conversionErrorToReport errWithPosition\n            diagnostic = addReport def report\n            diagnostic' = addFile diagnostic file' (toString content)\n        pure $ prettyError useColor diagnostic'\n\n-- A single position in the code. 0-based!\ndata Pos\n  = Pos\n  { posLine :: {-# UNPACK #-} !Word32\n  , posColumn :: {-# UNPACK #-} !Word32\n  }\n\nposToSourcePos :: Pos -> SourcePos\nposToSourcePos (Pos l c) =\n  SourcePos (fromIntegral l) (fromIntegral c)\n\n-- Actual location in the code (a range).\n-- Contains the file, start and end of the position.\ndata Location\n  = Location\n  { locationFile :: FilePath\n  , locationStart :: {-# UNPACK #-} !Pos\n  , locationEnd :: {-# UNPACK #-} !Pos\n  }\n\nlocationToSourceSpan :: Location -> SourceSpan\nlocationToSourceSpan loc =\n  SourceSpan (locationFile loc) posBegin posEnd\n  where\n    posBegin = posToSourcePos $ locationStart loc\n    posEnd = posToSourcePos $ locationEnd loc\n\n-- A helper type for referring to an issue at a location.\ndata Issue\n  = Issue\n  { issueMessage :: Report Text\n  , issueLocation :: Location\n  }\n\nrenderIssueMessage :: Issue -> Text\nrenderIssueMessage issue =\n  -- TODO generate a diagnostic per line of the report (like in Rust).\n  let report = issueMessage issue\n  in getReportTitle report\n\ngetReportTitle :: Report Text -> Text\ngetReportTitle = \\case\n  Err _ summary _ _ -> summary\n  Warn _ summary _ _ -> summary\n\n-- A helper function that can be used from the LSP. Splits all errors into\n-- separate issues for most flexibility and fine-grained reporting.\nerrorToIssues :: (FilePath -> IO Text) -> EclairError -> IO [Issue]\nerrorToIssues readTextFile = \\case\n  ParseErr file' err' -> do\n    case err' of\n      FileNotFound {} -> do\n        let errMessage = \"File not found: \" <> toText file'\n            report = Err Nothing errMessage [] []\n            noLocationInfo = Location file' (Pos 0 0) (Pos 0 0)\n        pure $ one $ Issue report noLocationInfo\n\n      ParsingError parseError -> do\n        let reportsWithLocs = errReportsWithLocationsFromBundle \"Failed to parse file\" parseError\n        pure $ map (uncurry Issue) reportsWithLocs\n\n  TypeErr file' spanMap errs -> do\n    content <- readTextFile file'\n    let errsWithPositions = getSourcePos file' content spanMap <<$>> errs\n        toLocation = positionToLocation . mainErrorPosition\n    pure $ map (uncurry Issue . (typeErrorToReport &&& toLocation)) errsWithPositions\n\n  SemanticErr file' spanMap semanticErrs -> do\n    content <- readTextFile file'\n    let semanticErrsWithPositions = map (getSourcePos file' content spanMap) semanticErrs\n        reportsWithLocs = semanticErrorsToReportsWithLocations semanticErrsWithPositions\n    pure $ map (uncurry Issue) reportsWithLocs\n\n  ConversionErr file' spanMap conversionErr -> do\n    content <- readTextFile file'\n    let errWithPosition = map (getSourcePos file' content spanMap) conversionErr\n        report = conversionErrorToReport errWithPosition\n        loc = positionToLocation $ mainErrorPosition errWithPosition\n    pure $ one $ Issue report loc\n\ntypeErrorToReport :: TypeError Position -> Report Text\ntypeErrorToReport e = case e of\n  UnknownConstraint _ factName ->\n    let title = \"Missing type definition\"\n        markers = [(mainErrorPosition e, This $ \"Could not find a type definition for '\" <> unId factName <> \"'.\")]\n        hints =\n          [ Hint $ \"Add a type definition for '\" <> unId factName <> \"'.\"\n          , Hint $ \"Add an extern definition for '\" <> unId factName <> \"'.\"\n          ]\n    in Err Nothing title markers hints\n\n  UnknownFunction _ factName ->\n    let title = \"Missing type definition\"\n        markers = [(mainErrorPosition e, This $ \"Could not find a type definition for '\" <> unId factName <> \"'.\")]\n        hints =\n          [ Hint $ \"Add an extern definition for '\" <> unId factName <> \"'.\" ]\n    in Err Nothing title markers hints\n\n  ArgCountMismatch factName (expectedSrcLoc, expectedCount) (actualSrcLoc, actualCount) ->\n    let title = \"Found an unexpected amount of arguments for '\" <> unId factName <> \"'\"\n        markers = [ (actualSrcLoc, This $ show actualCount <> pluralize actualCount \" argument is\" \" arguments are\" <> \" provided here.\")\n                  , (expectedSrcLoc, Where $ \"'\" <> unId factName <> \"' is defined with \" <> show expectedCount <> \" \" <>\n                    pluralize expectedCount \"argument\" \"arguments\" <> \".\")\n                  ]\n        hints = [Hint $ \"You can solve this by passing exactly \" <> show expectedCount <> \" \"\n          <> pluralize expectedCount \"argument\" \"arguments\" <> \" to '\" <> unId factName <> \"'.\"]\n    in Err Nothing title markers hints\n\n  TypeMismatch _ actualTy expectedTy ctx ->\n    Err Nothing title markers hints\n      where\n        title = \"Type mismatch\"\n        lastMarker = (mainErrorPosition e, This $ show (length ctx + 1) <> \") Expected this to be of type \" <> renderType expectedTy <> \",\"\n                    <> \" but it actually has type \" <> renderType actualTy <> \".\")\n        markers = zipWith renderDeduction markerTypes (toList ctx) ++ [lastMarker]\n        markerTypes = map (, Where) [1..]\n        hints = []  -- Can we even give a meaningful error here? Look in type env? (if a var is used)\n\n  UnificationFailure _ _ ctx ->\n    Err Nothing title markers hints\n      where\n        title = \"Type unification failure\"\n        markerTypes = markersForTypeError ctx\n        markers = zipWith renderDeduction markerTypes (toList ctx)\n        hints = []  -- What can we even give as a hint here? That it is a logical error?\n\n  HoleFound _ ctx holeTy typeEnv ->\n    Err Nothing title markers hints\n      where\n        title = \"Found hole\"\n        markerTypes = map (, Where) [1..]\n        deductions = zipWith renderDeduction markerTypes (toList ctx)\n        markers = deductions <>\n          [(mainErrorPosition e, This $ show (length deductions + 1) <> \") Found hole with type \" <> renderType holeTy <> \".\")]\n        typeEntries =\n          typeEnv\n            & M.mapWithKey (\\var ty -> (ty, renderBinding var ty))\n            & toList\n        (candidates, others) = partition (\\(entryTy, _) -> entryTy == holeTy) typeEntries\n        hints =\n          map (Hint . ((\"Possible candidate: \" <>) . snd)) candidates <>\n          if null others\n            then []\n            else [Hint \"Other variables include:\"] <> map (Hint . snd) others\n        renderBinding var ty =\n          unId var <> \" :: \" <> renderType ty\n\n  UnexpectedFunctionType _ defPos ->\n    let title = \"Invalid use of function\"\n        markers =\n          [ (mainErrorPosition e, This \"Expected a constraint here.\")\n          , (defPos, Where \"Previously defined as a function here.\")\n          ]\n        hints =\n          [ Hint \"Maybe you meant to declare this an external constraint instead?\"\n          , Hint \"Remove the invalid function.\"\n          ]\n    in Err Nothing title markers hints\n\n  UnexpectedConstraintType _ defPos ->\n    let title = \"Invalid use of constraint\"\n        markers =\n          [ (mainErrorPosition e, This \"Expected a function.\")\n          , (defPos, Where \"Previously defined as a constraint here.\")\n          ]\n        hints =\n          [ Hint \"Maybe you meant to declare this as a function instead?\"\n          , Hint \"Remove the invalid constraint.\"\n          ]\n    in Err Nothing title markers hints\n  where\n    markersForTypeError ctx =\n      zip [1..] $ replicate (length ctx - 1) Where ++ [This]\n\n    renderDeduction :: (Int, Text -> Marker a) -> Context Position -> (Position, Marker a)\n    renderDeduction (i, mkMarker) = \\case\n      WhileChecking srcLoc ->\n        (srcLoc, mkMarker $ show i <> \") While checking the type of this..\")\n      WhileInferring srcLoc ->\n        (srcLoc, mkMarker $ show i <> \") While inferring the type of this..\")\n      WhileUnifying srcLoc ->\n        (srcLoc, mkMarker $ show i <> \") While unifying these types..\")\n\nconversionErrorToReport :: ConversionError Position -> Report Text\nconversionErrorToReport e = case e of\n  HoleNotSupported _ ->\n    let title = \"Unsupported feature in Souffle\"\n        markers = [(mainErrorPosition e, This \"Souffle has no support for holes.\")]\n        hints = [Hint \"Replace the hole with a variable or literal.\"]\n    in Err Nothing title markers hints\n  UnsupportedCase _ ->\n    let title = \"Unsupported feature in Souffle\"\n        markers = [(mainErrorPosition e, This \"Eclair can't transpile extern definitions yet.\")]\n        hints = [Hint \"Please open a github issue asking for this feature.\"]\n     in Err Nothing title markers hints\n  UnsupportedType _ ty ->\n    let title = \"Unsupported type in Souffle\"\n        markers = [(mainErrorPosition e, This $ \"Souffle has no support for the \" <> renderType ty <> \" type.\")]\n        hints = []\n     in Err Nothing title markers hints\n\nungroundedVarToReport :: UngroundedVar Position -> Report Text\nungroundedVarToReport e@(UngroundedVar srcLocRule _ var) =\n  let title = \"Ungrounded variable\"\n      srcLocVar = mainErrorPosition e\n      markers = [ (srcLocVar, This $ \"The variable '\" <> unId var <> \"' is ungrounded, meaning it is not directly bound as an argument to a relation.\")\n                , (srcLocRule, Where $ \"This contains no clauses that refer to '\" <> unId var <> \"'.\")\n                ]\n      hints = [Hint $ \"Use the variable '\" <> unId var <> \"' as an argument in a relation.\"]\n   in Err Nothing title markers hints\n\nwildcardInFactToReport :: WildcardInFact Position -> Report Text\nwildcardInFactToReport e@(WildcardInFact srcLocFact _ _pos) =\n  let title = \"Wildcard in top level fact\"\n      markers = [ (mainErrorPosition e, This \"Wildcard found.\")\n                , (srcLocFact, Where \"A top level fact only supports constants.\\nVariables or wildcards are not allowed.\")\n                ]\n      hints = [Hint \"Replace the wildcard with a constant.\"]\n   in Err Nothing title markers hints\n\nwildcardInExternToReport :: WildcardInExtern Position -> Report Text\nwildcardInExternToReport e@(WildcardInExtern atomLoc _ _) =\n  Err Nothing title markers hints\n  where\n    title = \"Wildcard in externally defined atom\"\n    markers = [ (mainErrorPosition e, This \"Wildcard found.\")\n              , (atomLoc, Where \"An external atom only supports constants or grounded variables.\")\n              ]\n    hints = [Hint \"Replace the wildcard with a constant or grounded variable.\"]\n\nunconstrainedVarToReport :: UnconstrainedRuleVar Position -> Report Text\nunconstrainedVarToReport e@(UnconstrainedRuleVar ruleLoc _ varName) =\n  Err Nothing title markers hints\n  where\n    title = \"Found unconstrained variable\"\n    markers = [ (mainErrorPosition e, This $ \"The variable '\" <> unId varName <> \"' only occurs once.\")\n              , (ruleLoc, Where $ \"This rule contains no other references to '\" <> unId varName <> \"'.\")\n              ]\n    hints = [ Hint \"Replace the variable with a wildcard ('_').\"\n            , Hint \"Use the variable in another rule clause.\"\n            ]\n\nwildcardInRuleHeadToReport :: WildcardInRuleHead Position -> Report Text\nwildcardInRuleHeadToReport e@(WildcardInRuleHead srcLocRule _ _pos) =\n  let title = \"Wildcard in 'head' of rule\"\n      markers = [ (mainErrorPosition e, This \"Wildcard found.\")\n                , (srcLocRule, Where \"Only constants and variables are allowed in the head of a rule.\\nWildcards are not allowed.\")\n                ]\n      hints = [Hint \"Replace the wildcard with a constant or a variable.\"]\n   in Err Nothing title markers hints\n\nwildcardInConstraintToReport :: WildcardInConstraint Position -> Report Text\nwildcardInConstraintToReport e@(WildcardInConstraint srcLocConstraint _) =\n  let title = \"Found wildcard in constraint\"\n      markers = [ (mainErrorPosition e, This \"Wildcard found.\")\n                , (srcLocConstraint, Where \"Only constants and variables are allowed in a constraint.\")\n                ]\n      hints = [ Hint \"This statement can be removed since it has no effect.\"\n              , Hint \"Replace the wildcard with a variable.\"\n              ]\n   in Err Nothing title markers hints\n\nwildcardInBinOpToReport :: WildcardInBinOp Position -> Report Text\nwildcardInBinOpToReport e@(WildcardInBinOp srcLocBinOp _) =\n  let title = \"Found wildcard in binary operation\"\n      markers = [ (mainErrorPosition e, This \"Wildcard found.\")\n                , (srcLocBinOp, Where \"Only constants and variables are allowed in a binary operation.\")\n                ]\n      hints = [Hint \"Replace the wildcard with a variable or literal.\"]\n   in Err Nothing title markers hints\n\ndeadInternalRelationToReport :: DeadInternalRelation Position -> Report Text\ndeadInternalRelationToReport e@(DeadInternalRelation _ r) =\n  let title = \"Dead internal relation\"\n      markers = [(mainErrorPosition e, This $ \"The internal rule '\" <> unId r <> \"' has no facts or rules defined and will never produce results.\")]\n      hints = [ Hint \"This might indicate a logic error in your code.\"\n              , Hint \"Remove this rule if it is no longer needed.\"\n              , Hint \"Add 'input' to the declaration to indicate this rule is an input.\"\n              ]\n   in Err Nothing title markers hints\n\nnoOutputRelationsToReport :: NoOutputRelation Position -> Report Text\nnoOutputRelationsToReport e@(NoOutputRelation _) =\n  let title = \"No output relations found\"\n      markers = [(mainErrorPosition e, This \"This module does not produce any results\")]\n      hints = [ Hint \"Add an 'output' qualifier to one of the relations defined in this module.\" ]\n  in Err Nothing title markers hints\n\nconflictingDefinitionsToReport :: ConflictingDefinitionGroup Position -> Report Text\nconflictingDefinitionsToReport e@(ConflictingDefinitionGroup name locs) =\n  Err Nothing title (mainMarker:markers) hints\n  where\n    title = \"Multiple definitions for '\" <> unId name <> \"'\"\n    mainMarker =\n      (mainErrorPosition e, This $ \"'\" <> unId name <> \"' is originally defined here.\")\n    markers = tail locs & toList & map (, Where $ \"'\" <> unId name <> \"' is re-defined here.\")\n    hints = [Hint $ \"You can solve this by removing the duplicate definitions for '\" <> unId name <> \"'.\"]\n\nexternUsedAsFactToReport :: ExternUsedAsFact Position -> Report Text\nexternUsedAsFactToReport e@(ExternUsedAsFact _ externLoc name) =\n  Err Nothing title markers hints\n  where\n    title = \"Extern definition used as top level fact\"\n    markers =\n      [ (mainErrorPosition e, This $ \"'\" <> unId name <> \"' is used as a fact here, which is not allowed for extern definitions.\")\n      , (externLoc, Where $ \"'\" <> unId name <> \"' previously defined here as external.\")\n      ]\n    hints = [ Hint $ \"Convert '\" <> unId name <> \"' to a relation.\"\n            , Hint \"Remove the top level fact.\"\n            ]\n\nexternUsedAsRuleToReport :: ExternUsedAsRule Position -> Report Text\nexternUsedAsRuleToReport e@(ExternUsedAsRule _ externLoc name) =\n  Err Nothing title markers hints\n  where\n    title = \"Extern definition used in rule head\"\n    markers =\n      [ (mainErrorPosition e, This $ \"'\" <> unId name <> \"' is used as a rule head here, which is not allowed for extern definitions.\")\n      , (externLoc, Where $ \"'\" <> unId name <> \"' previously defined here as external.\")\n      ]\n    hints = [ Hint $ \"Convert '\" <> unId name <> \"' to a relation.\"\n            , Hint \"Remove the rule.\"\n            ]\n\ncyclicNegationToReport :: CyclicNegation Position -> Report Text\ncyclicNegationToReport e =\n  Err Nothing title markers hints\n  where\n    title = \"Negation used in recursive set of rules\"\n    markers =\n      [ (mainErrorPosition e, This \"This negation is used in a set of rules that is recursive, which is not allowed.\")\n      ]\n    hints = [ Hint \"Restructure the program so the negation does not occur in the set of recursive rules.\"\n            , Hint \"Remove the negation entirely.\"\n            ]\n\n\n-- NOTE: pattern match is done this way to keep track of additional errors that need to be reported\n{-# ANN semanticErrorsToReportsWithLocations (\"HLint: ignore Use record patterns\" :: String) #-}\nsemanticErrorsToReportsWithLocations :: SemanticErrors Position -> [(Report Text, Location)]\nsemanticErrorsToReportsWithLocations e@(SemanticErrors _ _ _ _ _ _ _ _ _ _ _ _ _) =\n  concat [ ungroundedVarReports\n         , wildcardInFactReports\n         , wildcardInRuleHeadReports\n         , wildcardInConstraintReports\n         , wildcardInBinOpReports\n         , wildcardInExternReports\n         , unconstrainedVarReports\n         , deadInternalRelationReports\n         , noOutputReports\n         , conflictingDefinitionReports\n         , externUsedAsFactReports\n         , externUsedAsRuleReports\n         , cyclicNegationReports\n         ]\n  where\n    getReportsWithLocationsFor\n      :: HasMainErrorPosition a\n      => (SemanticErrors Position -> Container a)\n      -> (a -> Report Text)\n      -> [(Report Text, Location)]\n    getReportsWithLocationsFor f g =\n      map (g &&& positionToLocation . mainErrorPosition) (f e)\n    ungroundedVarReports = getReportsWithLocationsFor ungroundedVars ungroundedVarToReport\n    wildcardInFactReports = getReportsWithLocationsFor wildcardsInFacts wildcardInFactToReport\n    wildcardInRuleHeadReports = getReportsWithLocationsFor wildcardsInRuleHeads wildcardInRuleHeadToReport\n    wildcardInConstraintReports = getReportsWithLocationsFor wildcardsInConstraints wildcardInConstraintToReport\n    wildcardInBinOpReports = getReportsWithLocationsFor wildcardsInBinOps wildcardInBinOpToReport\n    wildcardInExternReports = getReportsWithLocationsFor wildcardsInExternAtoms wildcardInExternToReport\n    unconstrainedVarReports = getReportsWithLocationsFor unconstrainedVars unconstrainedVarToReport\n    deadInternalRelationReports = getReportsWithLocationsFor deadInternalRelations deadInternalRelationToReport\n    noOutputReports = getReportsWithLocationsFor noOutputRelations noOutputRelationsToReport\n    conflictingDefinitionReports = getReportsWithLocationsFor conflictingDefinitions conflictingDefinitionsToReport\n    externUsedAsFactReports = getReportsWithLocationsFor externsUsedAsFact externUsedAsFactToReport\n    externUsedAsRuleReports = getReportsWithLocationsFor externsUsedAsRule externUsedAsRuleToReport\n    cyclicNegationReports = getReportsWithLocationsFor cyclicNegations cyclicNegationToReport\n\npluralize :: Int -> Text -> Text -> Text\npluralize count singular plural' =\n  if count == 1 then singular else plural'\n\ngetSourcePos :: FilePath -> Text -> SpanMap -> NodeId -> Position\ngetSourcePos file' fileContent spanMap nodeId =\n  let span' = lookupSpan spanMap nodeId\n   in sourceSpanToPosition $ spanToSourceSpan file' fileContent span'\n\n-- Diagnose is 1-based, Eclair 0-based\n-- TODO get rid of this hack (and diagnose alltogether)\ngetSourcePosCLI :: FilePath -> Text -> SpanMap -> NodeId -> Position\ngetSourcePosCLI file' fileContent spanMap nodeId =\n  addOffset $ getSourcePos file' fileContent spanMap nodeId\n  where\n    addOffset pos =\n      Position (both (+1) $ begin pos) (both (+1) $ end pos) (file pos)\n\n\nsourceSpanToPosition :: SourceSpan -> Position\nsourceSpanToPosition sourceSpan =\n  let beginPos' = sourceSpanBegin sourceSpan\n      endPos' = sourceSpanEnd sourceSpan\n      start = (sourcePosLine beginPos', sourcePosColumn beginPos')\n      end' = (sourcePosLine endPos', sourcePosColumn endPos')\n   in Position start end' (sourceSpanFile sourceSpan)\n\npositionToLocation :: Position -> Location\npositionToLocation position =\n  let locStart = uncurry Pos (both fromIntegral $ begin position)\n      locEnd = uncurry Pos (both fromIntegral $ end position)\n   in Location (file position) locStart locEnd\n\nboth :: (a -> b) -> (a, a) -> (b, b)\nboth f = bimap f f\n\nrenderType :: Type -> Text\nrenderType ty =\n  let userFacingType = case ty of\n        U32 -> \"u32\"\n        Str -> \"string\"\n        TUnknown x -> \"t\" <> show x\n  in \"'\" <> userFacingType <> \"'\"\n\nclass HasMainErrorPosition a where\n  mainErrorPosition :: a -> Position\ninstance HasMainErrorPosition (NoOutputRelation Position) where\n  mainErrorPosition (NoOutputRelation pos) = startOfFile $ file pos\n    where startOfFile = Position (1, 1) (1, 2)  -- Diagnose is 1-based!\ninstance HasMainErrorPosition (DeadInternalRelation Position) where\n  mainErrorPosition (DeadInternalRelation pos _) = pos\ninstance HasMainErrorPosition (WildcardInConstraint Position) where\n  mainErrorPosition (WildcardInConstraint _ pos) = pos\ninstance HasMainErrorPosition (WildcardInBinOp Position) where\n  mainErrorPosition (WildcardInBinOp _ pos) = pos\ninstance HasMainErrorPosition (WildcardInFact Position) where\n  mainErrorPosition (WildcardInFact _ factArgPos _) = factArgPos\ninstance HasMainErrorPosition (WildcardInExtern Position) where\n  mainErrorPosition (WildcardInExtern _ externArgPos _) = externArgPos\ninstance HasMainErrorPosition (UnconstrainedRuleVar Position) where\n  mainErrorPosition (UnconstrainedRuleVar _ varPos _) = varPos\ninstance HasMainErrorPosition (UngroundedVar Position) where\n  mainErrorPosition (UngroundedVar _ varPos _) = varPos\ninstance HasMainErrorPosition (WildcardInRuleHead Position) where\n  mainErrorPosition (WildcardInRuleHead _ ruleArgPos _) = ruleArgPos\ninstance HasMainErrorPosition (ConflictingDefinitionGroup Position) where\n  mainErrorPosition (ConflictingDefinitionGroup _ positions) = head positions\ninstance HasMainErrorPosition (ExternUsedAsFact Position) where\n  mainErrorPosition (ExternUsedAsFact pos _ _) = pos\ninstance HasMainErrorPosition (ExternUsedAsRule Position) where\n  mainErrorPosition (ExternUsedAsRule pos _ _) = pos\ninstance HasMainErrorPosition (CyclicNegation Position) where\n  mainErrorPosition (CyclicNegation pos) = pos\ninstance HasMainErrorPosition (TypeError Position) where\n  mainErrorPosition = \\case\n    UnknownConstraint pos _ -> pos\n    UnknownFunction pos _ -> pos\n    ArgCountMismatch _ _ (pos, _) -> pos\n    TypeMismatch pos _ _ _ -> pos\n    UnificationFailure _ _ ctx -> getContextLocation (last ctx)\n    HoleFound pos _ _ _ -> pos\n    UnexpectedFunctionType pos _ -> pos\n    UnexpectedConstraintType pos _ -> pos\ninstance HasMainErrorPosition (ConversionError Position) where\n  mainErrorPosition = \\case\n    HoleNotSupported pos -> pos\n    UnsupportedType pos _ -> pos\n    UnsupportedCase pos -> pos\n\n-- Helper function to transform a Megaparsec error bundle into multiple reports\n-- Extracted from the Diagnose library, and simplified for usage in Eclair.\nerrReportsWithLocationsFromBundle\n  :: Text -> P.ParseErrorBundle Text CustomParseErr -> [(Report Text, Location)]\nerrReportsWithLocationsFromBundle msg errBundle =\n  toList (addLabelAndLocation <$> P.bundleErrors errBundle)\n  where\n    addLabelAndLocation e =\n      let (_, pos) = P.reachOffset (P.errorOffset e) (P.bundlePosState errBundle)\n          source = fromSourcePos (P.pstateSourcePos pos)\n          msgs = lines $ toText (P.parseErrorTextPretty e)\n          markers = case msgs of\n            [m] ->\n              [(source, This m)]\n            [m1, m2] ->\n              [(source, This m1), (source, Where m2)]\n            _ ->\n              [(source, This \"<<Unknown error>>\")]\n          report = Err Nothing msg markers mempty\n      in (report, positionToLocation source)\n\n    fromSourcePos sourcePos =\n      let begin' = both (fromIntegral . P.unPos) (P.sourceLine sourcePos, P.sourceColumn sourcePos)\n          end' = second (+ 1) begin'\n       in Position begin' end' (P.sourceName sourcePos)\n\n\ndata UseColor = UseColor\n  deriving Eq\n\nprettyError :: Maybe UseColor -> Diagnostic Text -> Doc AnsiStyle\nprettyError useColor =\n  applyStyle . prettyDiagnostic useUnicode tabSpaces\n  where\n    applyStyle =\n      if useColor == Just UseColor\n        then style\n        else unAnnotate\n    useUnicode = True\n    tabSpaces = 2\n\nstyle :: Style\nstyle = reAnnotate style'\n  where\n    style' = \\case\n      ThisColor isError ->\n        colorDull $ if isError then Red else Yellow\n      MaybeColor ->\n        color Magenta\n      WhereColor ->\n        colorDull Blue\n      HintColor ->\n        colorDull Cyan\n      FileColor ->\n        bold <> colorDull Green\n      RuleColor ->\n        bold <> color Black\n      KindColor isError ->\n        bold <> style' (ThisColor isError)\n      NoLineColor ->\n        bold <> colorDull Magenta\n      MarkerStyle st ->\n        bold <> style' st\n      CodeStyle ->\n        color White\n"
  },
  {
    "path": "lib/Eclair/JSON.hs",
    "content": "{-# LANGUAGE LinearTypes, MagicHash #-}\n-- | Helper module encoding Haskell values as JSON.\n--   Only a limited set of functionality is provided.\n--   (Needed since hermes-json only does decoding of JSON.)\nmodule Eclair.JSON\n  ( JSON(..)\n  , encodeJSON\n  ) where\n\nimport Data.Text.Builder.Linear.Buffer\nimport GHC.Prim (Addr#)\n\ndata JSON\n  = Null\n  | Boolean Bool\n  | Number Int\n  | String Text\n  | Object [(Text, JSON)]\n  | Array [JSON]\n\nencodeJSON :: JSON -> Text\nencodeJSON json =\n  runBuffer (`toJSON'` json)\n  where\n    toJSON' :: Buffer %1 -> JSON -> Buffer\n    toJSON' buf = \\case\n      Null ->\n        buf |># \"null\"#\n      Boolean b ->\n        buf |># if b then \"true\"# else \"false\"#\n      Number x ->\n        buf |> show x\n      String s ->\n        dquotes buf (|> s)\n      Object pairs ->\n        braces buf (\\buf' ->\n          sepBy \",\"# buf' pairs (\\buf'' (k, v) ->\n            (dquotes buf'' (|> k) |>. ':') `toJSON'` v\n          )\n        )\n      Array elems ->\n        brackets buf (\\buf' -> sepBy \",\"# buf' elems toJSON')\n\ntype BufferDecorator = Buffer %1 -> (Buffer %1 -> Buffer) -> Buffer\n\nbrackets :: BufferDecorator\nbrackets = betweenChars '[' ']'\n{-# INLINABLE brackets #-}\n\nbraces :: BufferDecorator\nbraces = betweenChars '{' '}'\n{-# INLINABLE braces #-}\n\ndquotes :: BufferDecorator\ndquotes = betweenChars '\"' '\"'\n{-# INLINABLE dquotes #-}\n\nbetweenChars :: Char -> Char -> BufferDecorator\nbetweenChars begin end buf f =\n  f (buf |>. begin) |>. end\n{-# INLINABLE betweenChars #-}\n\nsepBy :: forall a. Addr# -> Buffer %1 -> [a] -> (Buffer %1 -> a -> Buffer) -> Buffer\nsepBy separator buf as f =\n  foldlIntoBuffer combine buf parts\n  where\n    parts = intersperse Nothing $ map Just as\n    combine :: Buffer %1 -> Maybe a -> Buffer\n    combine buf' = \\case\n      Nothing ->\n        buf' |># separator\n      Just a ->\n        f buf' a\n{-# INLINABLE sepBy #-}\n"
  },
  {
    "path": "lib/Eclair/LLVM/Allocator/Arena.hs",
    "content": "{-# LANGUAGE GADTs #-}\n\nmodule Eclair.LLVM.Allocator.Arena\n  ( Arena\n  , allocator\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.Allocator.Common\nimport Eclair.LLVM.Codegen\n\ndata Arena a\n\n-- TODO support multiple implementations, switch based on an enum:\n-- - variant that only allocates, never frees until destroyed\n-- - variant that can free sometimes, when allocation counter hits 0\n-- - bump up vs bump down (each has it's own benefits): https://fitzgeraldnick.com/2019/11/01/always-bump-downwards.html\n\nallocator :: Int -> Allocator a -> Allocator (Arena a)\nallocator arenaSize inner\n  = Allocator\n  { aType = mkType\n  , aInit = arenaInit arenaSize\n  , aDestroy = arenaDestroy arenaSize\n  , aAlloc = arenaAlloc\n  , aFree = arenaFree\n  , aKind = Node\n  , aInner = inner\n  }\n\nmkType :: Text -> Type -> AllocCodegenM Type\nmkType prefix baseTy =\n  typedef (Name $ prefix <> \"arena_allocator\") Off\n    [ baseTy    -- inner allocator\n    , ptr void  -- start\n    , ptr void  -- current\n    ]\n\ninnerPtr :: Operand -> AllocIRCodegenM Operand\ninnerPtr = addr innerAllocOf\n\narenaInit :: Int -> VTable -> InitFn\narenaInit arenaSize fns alloc = do\n  inner <- innerPtr alloc\n  vtInit fns inner\n\n  let size = int32 $ fromIntegral arenaSize\n  memory <- vtAllocate fns inner size\n  didAllocationFail <- memory `eq` nullPtr void\n  if' didAllocationFail $ do\n    assign startPtrOf alloc (nullPtr void)\n    assign currentPtrOf alloc (nullPtr void)\n    retVoid\n\n  memoryEnd <- gep memory [size]\n  assign startPtrOf alloc memory\n  -- NOTE: we start at end and bump down, this can be implemented faster\n  -- https://fitzgeraldnick.com/2019/11/01/always-bump-downwards.html\n  assign currentPtrOf alloc memoryEnd\n\narenaDestroy :: Int -> VTable -> DestroyFn\narenaDestroy arenaSize fns alloc = do\n  startPtr <- deref startPtrOf alloc\n  isNull <- startPtr `eq` nullPtr void\n  if' isNull $ do\n    vtDestroy fns =<< innerPtr alloc\n    retVoid\n\n  inner <- innerPtr alloc\n  let numBytes = int32 $ fromIntegral arenaSize\n  vtDeallocate fns inner startPtr numBytes\n\n  vtDestroy fns =<< innerPtr alloc\n  assign startPtrOf alloc (nullPtr void)\n  assign currentPtrOf alloc (nullPtr void)\n\narenaAlloc :: VTable -> AllocateFn\narenaAlloc _ alloc numBytes = do\n  startPtr <- deref startPtrOf alloc\n  currentPtr <- deref currentPtrOf alloc\n\n  numBytesNegated <- sub (int32 0) numBytes\n  newPtr <- gep currentPtr [numBytesNegated]\n  newAddr <- ptrtoint newPtr i64\n  startAddr <- ptrtoint startPtr i64\n  noSpaceLeft <- newAddr `ult` startAddr\n\n  if' noSpaceLeft $ do\n    pure $ nullPtr void\n\n  assign currentPtrOf alloc newPtr\n  pure newPtr\n\n-- Arena can't free individual pieces of memory, only everything at once\narenaFree :: VTable -> DeallocateFn\narenaFree _ _ _ _ = pass\n\ndata Paths\n  = Alloc\n  | InnerAlloc\n  | StartPtr\n  | CurrentPtr\n\ninnerAllocOf :: Path Alloc InnerAlloc\ninnerAllocOf = mkPath [int32 0]\n\nstartPtrOf :: Path Alloc StartPtr\nstartPtrOf = mkPath [int32 1]\n\ncurrentPtrOf :: Path Alloc CurrentPtr\ncurrentPtrOf = mkPath [int32 2]\n"
  },
  {
    "path": "lib/Eclair/LLVM/Allocator/Common.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n{-# LANGUAGE GADTs #-}\n\nmodule Eclair.LLVM.Allocator.Common\n  ( Allocator(..)\n  , Blueprint(..)\n  , AllocatorKind(..)\n  , AllocatorKindTag(..)\n  , VTable(..)\n  , None(..)\n  , AllocCodegenM\n  , AllocIRCodegenM\n  , InitFn\n  , DestroyFn\n  , AllocateFn\n  , DeallocateFn\n  , cgAlloc\n  , mkBaseAllocator\n  , module Eclair.LLVM.Externals\n  ) where\n\nimport Prelude hiding (void)\nimport qualified Data.Kind as K\nimport Eclair.LLVM.Codegen\nimport Eclair.LLVM.Externals\n\ntype AllocCodegenM = StateT Externals ModuleBuilder\ntype AllocIRCodegenM = IRBuilderT AllocCodegenM\n\ntype InitFn\n  = Operand -- allocator\n  -> AllocIRCodegenM ()\ntype DestroyFn\n  = Operand -- allocator\n  -> AllocIRCodegenM ()\ntype AllocateFn\n  =  Operand  -- allocator\n  -> Operand  -- length (in bytes)\n  -> AllocIRCodegenM Operand\ntype DeallocateFn\n  = Operand   -- allocator\n  -> Operand  -- ptr\n  -> Operand  -- length\n  -> AllocIRCodegenM ()\n-- TODO add resize everywhere, just like alloc and free\n-- type ResizeFn = Operand  -- ptr\n--              -> Operand  -- length\n--              -> AllocIRCodegenM ()\n\n-- Helper type that for performing actions on an allocator.\n-- This allows a composed allocator to call inner functionality freely.\ndata VTable\n  = VTable\n  { vtInit       :: InitFn\n  , vtDestroy    :: DestroyFn\n  , vtAllocate   :: AllocateFn\n  , vtDeallocate :: DeallocateFn\n  }\n\ndata AllocatorKindTag\n  = IsRoot\n  | IsNode\n\ndata AllocatorKind (k :: AllocatorKindTag) where\n  Root :: AllocatorKind 'IsRoot\n  Node :: AllocatorKind 'IsNode\n\ntype family CreateTypeFn (k :: AllocatorKindTag) where\n  CreateTypeFn 'IsRoot = Text -> AllocCodegenM Type\n  CreateTypeFn 'IsNode = Text -> Type -> AllocCodegenM Type\n\ntype family AllocatorFn (k :: AllocatorKindTag) (ty :: K.Type) where\n  AllocatorFn 'IsRoot f = f\n  AllocatorFn 'IsNode f = VTable -> f\n\ndata None a = None\n\ntype family BackingAllocator (k :: AllocatorKindTag) :: (K.Type -> K.Type) where\n  BackingAllocator 'IsRoot = None\n  BackingAllocator 'IsNode = Allocator\n\n-- First we build up the allocator info in a data-structure.\n-- \"Stateless\" allocators carry no state, and call direct functions provided by the OS (mmap, malloc, ...)\n-- \"Stateful\" allocators do have state, and can further enhance the behavior of the underlying allocator.\ndata Allocator repr where\n  Allocator\n    :: { aType    :: CreateTypeFn k\n       , aInit    :: AllocatorFn k InitFn\n       , aDestroy :: AllocatorFn k DestroyFn\n       , aAlloc   :: AllocatorFn k AllocateFn\n       , aFree    :: AllocatorFn k DeallocateFn\n       , aInner   :: BackingAllocator k inner\n       , aKind    :: AllocatorKind k\n       }\n    -> Allocator repr\n\n-- Helper type for keeping references to generated allocator code\ndata Blueprint repr\n  = Blueprint\n  { bpType :: Type\n  , bpInitFn :: Operand\n  , bpDestroyFn :: Operand\n  , bpAllocateFn :: Operand\n  , bpDeallocateFn :: Operand\n  }\n\n-- Helper type during codegen process.\ndata Gen\n  = Gen\n  { generateTy      :: Text -> AllocCodegenM Type\n  , generateInit    :: InitFn\n  , generateDestroy :: DestroyFn\n  , generateAlloc   :: AllocateFn\n  , generateFree    :: DeallocateFn\n  }\n\n-- This function does the actual code generation of the allocator.\ncgAlloc :: Text -> Allocator repr -> AllocCodegenM (Blueprint repr)\ncgAlloc prefix allocator = do\n  let g = cgHelper allocator\n  allocatorTy <- generateTy g prefix\n  allocFn <- function (Name $ prefix <> \"_alloc\")\n                [(ptr allocatorTy, \"allocator\"), (i32, \"size\")] (ptr void)\n                $ \\[alloc, size] -> do\n    ret =<< generateAlloc g alloc size\n  freeFn <- function (Name $ prefix <> \"_free\")\n                [(ptr allocatorTy, \"allocator\"), (ptr i8, \"memory\"), (i32, \"size\")] void\n                $ \\[alloc, memory, size] -> do\n    generateFree g alloc memory size\n  initFn <- function (Name $ prefix <> \"_init\")\n                [(ptr allocatorTy, \"allocator\")] void\n                $ \\[alloc] -> do\n    generateInit g alloc\n  destroyFn <- function (Name $ prefix <> \"_destroy\")\n                [(ptr allocatorTy, \"allocator\")] void\n                $ \\[alloc] -> do\n    generateDestroy g alloc\n\n  pure $ Blueprint allocatorTy initFn destroyFn allocFn freeFn\n  where\n    -- Recursively generates the code\n    cgHelper :: Allocator repr -> Gen\n    cgHelper = \\case\n      Allocator genTy genInit genDestroy genAlloc genFree innerAlloc kind ->\n        case kind of\n          Root ->\n            Gen { generateTy = genTy\n                , generateInit = genInit\n                , generateDestroy = genDestroy\n                , generateAlloc = genAlloc\n                , generateFree = genFree\n                }\n\n          Node ->\n            let generated = cgHelper innerAlloc\n                vtable = VTable\n                  { vtInit = generateInit generated\n                  , vtDestroy = generateDestroy generated\n                  , vtAllocate = generateAlloc generated\n                  , vtDeallocate = generateFree generated\n                  }\n            in Gen\n                { generateTy = \\namePrefix -> do\n                    -- First generate inner type, then outer type.\n                    ty <- generateTy generated namePrefix\n                    genTy namePrefix ty\n                  -- Pass generated inner code as function, then generate outer code based on that.\n                , generateInit = genInit vtable\n                , generateDestroy = genDestroy vtable\n                , generateAlloc   = genAlloc vtable\n                , generateFree    = genFree vtable\n                }\n\nmkBaseAllocator\n  :: (Text -> AllocCodegenM Type)\n  -> (Operand -> AllocIRCodegenM Operand)\n  -> (Operand -> Operand -> AllocIRCodegenM ())\n  -> Allocator repr\nmkBaseAllocator mkType allocFn freeFn\n  = Allocator\n  { aType = mkType\n  , aInit = const pass\n  , aDestroy = const pass\n  , aAlloc = const allocFn\n  , aFree = const freeFn\n  , aKind = Root\n  , aInner = None\n  }\n"
  },
  {
    "path": "lib/Eclair/LLVM/Allocator/Malloc.hs",
    "content": "module Eclair.LLVM.Allocator.Malloc\n  ( Malloc\n  , allocator\n  ) where\n\nimport Eclair.LLVM.Allocator.Common\nimport Eclair.LLVM.Codegen\n\ndata Malloc\n\nallocator :: Allocator Malloc\nallocator = mkBaseAllocator mkType allocFn freeFn\n\nmkType :: Text -> AllocCodegenM Type\nmkType prefix =\n  typedef (Name $ prefix <> \"_mallocator\") Off []\n\nallocFn :: Operand -> AllocIRCodegenM Operand\nallocFn numBytes = do\n  malloc <- gets extMalloc\n  call malloc [numBytes]\n\nfreeFn :: Operand -> Operand -> AllocIRCodegenM ()\nfreeFn memory _ = do\n  free <- gets extFree\n  _ <- call free [memory]\n  pass\n"
  },
  {
    "path": "lib/Eclair/LLVM/Allocator/Page.hs",
    "content": "module Eclair.LLVM.Allocator.Page\n  ( Page\n  , allocator\n  , roundToNearestPageSize  -- for testing only\n  ) where\n\nimport Eclair.LLVM.Allocator.Common\nimport Eclair.LLVM.Codegen\n\ndata Page\n\n-- TODO: parametrize on page size (add argument, pass to helper functions)\nallocator :: Allocator Page\nallocator = mkBaseAllocator mkType allocatePages freePages\n\nmkType :: Text -> AllocCodegenM Type\nmkType prefix =\n  typedef (Name $ prefix <> \"page_allocator\") Off []\n\npageSize :: Operand\npageSize = int32 4096\n\nallocatePages :: Operand -> AllocIRCodegenM Operand\nallocatePages numBytes = do\n  mmap <- gets extMmap\n  numBytes' <- flip zext i64 =<< roundToNearestPageSize numBytes\n  let hint = nullPtr VoidType\n      protRead = int32 1\n      protWrite = int32 2\n      mapPrivate = int32 2\n      mapAnonymous = int32 32\n      noFd = int32 (-1)\n      offset = int32 0\n  prot <- protRead `or` protWrite  -- allow both reads and writes\n  flags <- mapPrivate `or` mapAnonymous  -- anonymous private mapping, can be used as RAM\n  call mmap [hint, numBytes', prot, flags, noFd, offset]\n\nroundToNearestPageSize :: Operand -> AllocIRCodegenM Operand\nroundToNearestPageSize numBytes = do\n  x1 <- numBytes `add` pageSize\n  x2 <- x1 `sub` int32 1\n  y <- int32 0 `sub` pageSize\n  x2 `and` y\n\nfreePages :: Operand -> Operand -> AllocIRCodegenM ()\nfreePages memory len = do\n  munmap <- gets extMunmap\n  len' <- zext len i64\n  _ <- call munmap [memory, len']\n  pass\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Bounds.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Bounds\n  ( mkLinearSearchLowerBound\n  , mkLinearSearchUpperBound\n  , mkBtreeLowerBound\n  , mkBtreeUpperBound\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.BTree.Types\n\nmkLinearSearchLowerBound :: Operand -> ModuleCodegen Operand\nmkLinearSearchLowerBound compareValues = do\n  value <- typeOf Value\n  let args = [(ptr value, \"val\"), (ptr value, \"current\"), (ptr value, \"end\")]\n\n  function \"eclair_btree_linear_search_lower_bound\" args (ptr value) $ \\[val, curr, end] -> mdo\n    -- Finds an iterator to first element not less than given value.\n    currentPtr <- allocate (ptr value) curr\n    let loopCondition = do\n          current <- load currentPtr 0\n          current `ne` end\n    loopWhile loopCondition $ mdo\n      current <- load currentPtr 0\n      result <- call compareValues [current, val]\n      isGtOrEqThan <- result `ne` int8 (-1)\n      if' isGtOrEqThan $\n        ret current\n\n      current' <- gep current [int32 1]\n      store currentPtr 0 current'\n\n    ret end\n\nmkLinearSearchUpperBound :: Operand -> ModuleCodegen Operand\nmkLinearSearchUpperBound compareValues = do\n  value <- typeOf Value\n  let args = [(ptr value, \"val\"), (ptr value, \"current\"), (ptr value, \"end\")]\n\n  function \"eclair_btree_linear_search_upper_bound\" args (ptr value) $ \\[val, curr, end] -> mdo\n    -- Finds an iterator to first element that is greater than given value.\n    currentPtr <- allocate (ptr value) curr\n    let loopCondition = do\n          current <- load currentPtr 0\n          current `ne` end\n    loopWhile loopCondition $ mdo\n      current <- load currentPtr 0\n      result <- call compareValues [current, val]\n      isGreaterThan <- result `eq` int8 1\n      if' isGreaterThan $\n        ret current\n\n      current' <- gep current [int32 1]\n      store currentPtr 0 current'\n\n    ret end\n\nmkBtreeLowerBound :: Operand -> Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand\nmkBtreeLowerBound isEmptyTree iterInit iterInitEnd searchLowerBound compareValues = do\n  tree <- typeOf BTree\n  iter <- typeOf Iterator\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n  value <- typeOf Value\n  valSize <- asks (valueSize . typeSizes)\n  let args = [(ptr tree, \"tree\"), (ptr value, \"val\"), (ptr iter, \"result\")]\n\n  function \"eclair_btree_lower_bound\" args void $ \\[t, val, result] -> mdo\n    isEmpty <- call isEmptyTree [t]\n    if' isEmpty $ do\n      _ <- call iterInitEnd [result]\n      retVoid\n\n    res <- allocateIter\n    _ <- call iterInitEnd [res]\n    currentPtr <- allocate (ptr node) =<< deref rootPtrOf t\n\n    loop $ mdo\n      current <- load currentPtr 0\n      numElems <- deref (metaOf ->> numElemsOf) current\n      first <- addr (valueAt (int16 0)) current\n      last <- addr (valueAt numElems) current\n      pos <- call searchLowerBound [val, first, last]\n      idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize))\n      isLeaf <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` leafNodeTypeVal)\n      if' isLeaf $ mdo\n        isLast <- pos `eq` last\n        condBr isLast handleLast handleOther\n\n        handleLast <- blockNamed \"handle_last\"\n        copy currentPtrOf res result\n        copy valuePosOf res result\n        retVoid\n\n        handleOther <- blockNamed \"handle_not_last\"\n        _ <- call iterInit [result, current, idx]\n        retVoid\n\n      isNotLast <- pos `ne` last\n      if' isNotLast $ do\n        matchFound' <- (int8 0 `eq`) =<< call compareValues [pos, val]\n        if' matchFound' $ do\n          _ <- call iterInit [result, current, idx]\n          retVoid\n\n      if' isNotLast $ do\n        call iterInit [res, current, idx]\n\n      let iCurrent = ptrcast innerNode current\n      store currentPtr 0 =<< deref (childAt idx) iCurrent\n\nmkBtreeUpperBound :: Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand\nmkBtreeUpperBound isEmptyTree iterInit iterInitEnd searchUpperBound = do\n  tree <- typeOf BTree\n  iter <- typeOf Iterator\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n  value <- typeOf Value\n  valSize <- asks (valueSize . typeSizes)\n  let args = [(ptr tree, \"tree\"), (ptr value, \"val\"), (ptr iter, \"result\")]\n\n  function \"eclair_btree_upper_bound\" args void $ \\[t, val, result] -> mdo\n    isEmpty <- call isEmptyTree [t]\n    if' isEmpty $ do\n      _ <- call iterInitEnd [result]\n      retVoid\n\n    res <- allocateIter\n    _ <- call iterInitEnd [res]\n    currentPtr <- allocate (ptr node) =<< deref rootPtrOf t\n\n    loop $ mdo\n      current <- load currentPtr 0\n      numElems <- deref (metaOf ->> numElemsOf) current\n      first <- addr (valueAt (int16 0)) current\n      last <- addr (valueAt numElems) current\n      pos <- call searchUpperBound [val, first, last]\n      idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize))\n      isLeaf <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` leafNodeTypeVal)\n      if' isLeaf $ mdo\n        isLast <- pos `eq` last\n        condBr isLast handleLast handleOther\n\n        handleLast <- blockNamed \"handle_last\"\n        copy currentPtrOf res result\n        copy valuePosOf res result\n        retVoid\n\n        handleOther <- blockNamed \"handle_not_last\"\n        _ <- call iterInit [result, current, idx]\n        retVoid\n\n      -- Can the following be done with just pointer comparisons?\n      isNotLast <- pos `ne` last\n      if' isNotLast $ do\n        call iterInit [result, current, idx]\n\n      let iCurrent = ptrcast innerNode current\n      store currentPtr 0 =<< deref (childAt idx) iCurrent\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Compare.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Compare\n  ( mkCompare\n  ) where\n\nimport Eclair.LLVM.BTree.Types\nimport qualified Data.Map as Map\n\n\nmkCompare :: ModuleCodegen Operand\nmkCompare = do\n  settings <- getParams\n  tys <- asks types\n  let column' = columnTy tys\n      value = valueTy tys\n  compare' <- function \"eclair_btree_value_compare\" [(column', \"lhs\"), (column', \"rhs\")] i8 $ \\[lhs, rhs] -> mdo\n    result1 <- lhs `ult` rhs\n    if' result1 $\n      ret $ int8 (-1)\n    result2 <- lhs `ugt` rhs\n    ret =<< select result2 (int8 1) (int8 0)\n\n  function \"eclair_btree_value_compare_values\" [(ptr value, \"lhs\"), (ptr value, \"rhs\")] i8 $ \\[lhs, rhs] -> mdo\n    let columns = map fromIntegral $ index settings\n    results <- flip execStateT mempty $ flip (zygo endCheck) columns $ \\case\n      Nil -> pass\n      Cons col (atEnd, asm) -> do\n        blk <- blockNamed \"comparison\"\n        let indices = [int32 0, int32 col]\n        lhsPtr <- gep lhs indices\n        rhsPtr <- gep rhs indices\n        lhsValue <- load lhsPtr 0\n        rhsValue <- load rhsPtr 0\n        compareResult <- call compare' [lhsValue, rhsValue]\n        modify $ Map.insert compareResult blk\n        case atEnd of\n          End -> br end\n          Continue -> mdo\n            isEqual <- compareResult `eq` int8 0\n            condBr isEqual continue end\n            asm\n            continue <- currentBlock\n            pass\n    end <- blockNamed \"end\"\n    ret =<< phi (Map.toList results)\n  where\n    endCheck = \\case\n      Nil -> End\n      _ -> Continue\n\ndata ControlFlow\n  = Continue\n  | End\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Create.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Create\n  ( mkNodeNew\n  , mkBtreeInit\n  , mkBtreeInitEmpty\n  , mkBtreeSwap\n  ) where\n\nimport Prelude hiding (void, swap)\nimport Eclair.LLVM.BTree.Types\n\n\nmkNodeNew :: ModuleCodegen Operand\nmkNodeNew = mdo\n  md <- getParams\n  nodeType <- typeOf NodeType\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n\n  sizes <- asks typeSizes\n  let numKeys' = numKeys md sizes\n      ptrSize = pointerSize sizes\n      valuesByteCount = numKeys' * valueSize sizes\n      leafSize = int32 . toInteger $ leafNodeSize sizes\n      innerSize = int32 . toInteger $ innerNodeSize sizes\n\n  malloc <- asks (extMalloc . externals)\n\n  function \"eclair_btree_node_new\" [(nodeType, \"type\")] (ptr node) $ \\[ty] -> mdo\n    structSize <- select ty innerSize leafSize\n    memory <- call malloc [structSize]\n    let n = ptrcast node memory\n\n    assign (metaOf ->> parentOf) n (nullPtr node)\n    assign (metaOf ->> posInParentOf) n (int16 0)\n    assign (metaOf ->> numElemsOf) n (int16 0)\n    assign (metaOf ->> nodeTypeOf) n ty\n\n    valuesPtr <- addr valuesOf n\n    memset valuesPtr 0 valuesByteCount\n\n    isInner <- ty `eq` innerNodeTypeVal\n    if' isInner $ mdo\n      let inner = ptrcast innerNode n\n      let childrenByteCount = (numKeys' + 1) * ptrSize\n      childrenPtr <- addr childrenOf inner\n      memset childrenPtr 0 childrenByteCount\n\n    ret n\n\nmkBtreeInitEmpty :: ModuleCodegen Operand\nmkBtreeInitEmpty = do\n  tree <- typeOf BTree\n  node <- typeOf Node\n\n  function \"eclair_btree_init_empty\" [(ptr tree, \"tree\")] void $ \\[t] -> mdo\n    assign rootPtrOf t (nullPtr node)\n    assign firstPtrOf t (nullPtr node)\n\nmkBtreeInit :: Operand -> ModuleCodegen Operand\nmkBtreeInit btreeInsertRange = do\n  tree <- typeOf BTree\n  iter <- typeOf Iterator\n  let args = [(ptr tree, \"tree\"), (ptr iter, \"start\"), (ptr iter, \"end\")]\n\n  function \"eclair_btree_init\" args void $ \\[t, start, end] -> mdo\n    _ <- call btreeInsertRange [t, start, end]\n    pass\n\nmkBtreeSwap :: ModuleCodegen Operand\nmkBtreeSwap = do\n  tree <- typeOf BTree\n\n  function \"eclair_btree_swap\" [(ptr tree, \"lhs\"), (ptr tree, \"rhs\")] void $ \\[lhs, rhs] ->\n    for_ [rootPtrOf, firstPtrOf] $ \\path ->\n      swap path lhs rhs\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Destroy.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Destroy\n  ( mkBtreeDestroy\n  , mkBtreeClear\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.BTree.Types\n\n\nmkBtreeDestroy :: Operand -> ModuleCodegen Operand\nmkBtreeDestroy btreeClear = do\n  tree <- typeOf BTree\n\n  function \"eclair_btree_destroy\" [(ptr tree, \"tree\")] void $ \\[t] -> do\n    _ <- call btreeClear [t]\n    pass\n\nmkNodeDelete :: ModuleCodegen Operand\nmkNodeDelete = mdo\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n  free <- asks (extFree . externals)\n\n  nodeDelete <- function \"eclair_btree_node_delete\" [(ptr node, \"node\")] void $ \\[n] -> mdo\n    nodeTy <- deref (metaOf ->> nodeTypeOf) n\n    isInner <- nodeTy `eq` innerNodeTypeVal\n    if' isInner $ do  -- Delete children of inner node\n      let inner = ptrcast innerNode n\n\n      numElements <- deref (metaOf ->> numElemsOf) n\n      loopFor (int16 0) (`ule` numElements) (add (int16 1)) $ \\i -> mdo\n        child <- deref (childAt i) inner\n        isNotNull <- child `ne` nullPtr node\n        if' isNotNull $\n          call nodeDelete [child]\n\n    let memory = ptrcast i8 n\n    _ <- call free [memory]\n    pass\n\n  pure nodeDelete\n\nmkBtreeClear :: ModuleCodegen Operand\nmkBtreeClear = do\n  tree <- typeOf BTree\n  node <- typeOf Node\n\n  nodeDelete <- mkNodeDelete\n\n  function \"eclair_btree_clear\" [(ptr tree, \"tree\")] void $ \\[t] -> do\n    root <- deref rootPtrOf t\n    isNotNull <- root `ne` nullPtr node\n    if' isNotNull $ do\n      _ <- call nodeDelete [root]\n      assign rootPtrOf t (nullPtr node)\n      assign firstPtrOf t (nullPtr node)\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Find.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Find\n  ( mkBtreeContains\n  , mkBtreeFind\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.BTree.Types\n\n\nmkBtreeContains :: Operand -> Operand -> Operand -> ModuleCodegen Operand\nmkBtreeContains iterIsEqual btreeFind btreeEnd = do\n  tree <- typeOf BTree\n  value <- typeOf Value\n\n  function \"eclair_btree_contains\" [(ptr tree, \"tree\"), (ptr value, \"val\")] i1 $ \\[t, val] -> do\n    iterPtr <- allocateIter\n    endIterPtr <- allocateIter\n    _ <- call btreeFind [t, val, iterPtr]\n    _ <- call btreeEnd [t, endIterPtr]\n    isEqual <- call iterIsEqual [iterPtr, endIterPtr]\n    ret =<< not' isEqual\n\nmkBtreeFind :: Operand -> Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand\nmkBtreeFind isEmptyTree searchLowerBound compareValues iterInit iterInitEnd = do\n  tree <- typeOf BTree\n  iter <- typeOf Iterator\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n  value <- typeOf Value\n  valSize <- asks (valueSize . typeSizes)\n\n  let args = [(ptr tree, \"tree\"), (ptr value, \"val\"), (ptr iter, \"result\")]\n\n  function \"eclair_btree_find\" args void $ \\[t, val, result] -> mdo\n    isEmpty <- call isEmptyTree [t]\n    if' isEmpty $ do\n      _ <- call iterInitEnd [result]\n      retVoid\n\n    currentPtr <- allocate (ptr node) =<< deref rootPtrOf t\n    -- Find iterator using iterative approach\n    loop $ mdo\n      current <- load currentPtr 0\n      numElems <- deref (metaOf ->> numElemsOf) current\n      first <- addr (valueAt (int16 0)) current\n      last <- addr (valueAt numElems) current\n      pos <- call searchLowerBound [val, first, last]\n      idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize))\n\n      -- Can the following equality check be done using just pointers?\n      foundMatch <- pos `ult` last\n      if' foundMatch $ do\n        matchesVal <- (int8 0 `eq`) =<< call compareValues [pos, val]\n        if' matchesVal $ do\n          _ <- call iterInit [result, current, idx]\n          retVoid\n\n      isLeaf <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` leafNodeTypeVal)\n      if' isLeaf $ do\n        _ <- call iterInitEnd [result]\n        retVoid\n\n      -- Continue search in child node\n      let iCurrent = ptrcast innerNode current\n      store currentPtr 0 =<< deref (childAt idx) iCurrent\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Insert.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Insert\n  ( mkBtreeInsertValue\n  , mkBtreeInsertRangeTemplate\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.Table\nimport Eclair.LLVM.BTree.Types\n\nmkNodeSplitPoint :: ModuleCodegen Operand\nmkNodeSplitPoint = mdo\n  nodeSize <- typeOf NodeSize\n  numberOfKeys <- numKeysAsOperand\n\n  function \"eclair_btree_node_split_point\" [] nodeSize $ \\_ -> mdo\n    a' <- mul (int16 3) numberOfKeys\n    a <- udiv a' (int16 4)\n    b <- sub numberOfKeys (int16 2)\n    ret =<< minimum' Unsigned a b\n\nmkSplit :: Operand -> Operand -> Operand -> ModuleCodegen Operand\nmkSplit nodeNew nodeSplitPoint growParent = mdo\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n  numberOfKeys <- numKeysAsOperand\n\n  function \"eclair_btree_node_split\" [(ptr node, \"node\"), (ptr (ptr node), \"root\")] void $ \\[n, root] -> mdo\n    -- TODO: how to do assertions in LLVM?\n    -- assert(n->meta.num_elements == NUM_KEYS);\n    splitPoint <- call nodeSplitPoint []\n    splitPoint' <- add (int16 1) splitPoint\n    ty <- deref (metaOf ->> nodeTypeOf) n\n    -- Create a new sibling node and move some of the data to sibling\n    sibling <- call nodeNew [ty]\n\n    jPtr <- allocate i16 (int16 0)\n    loopFor splitPoint' (`ult` numberOfKeys) (add (int16 1)) $ \\i -> mdo\n      j <- load jPtr 0\n      assign (valueAt j) sibling =<< deref (valueAt i) n\n      store jPtr 0 =<< add (int16 1) j\n\n    isInner <- ty `eq` innerNodeTypeVal\n    if' isInner $ mdo\n      let iSibling = ptrcast innerNode sibling\n      let iN = ptrcast innerNode n\n\n      store jPtr 0 (int16 0)\n      loopFor splitPoint' (`ule` numberOfKeys) (add (int16 1)) $ \\i -> mdo\n        j <- load jPtr 0\n        iChild <- deref (childAt i) iN\n        assign (metaOf ->> parentOf) iChild sibling\n        assign (metaOf ->> posInParentOf) iChild j\n        assign (childAt j) iSibling iChild\n        store jPtr 0 =<< add (int16 1) j\n\n    assign (metaOf ->> numElemsOf) n splitPoint\n    siblingNumKeys <- sub numberOfKeys splitPoint >>= flip sub (int16 1)\n    assign (metaOf ->> numElemsOf) sibling siblingNumKeys\n\n    _ <- call growParent [n, root, sibling]\n    pass\n\nmkGrowParent :: Operand -> Operand -> ModuleCodegen Operand\nmkGrowParent nodeNew insertInner = mdo\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n\n  function \"eclair_btree_node_grow_parent\" [(ptr node, \"node\"), (ptr (ptr node), \"root\"), (ptr node, \"sibling\")] void $\n    \\[n, root, sibling] -> mdo\n    parent <- deref (metaOf ->> parentOf) n\n    isNull <- parent `eq` nullPtr node\n    numElems <- deref (metaOf ->> numElemsOf) n\n    condBr isNull createNewRoot insertNewNodeInParent\n\n    createNewRoot <- blockNamed \"create_new_root\"\n    -- TODO: assert(n == *root)\n    newRoot <- call nodeNew [innerNodeTypeVal]\n    let iNewRoot = ptrcast innerNode newRoot\n    assign (metaOf ->> numElemsOf) newRoot (int16 1)\n    lastValueOfN <- deref (valueAt numElems) n\n    assign (valueAt (int16 0)) newRoot lastValueOfN\n    assign (childAt (int16 0)) iNewRoot n\n    assign (childAt (int16 1)) iNewRoot sibling\n\n    assign (metaOf ->> parentOf) n newRoot\n    assign (metaOf ->> parentOf) sibling newRoot\n\n    -- assign (metaOf ->> posInParentOf) n (int16 0) -- Not needed, root already has position 0\n    assign (metaOf ->> posInParentOf) sibling (int16 1)\n    store root 0 newRoot\n    retVoid\n\n    insertNewNodeInParent <- blockNamed \"insert_new_node_in_parent\"\n    pos <- deref (metaOf ->> posInParentOf) n\n    lastValuePtr <- addr (valueAt numElems) n\n    _ <- call insertInner [parent, root, pos, n, lastValuePtr, sibling]\n    retVoid\n\nmkInsertInner :: Operand -> ModuleCodegen Operand\nmkInsertInner rebalanceOrSplit = mdo\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n  nodeSize <- typeOf NodeSize\n  value <- typeOf Value\n  let args = [ (ptr node, \"node\"), (ptr (ptr node), \"root\")\n             , (nodeSize, \"pos\"), (ptr node, \"predecessor\")\n             , (ptr value, \"key\"), (ptr node, \"new_node\")\n             ]\n  numberOfKeys <- numKeysAsOperand\n\n  insertInner <- function \"eclair_btree_node_insert_inner\" args void $\n    \\[n, root, pos, predecessor, key, newNode] -> mdo\n    -- Need to allocate pos on the stack, otherwise pos updates are\n    -- not visible later on!\n    posPtr <- allocate nodeSize pos\n\n    numElems <- deref (metaOf ->> numElemsOf) n\n    needsRebalanceOrSplit <- numElems `uge` numberOfKeys\n\n    if' needsRebalanceOrSplit $ do\n      position' <- load posPtr 0\n      position'' <- sub position' =<< call rebalanceOrSplit [n, root, pos]\n      store posPtr 0 position''\n      numElems' <- deref (metaOf ->> numElemsOf) n  -- NOTE: n might be updated in rebalanceOrSplit\n      needsInsertInNewNode <- position'' `ugt` numElems'\n\n      if' needsInsertInNewNode $ do\n        -- Insertion needs to be done in new sibling node:\n        pos''' <- sub position'' numElems' >>= flip sub (int16 1)\n        store posPtr 0 pos'''\n        parent <- ptrcast innerNode <$> deref (metaOf ->> parentOf) n\n        siblingPos <- add (int16 1) =<< deref (metaOf ->> posInParentOf) n\n        sibling <- deref (childAt siblingPos) parent\n        _ <- call insertInner [sibling, root, pos''', predecessor, key, newNode]\n        retVoid\n\n    -- Move bigger keys one forward\n    let iN = ptrcast innerNode n\n    numElems'' <- deref (metaOf ->> numElemsOf) n\n    startIdx <- sub numElems'' (int16 1)\n    pos' <- load posPtr 0\n    loopFor startIdx (`sge` pos') (`sub` int16 1) $ \\i -> mdo\n      j <- add i (int16 1)\n      k <- add i (int16 2)\n      assign (valueAt j) n =<< deref (valueAt i) n\n      assign (childAt k) iN =<< deref (childAt j) iN\n      childK <- deref (childAt k) iN\n      increment int16 (metaOf ->> posInParentOf) childK\n\n    -- TODO: assert(i_n->children[pos] == predecessor);\n    -- Insert new element\n    assign (valueAt pos') n =<< load key 0\n    pos'' <- add pos' (int16 1)\n    assign (childAt pos'') iN newNode\n    assign (metaOf ->> parentOf) newNode n\n    assign (metaOf ->> posInParentOf) newNode pos''\n    increment int16 (metaOf ->> numElemsOf) n\n\n  pure insertInner\n\nmkRebalanceOrSplit :: Operand -> ModuleCodegen Operand\nmkRebalanceOrSplit splitFn = mdo\n  node <- typeOf Node\n  innerNode <- typeOf InnerNode\n  nodeSize <- typeOf NodeSize\n\n  numberOfKeys <- numKeysAsOperand\n\n  let args = [(ptr node, \"node\"), (ptr (ptr node), \"root\"), (nodeSize, \"idx\")]\n  function \"eclair_btree_node_rebalance_or_split\" args nodeSize $ \\[n, root, idx] -> mdo\n    -- TODO assert(n->meta.num_elements == NUM_KEYS);\n\n    parent <- ptrcast innerNode <$> deref (metaOf ->> parentOf) n\n    pos <- deref (metaOf ->> posInParentOf) n\n    hasParent <- parent `ne` nullPtr node\n    posGTZero <- pos `ugt` int16 0\n    shouldRebalance <- and hasParent posGTZero\n    condBr shouldRebalance rebalance split\n\n    rebalance <- blockNamed \"rebalance\"\n    -- Option A) re-balance data\n    pos' <- sub pos (int16 1)\n    left <- deref (childAt pos') parent\n\n    -- Compute amount of elements movable to the left\n    leftSlotsOpen <- calculateLeftSlotsOpen numberOfKeys left idx\n    hasOpenLeftSlots <- leftSlotsOpen `ugt` int16 0\n    if' hasOpenLeftSlots $ do\n      splitPos <- deref (metaOf ->> posInParentOf) n >>= (`sub` int16 1)\n      splitter <- addr (baseOf ->> valueAt splitPos) parent\n      splitterValue <- load splitter 0\n\n      -- Move keys to left node\n      leftNumElems <- deref (metaOf ->> numElemsOf) left\n      assign (valueAt leftNumElems) left splitterValue\n\n      leftSlotsOpen' <- sub leftSlotsOpen (int16 1)\n      loopFor (int16 0) (`ult` leftSlotsOpen') (add (int16 1)) $ \\i -> do\n        j <- add leftNumElems (int16 1) >>= add i\n        assign (valueAt j) left =<< deref (valueAt i) n\n\n      store splitter 0 =<< deref (valueAt leftSlotsOpen') n\n\n      -- Shift keys in this node to the left\n      numElemsN <- deref (metaOf ->> numElemsOf) n\n      idxEnd <- sub numElemsN leftSlotsOpen\n      loopFor (int16 0) (`ult` idxEnd) (add (int16 1)) $ \\i -> do\n        j <- add i leftSlotsOpen\n        assign (valueAt i) n =<< deref (valueAt j) n\n\n      -- And children (if necessary)\n      isInnerNode <- deref (metaOf ->> nodeTypeOf) n >>= (`eq` innerNodeTypeVal)\n      if' isInnerNode $ do\n        let iN = ptrcast innerNode n\n        let iLeft = ptrcast innerNode left\n\n        -- Move children\n        loopFor (int16 0) (`ult` leftSlotsOpen) (add (int16 1)) $ \\i -> do\n          leftNumElems' <- deref (metaOf ->> numElemsOf) left\n          leftPos <- add leftNumElems' (int16 1) >>= add i\n          assign (childAt leftPos) iLeft =<< deref (childAt i) iN\n\n        -- Update moved children\n        loopFor (int16 0) (`ult` leftSlotsOpen) (add (int16 1)) $ \\i -> do\n          leftNumElems' <- deref (metaOf ->> numElemsOf) left\n          leftPos <- add leftNumElems' (int16 1) >>= add i\n          child <- deref (childAt i) iN\n          assign (metaOf ->> parentOf) child left\n          assign (metaOf ->> posInParentOf) child leftPos\n\n        -- Shift child pointer to the left\n        endIdx <- sub numElemsN leftSlotsOpen >>= add (int16 1)\n        loopFor (int16 0) (`ult` endIdx) (add (int16 1)) $ \\i -> do\n          j <- add i leftSlotsOpen\n          assign (childAt i) iN =<< deref (childAt j) iN\n          -- Update position of children\n          child <- deref (childAt i) iN\n          assign (metaOf ->> posInParentOf) child i\n\n      -- Update node sizes\n      update (metaOf ->> numElemsOf) left (`add` leftSlotsOpen)\n      update (metaOf ->> numElemsOf) n (`sub` leftSlotsOpen)\n      ret leftSlotsOpen\n\n    br split\n    split <- blockNamed \"split\"\n    -- Option B) split\n    _ <- call splitFn [n, root]\n    ret (int16 0)  -- No re-balancing\n  where\n    calculateLeftSlotsOpen numberOfKeys left' idx = do\n      numElems <- deref (metaOf ->> numElemsOf) left'\n      openSlots <- sub numberOfKeys numElems\n      isLessThan <- openSlots `slt` idx\n      select isLessThan openSlots idx\n\nmkBtreeInsertValue :: Operand -> Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand\nmkBtreeInsertValue nodeNew compareValues searchLowerBound searchUpperBound isEmptyTree = mdo\n  tree <- typeOf BTree\n  node <- typeOf Node\n  value <- typeOf Value\n  numberOfKeys <- numKeysAsOperand\n\n  splitPoint <- mkNodeSplitPoint\n  split <- mkSplit nodeNew splitPoint growParent\n  growParent <- mkGrowParent nodeNew insertInner\n  insertInner <- mkInsertInner rebalanceOrSplit\n  rebalanceOrSplit <- mkRebalanceOrSplit split\n\n  function \"eclair_btree_insert_value\" [(ptr tree, \"tree\"), (ptr value, \"val\")] i1 $ \\[t, val] -> mdo\n    isEmpty <- call isEmptyTree [t]\n    condBr isEmpty emptyCase nonEmptyCase\n\n    emptyCase <- blockNamed \"empty\"\n    leaf <- call nodeNew [leafNodeTypeVal]\n    assign (metaOf ->> numElemsOf) leaf (int16 1)\n    assign (valueAt (int16 0)) leaf =<< load val 0\n\n    assign rootPtrOf t leaf\n    assign firstPtrOf t leaf\n    br inserted\n\n    nonEmptyCase <- blockNamed \"non_empty\"\n    -- Insert using iterative approach\n    currentPtr <- allocate (ptr node) =<< deref rootPtrOf t\n    loop $ mdo\n      loopBlock <- currentBlock\n      current <- load currentPtr 0\n      isInner <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` innerNodeTypeVal)\n      condBr isInner inner leaf\n\n      inner <- blockNamed \"inner\"\n      insertInNonEmptyInnerNode loopBlock noInsert currentPtr current val\n\n      leaf <- blockNamed \"leaf\"\n      insertInNonEmptyLeafNode rebalanceOrSplit noInsert inserted t currentPtr current val numberOfKeys\n\n    noInsert <- blockNamed \"no_insert\"\n    ret (bit 0)\n\n    inserted <- blockNamed \"inserted_new_value\"\n    ret (bit 1)\n  where\n    insertInNonEmptyInnerNode loopBlock noInsert currentPtr current val = mdo\n      innerNode <- typeOf InnerNode\n      valSize <- asks (valueSize . typeSizes)\n\n      numElems <- deref (metaOf ->> numElemsOf) current\n      first <- addr (valueAt (int16 0)) current\n      last <- addr (valueAt numElems) current\n      pos <- call searchLowerBound [val, first, last]\n      idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize))\n      notLast <- pos `ne` last\n      if' notLast $ do\n        alreadyInserted <- (int8 0 `eq`) =<< call compareValues [pos, val]\n        condBr alreadyInserted noInsert continueInsert\n\n      continueInsert <- blockNamed \"inner_continue_insert\"\n      let iCurrent = ptrcast innerNode current\n      store currentPtr 0 =<< deref (childAt idx) iCurrent\n      br loopBlock\n\n    insertInNonEmptyLeafNode rebalanceOrSplit noInsert inserted t currentPtr current val numberOfKeys = mdo\n      -- Rest is for leaf nodes\n      innerNode <- typeOf InnerNode\n      valSize <- asks (valueSize . typeSizes)\n\n      -- TODO: assert(current->meta.type == LEAF_NODE);\n      numElems <- deref (metaOf ->> numElemsOf) current\n      first <- addr (valueAt (int16 0)) current\n      last <- addr (valueAt numElems) current\n      pos <- call searchUpperBound [val, first, last]\n      distance <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize))\n      idxPtr <- allocate i16 distance\n      notFirst <- pos `ne` first\n      if' notFirst $ do\n        valueAtPrevPos <- gep pos [int32 (-1)]\n        alreadyInserted <- (int8 0 `eq`) =<< call compareValues [valueAtPrevPos, val]\n        condBr alreadyInserted noInsert continueInsert\n\n      continueInsert <- blockNamed \"leaf_continue_insert\"\n      nodeIsFull <- numElems `uge` numberOfKeys\n      condBr nodeIsFull split noSplit\n\n      split <- blockNamed \"split\"\n      root <- addr rootPtrOf t\n      idx <- load idxPtr 0\n      res <- call rebalanceOrSplit [current, root, idx]\n      idx' <- sub idx res\n      store idxPtr 0 idx'\n\n      -- Insert in right fragment if needed\n      numElems' <- deref (metaOf ->> numElemsOf) current  -- NOTE: numElems' modified after rebalanceOrSplit\n      shouldInsertRight <- idx' `ugt` numElems'\n      if' shouldInsertRight $ do\n        numElems'' <- add numElems' (int16 1)\n        idx'' <- sub idx' numElems''\n        store idxPtr 0 idx''\n        parent <- ptrcast innerNode <$> deref (metaOf ->> parentOf) current\n        nextPos <- deref (metaOf ->> posInParentOf) current >>= add (int16 1)\n        store currentPtr 0 =<< deref (childAt nextPos) parent\n\n      br noSplit\n\n      noSplit <- blockNamed \"no_split\"\n\n      -- No split -> move keys and insert new element\n      current' <- load currentPtr 0  -- NOTE: current might have changed in previous part\n      idx''' <- load idxPtr 0\n      numElems''' <- deref (metaOf ->> numElemsOf) current'  -- NOTE: Might've been updated in the meantime\n      loopFor numElems''' (`ugt` idx''') (`sub` int16 1) $ \\j -> do\n        -- TODO: memmove possible?\n        j' <- sub j (int16 1)\n        assign (valueAt j) current' =<< deref (valueAt j') current'\n\n      assign (valueAt idx''') current' =<< load val 0\n      update (metaOf ->> numElemsOf) current' (add (int16 1))\n      br inserted\n\nmkBtreeInsertRangeTemplate :: Operand -> ModuleCodegen (Template IteratorParams Operand)\nmkBtreeInsertRangeTemplate btreeInsertValue = do\n  -- Context of BTree template\n  tree <- typeOf BTree\n  pure $ do\n    -- Context of insert range template\n    iterParams <- getParams\n    let iterTy = ipTypeIter iterParams\n        args = [(ptr tree, \"tree\"), (ptr iterTy, \"begin\"), (ptr iterTy, \"end\")]\n    function \"eclair_btree_insert_range\" args void $ \\[t, begin, end] -> do\n      let loopCondition = do\n            isEqual <- call (ipIterIsEqual iterParams) [begin, end]\n            not' isEqual\n      loopWhile loopCondition $ do\n        -- NOTE: Can directly insert value in other btree, same array type!\n        val <- call (ipIterCurrent iterParams) [begin]\n        _ <- call btreeInsertValue [t, val]\n        call (ipIterNext iterParams) [begin]\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Iterator.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Iterator\n  ( mkIteratorInit\n  , mkIteratorInitEnd\n  , mkIteratorIsEqual\n  , mkIteratorCurrent\n  , mkIteratorNext\n  , mkBtreeBegin\n  , mkBtreeEnd\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.BTree.Types\n\nmkIteratorInit :: ModuleCodegen Operand\nmkIteratorInit = do\n  iter <- typeOf Iterator\n  node <- typeOf Node\n  nodeSize <- typeOf NodeSize\n  let args = [(ptr iter, \"iter\"), (ptr node, \"cur\"), (nodeSize, \"pos\")]\n\n  function \"eclair_btree_iterator_init\" args void $ \\[it, cur, pos] -> do\n    assign currentPtrOf it cur\n    assign valuePosOf it pos\n\nmkIteratorInitEnd :: Operand -> ModuleCodegen Operand\nmkIteratorInitEnd iterInit = do\n  iter <- typeOf Iterator\n  node <- typeOf Node\n\n  function \"eclair_btree_iterator_end_init\" [(ptr iter, \"iter\")] void $ \\[it] -> do\n    _ <- call iterInit [it, nullPtr node, int16 0]\n    retVoid\n\nmkIteratorIsEqual :: ModuleCodegen Operand\nmkIteratorIsEqual = do\n  iter <- typeOf Iterator\n\n  function \"eclair_btree_iterator_is_equal\" [(ptr iter, \"lhs\"), (ptr iter, \"rhs\")] i1 $ \\[lhs, rhs] -> mdo\n    currentLhs <- deref currentPtrOf lhs\n    currentRhs <- deref currentPtrOf rhs\n\n    isDifferentPtrs <- currentLhs `ne` currentRhs\n    if' isDifferentPtrs $\n      ret (bit 0)\n\n    valuePosLhs <- deref valuePosOf lhs\n    valuePosRhs <- deref valuePosOf rhs\n    ret =<< valuePosLhs `eq` valuePosRhs\n\nmkIteratorCurrent :: ModuleCodegen Operand\nmkIteratorCurrent = do\n  iter <- typeOf Iterator\n  value <- typeOf Value\n\n  function \"eclair_btree_iterator_current\" [(ptr iter, \"iter\")] (ptr value) $ \\[it] -> mdo\n    valuePos <- deref valuePosOf it\n    currentNode <- deref currentPtrOf it\n    ret =<< addr (valueAt valuePos) currentNode\n\nmkIteratorNext :: ModuleCodegen Operand\nmkIteratorNext = do\n  iter <- typeOf Iterator\n\n  function \"eclair_btree_iterator_next\" [(ptr iter, \"iter\")] void $ \\[it] -> mdo\n    current <- deref currentPtrOf it\n    isInner <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` innerNodeTypeVal)\n    if' isInner $ do\n      innerIterNext leafNextBlock it\n      retVoid\n\n    leafNextBlock <- leafIterNext it\n    pass\n  where\n    leafIterNext iter = mdo\n      leafNextBlock <- blockNamed \"leaf.next\"\n      node <- typeOf Node\n      -- Case 1: Still elements left to iterate -> increment position\n      increment int16 valuePosOf iter\n      valuePos <- deref valuePosOf iter\n      current <- deref currentPtrOf iter\n      numElems <- deref (metaOf ->> numElemsOf) current\n      hasNextInLeaf <- valuePos `ult` numElems\n      if' hasNextInLeaf\n        retVoid\n\n      -- Case 2: at right-most element -> go to next inner node\n      let loopCondition = mdo\n            isNull <- deref currentPtrOf iter >>= (`eq` nullPtr node)\n            condBr isNull nullBlock notNullBlock\n            nullBlock <- blockNamed \"leaf.no_parent\"\n            br endLoopCondition\n\n            notNullBlock <- blockNamed \"leaf.has_parent\"\n            pos' <- deref valuePosOf iter\n            current' <- deref currentPtrOf iter\n            numElems' <- deref (metaOf ->> numElemsOf) current'\n            atEnd <- pos' `eq` numElems'\n\n            br endLoopCondition\n\n            endLoopCondition <- blockNamed \"loop.condition.end\"\n            phi [(bit 0, nullBlock), (atEnd, notNullBlock)]\n      loopWhile loopCondition $ do\n        current' <- deref currentPtrOf iter\n        assign valuePosOf iter =<< deref (metaOf ->> posInParentOf) current'\n        assign currentPtrOf iter =<< deref (metaOf ->> parentOf) current'\n\n      pure leafNextBlock\n\n    innerIterNext leafNext iter = mdo\n      node <- typeOf Node\n      innerNode <- typeOf InnerNode\n      -- Case 3: Go to left most child in inner node (a leaf node)\n      nextPos <- deref valuePosOf iter >>= add (int16 1)\n      iCurrent <- ptrcast innerNode <$> deref currentPtrOf iter\n      currentPtr <- allocate (ptr node) =<< deref (childAt nextPos) iCurrent\n      let loopCondition' = do\n            ty <- deref (metaOf ->> nodeTypeOf) =<< load currentPtr 0\n            ty `eq` innerNodeTypeVal\n      loopWhile loopCondition' $ do\n        iCurrent' <- ptrcast innerNode <$> load currentPtr 0\n        firstChild <- deref (childAt (int16 0)) iCurrent'\n        store currentPtr 0 firstChild\n\n      currentLeaf <- load currentPtr 0\n      assign currentPtrOf iter currentLeaf\n      assign valuePosOf iter (int16 0)\n\n      -- Leaf nodes may be empty due to biased insertion => go to next\n      isNotEmpty <- deref (metaOf ->> numElemsOf) currentLeaf >>= (`ne` int16 0)\n      if' isNotEmpty $ do\n        retVoid\n\n      br leafNext\n\nmkBtreeBegin :: ModuleCodegen Operand\nmkBtreeBegin = do\n  tree <- typeOf BTree\n  iter <- typeOf Iterator\n\n  function \"eclair_btree_begin\" [(ptr tree, \"tree\"), (ptr iter, \"result\")] void $ \\[t, result] -> do\n    assign currentPtrOf result =<< deref firstPtrOf t\n    assign valuePosOf result (int16 0)\n\nmkBtreeEnd :: Operand -> ModuleCodegen Operand\nmkBtreeEnd iteratorInitEnd = do\n  tree <- typeOf BTree\n  iter <- typeOf Iterator\n\n  function \"eclair_btree_end\" [(ptr tree, \"tree\"), (ptr iter, \"result\")] void $ \\[_t, result] -> do\n    _ <- call iteratorInitEnd [result]\n    pass\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Size.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.BTree.Size\n  ( mkNodeCountEntries\n  , mkBtreeIsEmpty\n  , mkBtreeSize\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.BTree.Types\n\nmkNodeCountEntries :: ModuleCodegen Operand\nmkNodeCountEntries = mdo\n  node <- typeOf Node\n\n  countEntries <- function \"eclair_btree_node_count_entries\" [(ptr node, \"node\")] i64 $ \\[n] -> mdo\n    numElements <- deref (metaOf ->> numElemsOf) n\n    ty <- deref (metaOf ->> nodeTypeOf) n\n    isLeaf <- ty `eq` leafNodeTypeVal\n    numElements' <- zext numElements i64\n    if' isLeaf $\n      ret numElements'\n\n    count <- loopChildren n i64 numElements' $ \\entryCount child -> mdo\n      childNodeCount <- call countEntries [child]\n      add entryCount childNodeCount\n    ret count\n\n  pure countEntries\n  where\n    loopChildren n ty beginValue f = mdo\n      innerNode <- typeOf InnerNode\n      let inner = ptrcast innerNode n\n      result <- allocate ty beginValue\n      numElements <- deref (metaOf ->> numElemsOf) n\n      loopFor (int16 0) (`ule` numElements) (add (int16 1)) $ \\i -> mdo\n        currentResult <- load result 0\n        child <- deref (childAt i) inner\n        updatedResult <- f currentResult child\n        store result 0 updatedResult\n\n      load result 0\n\nmkBtreeIsEmpty :: ModuleCodegen Operand\nmkBtreeIsEmpty = do\n  tree <- typeOf BTree\n  node <- typeOf Node\n\n  function \"eclair_btree_is_empty\" [(ptr tree, \"tree\")] i1 $ \\[t] -> do\n    root <- deref rootPtrOf t\n    ret =<< root `eq` nullPtr node\n\nmkBtreeSize :: Operand -> ModuleCodegen Operand\nmkBtreeSize nodeCountEntries = do\n  tree <- typeOf BTree\n  node <- typeOf Node\n\n  function \"eclair_btree_size\" [(ptr tree, \"tree\")] i64 $ \\[t] -> mdo\n    root <- deref rootPtrOf t\n    isNull <- root `eq` nullPtr node\n    condBr isNull nullBlock notNullBlock\n\n    nullBlock <- blockNamed \"null\"\n    ret (int64 0)\n\n    notNullBlock <- blockNamed \"not_null\"\n    count <- call nodeCountEntries [root]\n    ret count\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree/Types.hs",
    "content": "module Eclair.LLVM.BTree.Types\n  ( Meta(..)\n  , Types(..)\n  , SearchIndex\n  , SearchType(..)\n  , Sizes(..)\n  , CGState(..)\n  , IRCodegen\n  , ModuleCodegen\n  , Index(..)\n  , metaOf\n  , valuesOf\n  , valueAt\n  , parentOf\n  , posInParentOf\n  , numElemsOf\n  , nodeTypeOf\n  , baseOf\n  , childrenOf\n  , childAt\n  , currentPtrOf\n  , valuePosOf\n  , rootPtrOf\n  , firstPtrOf\n  , DataType(..)\n  , typeOf\n  , allocateIter\n  , memset\n  , leafNodeTypeVal\n  , innerNodeTypeVal\n  , numKeysAsOperand\n  , numKeysHelper\n  , numKeys\n  , module Eclair.LLVM.Externals\n  , module Eclair.LLVM.Codegen\n  ) where\n\nimport Eclair.LLVM.Codegen\nimport Eclair.LLVM.Hash\nimport Eclair.LLVM.Externals\nimport Prettyprinter\n\ndata Meta\n  = Meta\n  { numColumns :: Int        -- Amount of columns each node has\n  , index :: SearchIndex     -- Which columns are used to index values\n  , blockSize :: Word64      -- Number of bytes per btree node\n  , searchType :: SearchType -- Search strategy used in a single node\n  }\n  deriving stock (Eq, Ord, Show)\n  deriving stock Generic\n  deriving ToHash via HashWithPrefix \"btree\" Meta\n\ninstance Pretty Meta where\n  pretty meta =\n    \"num_columns=\" <> pretty (numColumns meta) <> comma <+>\n    -- TODO: use \"withCommas\"\n    \"index=\" <> brackets (Prelude.fold $ intersperse comma $ map pretty (index meta)) <> comma <+>\n    \"block_size=\" <> pretty (blockSize meta) <> comma <+>\n    \"search_type=\" <> pretty (searchType meta)\n\ntype Column = Int\n\ntype SearchIndex = [Column]\n\ndata SearchType = Linear | Binary\n  deriving stock (Eq, Ord, Show)\n  deriving stock (Generic, Enum)\n\ninstance ToHash SearchType where\n  getHash = \\case\n    Linear -> getHash (\"linear\" :: Text)\n    Binary -> getHash (\"binary\" :: Text)\n\ninstance Pretty SearchType where\n  pretty Linear = \"linear\"\n  pretty Binary = \"binary\"\n\ndata Types\n  = Types\n  { btreeTy :: Type\n  , iteratorTy :: Type\n  , nodeSizeTy :: Type\n  , nodeTypeTy :: Type\n  , nodeTy :: Type\n  , leafNodeTy :: Type\n  , innerNodeTy :: Type\n  , valueTy :: Type\n  , columnTy :: Type\n  }\n\ndata Sizes\n  = Sizes\n  { pointerSize :: Word64\n  , valueSize :: Word64\n  , nodeDataSize :: Word64\n  , leafNodeSize :: Word64\n  , innerNodeSize :: Word64\n  }\n\n-- State used during rest of the btree codegen\ndata CGState\n  = CGState\n  { types :: Types\n  , typeSizes :: Sizes\n  , externals :: Externals\n  }\n\ntype IRCodegen = IRBuilderT ModuleCodegen\n\ntype ModuleCodegen = ReaderT CGState (Template Meta)\n\ndata Index\n  = NodeIdx\n  | InnerNodeIdx\n  | MetaIdx\n  | ValueIdx\n  | PositionIdx\n  | NumElemsIdx\n  | NodeTypeIdx\n  | IteratorIdx\n  | TreeIdx\n  | ArrayOf Index\n  | PtrOf Index\n\nmetaOf :: Path 'NodeIdx 'MetaIdx\nmetaOf = mkPath [int32 0]\n\nvaluesOf :: Path 'NodeIdx ('ArrayOf 'ValueIdx)\nvaluesOf = mkPath [int32 1]\n\nvalueAt :: Operand -> Path 'NodeIdx 'ValueIdx\nvalueAt idx = mkPath [int32 1, idx]\n\nparentOf :: Path 'MetaIdx 'NodeIdx\nparentOf = mkPath [int32 0]\n\nposInParentOf :: Path 'MetaIdx 'PositionIdx\nposInParentOf = mkPath [int32 1]\n\nnumElemsOf :: Path 'MetaIdx 'NumElemsIdx\nnumElemsOf = mkPath [int32 2]\n\nnodeTypeOf :: Path 'MetaIdx 'NodeTypeIdx\nnodeTypeOf = mkPath [int32 3]\n\nbaseOf :: Path 'InnerNodeIdx 'NodeIdx\nbaseOf = mkPath [int32 0]\n\nchildrenOf :: Path 'InnerNodeIdx ('ArrayOf 'NodeIdx)\nchildrenOf = mkPath [int32 1]\n\nchildAt :: Operand -> Path 'InnerNodeIdx ('PtrOf 'NodeIdx)\nchildAt idx = mkPath [int32 1, idx]\n\ncurrentPtrOf :: Path 'IteratorIdx ('PtrOf 'NodeIdx)\ncurrentPtrOf = mkPath [int32 0]\n\nvaluePosOf :: Path 'IteratorIdx 'PositionIdx\nvaluePosOf = mkPath [int32 1]\n\nrootPtrOf :: Path 'TreeIdx ('PtrOf 'NodeIdx)\nrootPtrOf = mkPath [int32 0]\n\nfirstPtrOf :: Path 'TreeIdx ('PtrOf 'NodeIdx)\nfirstPtrOf = mkPath [int32 1]\n\ndata DataType\n  = NodeType\n  | Node\n  | InnerNode\n  | Value\n  | NodeSize\n  | Iterator\n  | BTree\n\ntypeOf :: MonadReader CGState m => DataType -> m Type\ntypeOf dt =\n  let getType = case dt of\n        Node -> nodeTy\n        NodeType -> nodeTypeTy\n        InnerNode -> innerNodeTy\n        Value -> valueTy\n        NodeSize -> nodeSizeTy\n        Iterator -> iteratorTy\n        BTree -> btreeTy\n   in getType <$> asks types\n\nmemset :: Operand -> Word8 -> Word64 -> IRCodegen ()\nmemset p val byteCount = do\n  memsetFn <- asks (extMemset . externals)\n  let p' = ptrcast i8 p\n  _ <- call memsetFn [ p'\n                     , int8 $ fromIntegral val\n                     , int64 (fromIntegral byteCount)\n                     , bit 0\n                     ]\n  pass\n\nleafNodeTypeVal, innerNodeTypeVal :: Operand\nleafNodeTypeVal = bit 0\ninnerNodeTypeVal = bit 1\n\nnumKeys :: Meta -> Sizes -> Word64\nnumKeys settings sizes =\n  numKeysHelper settings nodeMetaSize valueByteSize\n  where\n    nodeMetaSize = nodeDataSize sizes\n    valueByteSize = valueSize sizes\n\n-- NOTE: Where possible, use the more userfriendly numKeys function\nnumKeysHelper :: Meta -> Word64 -> Word64 -> Word64\nnumKeysHelper settings nodeMetaSize valueByteSize =\n  max 3 desiredNumberOfKeys\n  where\n    blockByteSize = blockSize settings\n    valuesByteSize =\n      if blockByteSize > nodeMetaSize\n      then blockByteSize - nodeMetaSize\n      else 0\n    desiredNumberOfKeys = valuesByteSize `div` valueByteSize\n\nnumKeysAsOperand :: ModuleCodegen Operand\nnumKeysAsOperand = do\n  metadata <- getParams\n  sizes <- asks typeSizes\n  pure $ int16 $ toInteger $ numKeys metadata sizes\n\n-- NOTE: this only allocates on stack, but doesn't initialize it,\n-- this still needs to happen in rest of the code\nallocateIter :: IRCodegen Operand\nallocateIter = do\n  iter <- typeOf Iterator\n  alloca iter (Just (int32 1)) 0\n"
  },
  {
    "path": "lib/Eclair/LLVM/BTree.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}\n\nmodule Eclair.LLVM.BTree\n  ( Meta(..)\n  , SearchIndex\n  , SearchType(..)\n  , codegen\n  ) where\n\nimport Prelude hiding (void, swap)\nimport Control.Monad.Morph\nimport Eclair.LLVM.Codegen\nimport Eclair.LLVM.Table\nimport Eclair.LLVM.BTree.Types\nimport Eclair.LLVM.BTree.Create\nimport Eclair.LLVM.BTree.Destroy\nimport Eclair.LLVM.BTree.Compare\nimport Eclair.LLVM.BTree.Iterator\nimport Eclair.LLVM.BTree.Insert\nimport Eclair.LLVM.BTree.Find\nimport Eclair.LLVM.BTree.Bounds\nimport Eclair.LLVM.BTree.Size\n\n\ncodegen :: Externals -> ConfigT (TemplateT Meta IO) Table\ncodegen exts = do\n  sizes <- computeSizes\n  lift $ hoist intoIO $ do\n    tys <- generateTypes sizes\n    runReaderT generateTableFunctions $ CGState tys sizes exts\n  where intoIO = pure . runIdentity\n\ncomputeSizes :: ConfigT (TemplateT Meta IO) Sizes\ncomputeSizes = do\n  (ctx, td) <- (cfgLLVMContext &&& cfgTargetData) <$> getConfig\n  settings <- getParams\n  let nodeDataTy = StructureType Off\n        [ -- Next type doesn't matter here, but we need to break the\n          -- cyclic loop or Haskell will throw an exception.\n          ptrTy   -- parent\n        , i16     -- position_in_parent\n        , i16     -- num_elements\n        , i1      -- node type\n        ]\n      ptrTy = ptr i8\n      valueType = ArrayType (fromIntegral $ numColumns settings) i32\n  (ptrSz, valueSz, nodeDataSz) <- withLLVMTypeInfo ctx $ do\n    let sizeOf = llvmSizeOf ctx td\n    pointerSize' <- sizeOf ptrTy\n    valueSize' <- sizeOf valueType\n    nodeDataSize' <- sizeOf nodeDataTy\n    pure (pointerSize', valueSize', nodeDataSize')\n\n  let numKeys' = fromIntegral $ numKeysHelper settings nodeDataSz valueSz\n      nodeType = StructureType Off [nodeDataTy, ArrayType numKeys' valueType]\n      innerNodeType = StructureType Off [nodeType, ArrayType (numKeys' + 1) (ptr nodeType)]\n  (leafNodeSz, innerNodeSz) <- withLLVMTypeInfo ctx $ do\n    let sizeOf = llvmSizeOf ctx td\n    leafNodeSize' <- sizeOf nodeType\n    innerNodeSize' <- sizeOf innerNodeType\n    pure (leafNodeSize', innerNodeSize')\n\n  pure $ Sizes ptrSz valueSz nodeDataSz leafNodeSz innerNodeSz\n\ngenerateTypes :: (MonadModuleBuilder m, MonadFix m, MonadTemplate Meta m, HasSuffix m)\n              => Sizes -> m Types\ngenerateTypes sizes = mdo\n  meta <- getParams\n  let numKeys' = fromIntegral $ numKeys meta sizes\n\n  let columnTy' = i32\n      valueTy' = ArrayType (fromIntegral $ numColumns meta) columnTy'\n      positionTy = i16\n      nodeSizeTy' = i16  -- Note: used to be size_t/i64\n      nodeTypeTy' = i1\n      nodeDataName = \"node_data_t\"\n  nodeDataTy <- typedef nodeDataName Off\n    [ ptr nodeTy  -- parent\n    , positionTy  -- position_in_parent\n    , nodeSizeTy'  -- num_elements\n    , nodeTypeTy'  -- node type\n    ]\n  nodeTy <- typedef \"node_t\" Off\n    [ nodeDataTy                  -- meta\n    , ArrayType numKeys' valueTy'  -- values\n    ]\n  let leafNodeTy' = nodeTy\n  innerNodeTy <- typedef \"inner_node_t\" Off\n    [ nodeTy                                 -- base\n    , ArrayType (numKeys' + 1) (ptr nodeTy)  -- children\n    ]\n  btreeIteratorTy <- typedef \"btree_iterator_t\" Off\n    [ ptr nodeTy  -- current\n    , positionTy  -- value pos\n    ]\n  btreeTy <- typedef \"btree_t\" Off\n    [ ptr nodeTy  -- root\n    , ptr nodeTy  -- first\n    ]\n  pure $ Types\n    { btreeTy = btreeTy\n    , iteratorTy = btreeIteratorTy\n    , nodeSizeTy = nodeSizeTy'\n    , nodeTypeTy = nodeTypeTy'\n    , nodeTy = nodeTy\n    , leafNodeTy = leafNodeTy'\n    , innerNodeTy = innerNodeTy\n    , valueTy = valueTy'\n    , columnTy = columnTy'\n    }\n\ngenerateTableFunctions :: ModuleCodegen Table\ngenerateTableFunctions = mdo\n  tree <- typeOf BTree\n  iter <- typeOf Iterator\n  value <- typeOf Value\n\n  compareValues <- mkCompare\n  nodeNew <- mkNodeNew\n  nodeCountEntries <- mkNodeCountEntries\n  iterInit <- mkIteratorInit\n  iterInitEnd <- mkIteratorInitEnd iterInit\n  iterIsEqual <- mkIteratorIsEqual\n  iterCurrent <- mkIteratorCurrent\n  iterNext <- mkIteratorNext\n  searchLowerBound <- mkLinearSearchLowerBound compareValues\n  searchUpperBound <- mkLinearSearchUpperBound compareValues\n  btreeInitEmpty <- mkBtreeInitEmpty\n  btreeInit <- mkBtreeInit btreeInsertRange\n  btreeDestroy <- mkBtreeDestroy btreeClear\n  isEmptyTree <- mkBtreeIsEmpty\n  btreeSize <- mkBtreeSize nodeCountEntries\n  btreeInsert <- mkBtreeInsertValue nodeNew compareValues searchLowerBound searchUpperBound isEmptyTree\n  btreeInsertRangeTemplate <- mkBtreeInsertRangeTemplate btreeInsert\n  -- We need to instantiate it atleast once for use in the BTree itself.\n  let iterParams = IteratorParams\n        { ipIterCurrent = iterCurrent\n        , ipIterNext = iterNext\n        , ipIterIsEqual = iterIsEqual\n        , ipTypeIter = iter\n        }\n  btreeInsertRange <- lift $ partialInstantiate iterParams btreeInsertRangeTemplate\n  btreeBegin <- mkBtreeBegin\n  btreeEnd <- mkBtreeEnd iterInitEnd\n  btreeContains <- mkBtreeContains iterIsEqual btreeFind btreeEnd\n  btreeFind <- mkBtreeFind isEmptyTree searchLowerBound compareValues iterInit iterInitEnd\n  btreeLowerBound <- mkBtreeLowerBound isEmptyTree iterInit iterInitEnd searchLowerBound compareValues\n  btreeUpperBound <- mkBtreeUpperBound isEmptyTree iterInit iterInitEnd searchUpperBound\n  btreeClear <- mkBtreeClear\n  btreeSwap <- mkBtreeSwap\n\n  pure Table\n        { fnInit = btreeInit\n        , fnInitEmpty = btreeInitEmpty\n        , fnDestroy = btreeDestroy\n        , fnPurge = btreeClear\n        , fnSwap = btreeSwap\n        , fnBegin = btreeBegin\n        , fnEnd = btreeEnd\n        , fnInsert = btreeInsert\n        , fnInsertRangeTemplate = btreeInsertRangeTemplate\n        , fnIsEmpty = isEmptyTree\n        , fnSize = btreeSize\n        , fnLowerBound = btreeLowerBound\n        , fnUpperBound = btreeUpperBound\n        , fnContains = btreeContains\n        , fnIterIsEqual = iterIsEqual\n        , fnIterCurrent = iterCurrent\n        , fnIterNext = iterNext\n        , typeObj = tree\n        , typeIter = iter\n        , typeValue = value\n        }\n"
  },
  {
    "path": "lib/Eclair/LLVM/Codegen.hs",
    "content": "{-# LANGUAGE RoleAnnotations, PolyKinds #-}\n\nmodule Eclair.LLVM.Codegen\n  ( module Eclair.LLVM.Codegen\n  , module Eclair.LLVM.Template\n  , module Eclair.LLVM.Config\n  , module LLVM.Codegen\n  ) where\n\nimport qualified Data.Map as M\nimport Foreign.ForeignPtr\nimport Foreign.Ptr hiding (nullPtr)\nimport Eclair.LLVM.Template\nimport LLVM.Codegen hiding (function, typedef, typeOf)\nimport qualified LLVM.C.API as LibLLVM\nimport Eclair.LLVM.Config\n\n\nllvmSizeOf :: (MonadModuleBuilder m, MonadIO m)\n           => ForeignPtr LibLLVM.Context -> Ptr LibLLVM.TargetData -> Type -> m Word64\nllvmSizeOf ctx td ty = liftIO $ do\n  ty' <- encodeType ctx ty\n  LibLLVM.sizeOfType td ty'\n\nwithLLVMTypeInfo :: (MonadModuleBuilder m, MonadIO m)\n                 => ForeignPtr LibLLVM.Context -> m a -> m a\nwithLLVMTypeInfo ctx m = do\n  -- First, we forward declare all struct types known up to this point,\n  typedefs <- getTypedefs\n  structTys <- liftIO $ M.traverseWithKey (forwardDeclareStruct ctx) typedefs\n\n  -- Then we serialize all types (including structs, with their bodies),\n  _ <- liftIO $ traverse (serialize ctx) structTys\n  -- Finally, we can call the function with all type info available in LLVM.\n  m\n  where\n    forwardDeclareStruct ctx' name structTy =\n      (,structTy) <$> LibLLVM.mkOpaqueStructType ctx' name\n\n    serialize :: ForeignPtr LibLLVM.Context -> (Ptr LibLLVM.Type, Type) -> IO ()\n    serialize ctx' (llvmTy, ty) = case ty of\n      StructureType packed tys -> do\n        tys' <- traverse (encodeType ctx') tys\n        LibLLVM.setNamedStructBody llvmTy tys' packed\n      _ ->\n        panic $ \"Unexpected typedef: only structs are allowed, but got: \" <> show ty\n\n-- NOTE: this only works if all the named structs are known beforehand (a.k.a. forward declared)!\nencodeType :: ForeignPtr LibLLVM.Context -> Type -> IO (Ptr LibLLVM.Type)\nencodeType ctx = go\n  where\n    go = \\case\n      VoidType ->\n        LibLLVM.mkVoidType ctx\n      IntType bits ->\n        LibLLVM.mkIntType ctx bits\n      PointerType ty ->\n        LibLLVM.mkPointerType =<< go ty\n      StructureType packed tys -> do\n        tys' <- traverse go tys\n        LibLLVM.mkAnonStructType ctx tys' packed\n      ArrayType count ty -> do\n        ty' <- go ty\n        LibLLVM.mkArrayType ty' count\n      FunctionType retTy argTys -> do\n        retTy' <- go retTy\n        argTys' <- traverse go argTys\n        LibLLVM.mkFunctionType retTy' argTys'\n      NamedTypeReference name ->\n        LibLLVM.getTypeByName ctx name\n"
  },
  {
    "path": "lib/Eclair/LLVM/Config.hs",
    "content": "module Eclair.LLVM.Config\n  ( Config(..)\n  , ConfigT\n  , runConfigT\n  , MonadConfig(..)\n  ) where\n\nimport qualified LLVM.C.API as LibLLVM\nimport LLVM.Codegen\nimport Control.Monad.Morph\nimport Foreign.ForeignPtr\nimport Foreign.Ptr\nimport Eclair.Common.Config (Target)\n\n-- This is a helper module for carrying around specific information\n-- when compiling to a specific LLVM platform.\n\ndata Config\n  = Config\n    { cfgTargetTriple :: Maybe Target\n    , cfgLLVMContext :: ForeignPtr LibLLVM.Context\n    , cfgTargetData :: Ptr LibLLVM.TargetData\n    }\n\n-- TODO: automatically wrap ModuleBuilderT and call it CompileT?\nnewtype ConfigT m a\n  = ConfigT\n  { unConfigT :: ReaderT Config m a\n  } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadModuleBuilder)\n  via ReaderT Config m\n  deriving MonadTrans via ReaderT Config\n\ninstance MFunctor ConfigT where\n  hoist f =\n    ConfigT . hoist f . unConfigT\n\nrunConfigT :: Config -> ConfigT m a -> m a\nrunConfigT cfg m =\n  runReaderT (unConfigT m) cfg\n\nclass Monad m => MonadConfig m where\n  getConfig :: m Config\n\ninstance Monad m => MonadConfig (ConfigT m) where\n  getConfig =\n    ConfigT ask\n\n-- TODO other instances\n"
  },
  {
    "path": "lib/Eclair/LLVM/Externals.hs",
    "content": "module Eclair.LLVM.Externals\n  ( Externals(..)\n  ) where\n\nimport Eclair.LLVM.Codegen (Operand)\n\n-- Functions that are defined outside of LLVM.\ndata Externals\n  = Externals\n  { extMalloc :: Operand\n  , extFree   :: Operand\n  , extMemset :: Operand\n  , extMemcpy :: Operand\n  , extMemcmp :: Operand\n  , extMmap   :: Operand\n  , extMunmap :: Operand\n  }\n"
  },
  {
    "path": "lib/Eclair/LLVM/Hash.hs",
    "content": "{-# LANGUAGE TypeApplications, UndecidableInstances, TypeOperators, DefaultSignatures #-}\n\nmodule Eclair.LLVM.Hash\n  ( Hash\n  , unHash\n  , HashEnum(..)\n  , HashWithPrefix(..)\n  , HashOnly(..)\n  , ToHash(..)\n  ) where\n\nimport qualified Data.Text as T\n\n\nnewtype Hash = Hash { unHash :: T.Text }\n\ninstance Semigroup Hash where\n  Hash h1 <> Hash h2 =\n    Hash $ h1 <> \"__\" <> h2\n\nnewtype HashEnum a = HashEnum a\n\nnewtype HashWithPrefix (prefix :: Symbol) a\n  = HashWithPrefix a\n\nnewtype HashOnly (ty :: Symbol) a = HashOnly a\n\nclass ToHash a where\n  getHash :: a -> Hash\n  default getHash :: (Generic a, GToHash (Rep a)) => a -> Hash\n  getHash a = gGetHash (from a)\n\ninstance ToHash T.Text where\n  getHash = Hash\n\ninstance ToHash Int where\n  getHash = Hash . show\n\ninstance ToHash Word64 where\n  getHash = Hash . show\n\ninstance ToHash a => ToHash [a] where\n  getHash = getFoldableHash\n\ninstance ToHash a => ToHash (NonEmpty a) where\n  getHash = getFoldableHash\n\ninstance ToHash a => ToHash (Set a) where\n  getHash = getFoldableHash\n\ngetFoldableHash :: (Foldable f, ToHash a) => f a -> Hash\ngetFoldableHash =\n  Hash . mconcat . intersperse \"_\" . map (unHash . getHash) . toList\n\ninstance (Enum a) => ToHash (HashEnum a) where\n  getHash (HashEnum a) = getHash $ fromEnum a\n\ninstance forall prefix a. (KnownSymbol prefix, Generic a, GToHash (Rep a))\n  => ToHash (HashWithPrefix prefix a) where\n  getHash (HashWithPrefix a) =\n    let pre = Hash $ toText $ symbolVal (Proxy :: Proxy prefix)\n        h = gGetHash (from a)\n     in pre <> h\n\ninstance (HasField ty a b, ToHash b) => ToHash (HashOnly ty a) where\n  getHash (HashOnly a) =\n    getHash $ getField @ty a\n\nclass GToHash f where\n  gGetHash :: f a -> Hash\n\ninstance ToHash a => GToHash (K1 i a) where\n  gGetHash (K1 x) = getHash x\n\ninstance GToHash U1 where\n  gGetHash U1 = Hash \"0\"\n\ninstance GToHash a => GToHash (M1 i c a) where\n  gGetHash (M1 x) = gGetHash x\n\ninstance (GToHash f, GToHash g) => GToHash (f :*: g) where\n  gGetHash (a :*: b) =\n    gGetHash a <> gGetHash b\n\n"
  },
  {
    "path": "lib/Eclair/LLVM/HashMap.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Eclair.LLVM.HashMap\n  ( HashMap(..)\n  , Types(..)\n  , codegen\n  ) where\n\nimport Prelude hiding (void, HashMap)\nimport Control.Monad.Morph\nimport Eclair.LLVM.Codegen\nimport Eclair.LLVM.Externals\nimport qualified Eclair.LLVM.Symbol as Symbol\nimport qualified Eclair.LLVM.Vector as Vector\n\n-- NOTE: this is a really naive hashmap (no growing / re-hashing yet),\n-- should be replaced by something more sophisticated, like:\n-- https://github.com/souffle-lang/souffle/blob/d1522e06c6e99c259951dc348689a77fa5f5c932/src/include/souffle/datastructure/ConcurrentInsertOnlyHashMap.h\n\ndata Types\n  = Types\n  { tyHashMap :: Type\n  , tyKey :: Type\n  , tyValue :: Type\n  , tyEntry :: Type  -- struct containing key + value type\n  }\n\ndata HashMap\n  = HashMap\n  { hashMapTypes :: Types\n  , hashMapInit :: Operand\n  , hashMapDestroy :: Operand\n  , hashMapGetOrPutValue :: Operand\n  , hashMapLookup :: Operand\n  , hashMapContains :: Operand\n  }\n\ndata CGState\n  = CGState\n  { types :: Types\n  , symbolCodegen :: Symbol.Symbol\n  , vectorCodegen :: Vector.Vector\n  }\n\ntype ModuleCodegen = ReaderT CGState ModuleBuilder\ntype IRCodegen = IRBuilderT ModuleCodegen\n\n\n-- NOTE: no need to turn into template (for now)\ncodegen :: Symbol.Symbol -> Externals -> ConfigT (ModuleBuilderT IO) HashMap\ncodegen symbol exts = do\n  let keyTy = Symbol.tySymbol symbol\n      valueTy = i32\n  entryTy <- typedef \"entry_t\" Off [keyTy, valueTy]\n  vec <- hoist (instantiate \"entry\" entryTy) $ Vector.codegen exts Nothing\n  lift $ do\n    let vecTy = Vector.tyVector $ Vector.vectorTypes vec\n    hashMapTy <- typedef \"hashmap_t\" Off [ArrayType capacity vecTy]\n    let tys = Types\n          { tyHashMap = hashMapTy\n          , tyKey = keyTy\n          , tyValue = valueTy\n          , tyEntry = entryTy\n          }\n\n    hoist intoIO $ runReaderT generateFunctions $ CGState tys symbol vec\n  where\n    intoIO = pure . runIdentity\n\n-- TODO make this variable sized\ncapacity :: Word32\ncapacity = 64\n\ngenerateFunctions :: ModuleCodegen HashMap\ngenerateFunctions = do\n  tys <- asks types\n  hmHash <- mkHash\n  hmInit <- mkHashMapInit\n  hmDestroy <- mkHashMapDestroy\n  hmGetOrPutValue <- mkHashMapGetOrPutValue hmHash\n  hmLookup <- mkHashMapLookup hmHash\n  hmContains <- mkHashMapContains hmHash\n  pure $ HashMap\n    { hashMapTypes = tys\n    , hashMapInit = hmInit\n    , hashMapDestroy = hmDestroy\n    , hashMapGetOrPutValue = hmGetOrPutValue\n    , hashMapLookup = hmLookup\n    , hashMapContains = hmContains\n    }\n\nmkHash :: ModuleCodegen Operand\nmkHash = do\n  symbolTy <- symbolType\n  let hashTy = i32\n\n  -- TODO better hash function?\n  function \"eclair_symbol_hash\" [(ptr symbolTy, \"symbol\")] hashTy $ \\[symbol] -> do\n    hashPtr <- allocate hashTy (int32 0)\n    symbolSize <- deref Symbol.sizeOf symbol\n    dataPtr <- deref Symbol.dataOf symbol\n\n    loopFor (int32 0) (`ult` symbolSize) (add (int32 1)) $ \\i -> do\n      -- We need a raw gep here, since the data is dynamically allocated.\n      bytePtr <- gep dataPtr [i]\n      byte <- load bytePtr 0 >>= (`zext` i32)\n      currentHashCode <- load hashPtr 0\n      result1 <- mul (int32 31) currentHashCode\n      result2 <- add byte result1\n      store hashPtr 0 result2\n\n    hashCode <- load hashPtr 0\n    ret =<< modulo hashCode capacity\n\nmkHashMapInit :: ModuleCodegen Operand\nmkHashMapInit = do\n  (hmTy, vec) <- asks (tyHashMap . types &&& vectorCodegen)\n\n  function \"eclair_hashmap_init\" [(ptr hmTy, \"hashmap\")] void $ \\[hm] -> do\n    loopBuckets hm $ \\bucketPtr -> do\n      call (Vector.vectorInit vec) [bucketPtr]\n\nmkHashMapDestroy :: ModuleCodegen Operand\nmkHashMapDestroy = do\n  (hmTy, vec) <- asks (tyHashMap . types &&& vectorCodegen)\n\n  function \"eclair_hashmap_destroy\" [(ptr hmTy, \"hashmap\")] void $ \\[hm] -> do\n    loopBuckets hm $ \\bucketPtr -> do\n      call (Vector.vectorDestroy vec) [bucketPtr]\n\nmkHashMapGetOrPutValue :: Operand -> ModuleCodegen Operand\nmkHashMapGetOrPutValue hashFn = do\n  (hmTy, entryTy) <- asks ((tyHashMap &&& tyEntry) . types)\n  symbolTy <- asks (Symbol.tySymbol . symbolCodegen)\n  vec <- asks vectorCodegen\n\n  let args = [(ptr hmTy, \"hashmap\"), (ptr symbolTy, \"symbol\"), (i32, \"value\")]\n\n  function \"eclair_hashmap_get_or_put_value\" args i32 $ \\[hm, symbolPtr, value] -> do\n    bucketPtr <- bucketForHash hashFn hm symbolPtr\n    loopEntriesInBucket symbolPtr bucketPtr $\n      ret <=< deref valueOf\n\n    symbolValue <- load symbolPtr 0\n    newEntryPtr <- alloca entryTy Nothing 0\n    assign symbolOf newEntryPtr symbolValue\n    assign valueOf newEntryPtr value\n    _ <- call (Vector.vectorPush vec) [bucketPtr, newEntryPtr]\n    ret value\n\n-- NOTE: this is a unsafe lookup, assumes element is in there!\n-- NOTE: the index 0xFFFFFFFF is assumed to mean: not found.\n-- This should be a safe assumption as long as there are less keys than this in the hashmap\nmkHashMapLookup :: Operand -> ModuleCodegen Operand\nmkHashMapLookup hashFn = do\n  (hmTy, symbolTy) <- asks (tyHashMap . types &&& Symbol.tySymbol . symbolCodegen)\n  let args = [(ptr hmTy, \"hashmap\"), (ptr symbolTy, \"symbol\")]\n  function \"eclair_hashmap_lookup\" args i32 $ \\[hm, symbolPtr] -> do\n    bucketPtr <- bucketForHash hashFn hm symbolPtr\n    loopEntriesInBucket symbolPtr bucketPtr $\n      ret <=< deref valueOf\n\n    ret $ int32 0xFFFFFFFF\n\nmkHashMapContains :: Operand -> ModuleCodegen Operand\nmkHashMapContains hashFn = do\n  (hmTy, symbolTy) <- asks (tyHashMap . types &&& Symbol.tySymbol . symbolCodegen)\n  let args = [(ptr hmTy, \"hashmap\"), (ptr symbolTy, \"symbol\")]\n  function \"eclair_hashmap_contains\" args i1 $ \\[hm, symbolPtr] -> do\n    bucketPtr <- bucketForHash hashFn hm symbolPtr\n    loopEntriesInBucket symbolPtr bucketPtr $\n      const $ ret (bit 1)\n\n    ret $ bit 0\n\nbucketForHash :: Operand -> Operand -> Operand -> IRCodegen Operand\nbucketForHash hashFn hm symbolPtr = do\n  h <- call hashFn [symbolPtr]\n  idx <- modulo h capacity\n  addr (bucketAt idx) hm\n\nloopEntriesInBucket :: Operand -> Operand -> (Operand -> IRCodegen a) -> IRCodegen ()\nloopEntriesInBucket symbolPtr bucketPtr instrsForMatch = do\n  (vec, symbol) <- asks (vectorCodegen &&& symbolCodegen)\n\n  entryCount <- call (Vector.vectorSize vec) [bucketPtr]\n\n  loopFor (int32 0) (`ult` entryCount) (add (int32 1)) $ \\i -> do\n    entryPtr <- call (Vector.vectorGetValue vec) [bucketPtr, i]\n    entrySymbolPtr <- addr symbolOf entryPtr\n\n    isMatch <- call (Symbol.symbolIsEqual symbol) [entrySymbolPtr, symbolPtr]\n    if' isMatch $\n      instrsForMatch entryPtr\n\n-- Helpers\n\n-- NOTE: this assumes capacity is a power of 2!\n-- TODO: add proper modulo instruction to llvm-codegen\nmodulo :: Operand -> Word32 -> IRCodegen Operand\nmodulo value divisor =\n  value `and` int32 (toInteger divisor - 1)\n\nloopBuckets :: Operand -> (Operand -> IRCodegen a) -> IRCodegen ()\nloopBuckets hm f = do\n  let currentCapacity = int32 $ toInteger capacity\n  loopFor (int32 0) (`ult` currentCapacity) (add (int32 1)) $ \\i -> do\n    bucketPtr <- addr (bucketAt i) hm\n    f bucketPtr\n\nsymbolType :: ModuleCodegen Type\nsymbolType =\n  asks (Symbol.tySymbol . symbolCodegen)\n\ndata Index\n  = HashMapIdx\n  | BucketIdx  -- A vector inside the hashmap\n  | EntryIdx\n  | SymbolIdx\n  | ValueIdx\n\nbucketAt :: Operand -> Path 'HashMapIdx 'BucketIdx\nbucketAt idx = mkPath [int32 0, idx]\n\nsymbolOf :: Path 'EntryIdx 'SymbolIdx\nsymbolOf = mkPath [int32 0]\n\nvalueOf :: Path 'EntryIdx 'ValueIdx\nvalueOf = mkPath [int32 1]\n"
  },
  {
    "path": "lib/Eclair/LLVM/Metadata.hs",
    "content": "module Eclair.LLVM.Metadata\n  ( Metadata(..)\n  , mkMeta\n  , getIndex\n  , getNumColumns\n  ) where\n\nimport Eclair.RA.IndexSelection (Index(..))\nimport Eclair.TypeSystem\nimport Eclair.LLVM.Hash\nimport qualified Eclair.LLVM.BTree as BTree\nimport Prettyprinter\n\nnewtype Metadata\n  = BTree BTree.Meta\n  deriving (Eq, Ord, Show)\n  deriving ToHash via BTree.Meta\n  -- TODO: support other datastructures (Trie, ...)\n\ninstance Pretty Metadata where\n  pretty (BTree meta) = \"btree\" <> parens (pretty meta)\n\nmkMeta :: Index -> [Type] -> Metadata\nmkMeta (Index columns) ts =\n  -- TODO: choose datastructure based on index/types\n  BTree $ BTree.Meta\n    { BTree.numColumns = length ts\n    , BTree.index = columns\n    , BTree.blockSize = 256\n    , BTree.searchType = BTree.Linear\n    }\n\ngetIndex :: Metadata -> Index\ngetIndex = \\case\n  BTree meta ->\n    Index $ BTree.index meta\n\ngetNumColumns :: Metadata -> Int\ngetNumColumns = \\case\n  BTree meta ->\n    BTree.numColumns meta\n"
  },
  {
    "path": "lib/Eclair/LLVM/Symbol.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Eclair.LLVM.Symbol\n  ( Symbol(..)\n  , codegen\n  , sizeOf\n  , dataOf\n  ) where\n\nimport Prelude hiding (Symbol, void)\nimport Eclair.LLVM.Codegen\nimport Eclair.LLVM.Externals\n\n\ndata Symbol\n  = Symbol\n  { tySymbol :: Type  -- A symbol (string). Assumes UTF-8 encoding.\n  , symbolInit :: Operand\n  , symbolDestroy :: Operand\n  , symbolIsEqual :: Operand\n  }\n\ndata CGState\n  = CGState\n  { externals :: Externals\n  , tySym :: Type\n  }\n\ntype ModuleCodegen = ReaderT CGState ModuleBuilder\n\ncodegen :: Externals -> ModuleBuilder Symbol\ncodegen exts = do\n  symbolTy <- generateTypes\n  runReaderT generateFunctions $ CGState exts symbolTy\n\ngenerateTypes :: ModuleBuilder Type\ngenerateTypes =\n  -- For now, only up to 4GB of strings are supported.\n  -- TODO consider strings with i8 and i16 as size also\n  typedef \"symbol_t\" On [i32, ptr i8]\n\ngenerateFunctions :: ModuleCodegen Symbol\ngenerateFunctions = do\n  symbolTy <- asks tySym\n  symInit <- mkSymbolInit\n  symDestroy <- mkSymbolDestroy\n  symIsEqual <- mkSymbolIsEqual\n  pure $ Symbol\n    { tySymbol = symbolTy\n    , symbolInit = symInit\n    , symbolDestroy = symDestroy\n    , symbolIsEqual = symIsEqual\n    }\n\n-- NOTE: this copies data to the new struct, and assumes memory has already been\n-- allocated for the symbol. Don't pass a const char* / ptr i8 that point to\n-- static memory because it will be freed during cleanup.\nmkSymbolInit :: ModuleCodegen Operand\nmkSymbolInit = do\n  symbolTy <- asks tySym\n  let args = [(ptr symbolTy, \"symbol\"), (i32, \"size\"), (ptr i8, \"data\")]\n  function \"eclair_symbol_init\" args void $ \\[symbol, size, utf8Data] -> do\n    -- assert(symbol && \"symbol cannot be NULL!\");\n    -- assert(utf8Data && \"data cannot be NULL!\");\n    assign sizeOf symbol size\n    assign dataOf symbol utf8Data\n\n-- NOTE: this only destroys the memory this symbol is pointing to.\nmkSymbolDestroy :: ModuleCodegen Operand\nmkSymbolDestroy = do\n  (symbolTy, freeFn) <- asks (tySym &&& extFree . externals)\n  let args = [(ptr symbolTy, \"symbol\")]\n  function \"eclair_symbol_destroy\" args void $ \\[symbol] -> do\n    -- assert(symbol && \"symbol cannot be NULL!\");\n    dataPtr <- deref dataOf symbol\n    call freeFn [dataPtr]\n\nmkSymbolIsEqual :: ModuleCodegen Operand\nmkSymbolIsEqual = do\n  (symbolTy, memcmpFn) <- asks (tySym &&& extMemcmp . externals)\n  let args = [(ptr symbolTy, \"symbol1\"), (ptr symbolTy, \"symbol2\")]\n  function \"eclair_symbol_is_equal\" args i1 $ \\[symbol1, symbol2] -> do\n    -- assert(symbol1 && \"symbol1 cannot be NULL!\");\n    -- assert(symbol2 && \"symbol2 cannot be NULL!\");\n    size1 <- deref sizeOf symbol1\n    size2 <- deref sizeOf symbol2\n    isNotEqualSize <- size1 `ne` size2\n    if' isNotEqualSize $\n      ret $ bit 0\n\n    data1 <- deref dataOf symbol1\n    data2 <- deref dataOf symbol2\n    size1' <- zext size1 i64\n    result <- call memcmpFn [data1, data2, size1']\n    ret =<< result `eq` bit 0\n\ndata Index\n  = SymbolIdx\n  | SizeIdx\n  | DataIdx\n\nsizeOf :: Path 'SymbolIdx 'SizeIdx\nsizeOf = mkPath [int32 0]\n\ndataOf :: Path 'SymbolIdx 'DataIdx\ndataOf = mkPath [int32 1]\n"
  },
  {
    "path": "lib/Eclair/LLVM/SymbolTable.hs",
    "content": "{-# OPTIONS_GHC -Wno-unused-top-binds #-}\n{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.SymbolTable\n  ( SymbolTable(..)\n  , codegen\n  ) where\n\nimport Prelude hiding (void)\nimport LLVM.Codegen\nimport qualified Eclair.LLVM.Vector as Vector\nimport qualified Eclair.LLVM.HashMap as HashMap\n\ndata SymbolTable\n  = SymbolTable\n  { tySymbolTable :: Type\n  , symbolTableInit :: Operand\n  , symbolTableDestroy :: Operand\n  , symbolTableFindOrInsert :: Operand\n  , symbolTableContainsIndex :: Operand\n  , symbolTableContainsSymbol :: Operand\n  , symbolTableLookupIndex :: Operand\n  , symbolTableLookupSymbol :: Operand\n  }\n\ndata CGState\n  = CGState\n  { symbolTableTy :: Type\n  , symbolTy :: Type\n  , vectorCodegen :: Vector.Vector\n  , hashMapCodegen :: HashMap.HashMap\n  }\n\ntype ModuleCodegen = ReaderT CGState ModuleBuilder\n\n\ncodegen :: Type -> Vector.Vector -> HashMap.HashMap -> ModuleBuilder SymbolTable\ncodegen symbolTy' vec hm = do\n  let vecTy = Vector.tyVector $ Vector.vectorTypes vec\n      hmTy = HashMap.tyHashMap $ HashMap.hashMapTypes hm\n  ty <- typedef \"symbol_table\" Off [ vecTy  -- maps indexes (i32) to symbols\n                                   , hmTy   -- maps symbols to indexes\n                                   ]\n  runReaderT generateFunctions $ CGState ty symbolTy' vec hm\n\ngenerateFunctions :: ModuleCodegen SymbolTable\ngenerateFunctions = do\n  ty <- asks symbolTableTy\n  stInit <- mkSymbolTableInit\n  stDestroy <- mkSymbolTableDestroy\n  stFindOrInsert <- mkSymbolTableFindOrInsert\n  stContainsIndex <- mkSymbolTableContainsIndex\n  stContainsSymbol <- mkSymbolTableContainsSymbol\n  stLookupIndex <- mkSymbolTableLookupIndex\n  stLookupSymbol <- mkSymbolTableLookupSymbol\n  pure $ SymbolTable\n    { tySymbolTable = ty\n    , symbolTableInit = stInit\n    , symbolTableDestroy = stDestroy\n    , symbolTableFindOrInsert = stFindOrInsert\n    , symbolTableContainsIndex = stContainsIndex\n    , symbolTableContainsSymbol = stContainsSymbol\n    , symbolTableLookupIndex = stLookupIndex\n    , symbolTableLookupSymbol = stLookupSymbol\n    }\n\nmkSymbolTableInit :: ModuleCodegen Operand\nmkSymbolTableInit = do\n  CGState ty _ vec hm <- ask\n\n  function \"eclair_symbol_table_init\" [(ptr ty, \"table\")] void $ \\[symTab] -> do\n    -- assert(table && \"symbol table cannot be NULL!\");\n    vecPtr <- addr vecOf symTab\n    hmPtr <- addr hashMapOf symTab\n\n    _ <- call (Vector.vectorInit vec) [vecPtr]\n    _ <- call (HashMap.hashMapInit hm) [hmPtr]\n    pass\n\nmkSymbolTableDestroy :: ModuleCodegen Operand\nmkSymbolTableDestroy = do\n  CGState ty _ vec hm <- ask\n\n  function \"eclair_symbol_table_destroy\" [(ptr ty, \"table\")] void $ \\[symTab] -> do\n    -- assert(table && \"symbol table cannot be NULL!\");\n    vecPtr <- addr vecOf symTab\n    hmPtr <- addr hashMapOf symTab\n\n    _ <- call (Vector.vectorDestroy vec) [vecPtr]\n    _ <- call (HashMap.hashMapDestroy hm) [hmPtr]\n    pass\n\nmkSymbolTableFindOrInsert :: ModuleCodegen Operand\nmkSymbolTableFindOrInsert = do\n  CGState ty symbolTy' vec hm <- ask\n  let args = [(ptr ty, \"table\"), (ptr symbolTy', \"symbol\")]\n\n  function \"eclair_symbol_table_find_or_insert\" args i32 $ \\[symTabPtr, symbolPtr] -> do\n    -- assert(table && \"symbol table cannot be NULL!\");\n    vecPtr <- addr vecOf symTabPtr\n    hmPtr <- addr hashMapOf symTabPtr\n\n    count <- call (Vector.vectorSize vec) [vecPtr]\n    value <- call (HashMap.hashMapGetOrPutValue hm) [hmPtr, symbolPtr, count]\n\n    isInsertOfNewValue <- count `eq` value\n    if' isInsertOfNewValue $ do\n      -- New value is being inserted, so we need to update mapping in other direction.\n      call (Vector.vectorPush vec) [vecPtr, symbolPtr]\n\n    ret value\n\nmkSymbolTableContainsIndex :: ModuleCodegen Operand\nmkSymbolTableContainsIndex = do\n  CGState ty _ vec _ <- ask\n  let args = [(ptr ty, \"table\"), (i32, \"index\")]\n\n  function \"eclair_symbol_table_contains_index\" args i1 $ \\[symTabPtr, idx] -> do\n    -- assert(table && \"symbol table cannot be NULL!\");\n    vecPtr <- addr vecOf symTabPtr\n\n    size <- call (Vector.vectorSize vec) [vecPtr]\n    ret =<< idx `ult` size\n\nmkSymbolTableContainsSymbol :: ModuleCodegen Operand\nmkSymbolTableContainsSymbol = do\n  CGState ty symbolTy' _ hm <- ask\n  let args = [(ptr ty, \"table\"), (ptr symbolTy', \"symbol\")]\n\n  function \"eclair_symbol_table_contains_symbol\" args i1 $ \\[symTabPtr, symbolPtr] -> do\n    hmPtr <- addr hashMapOf symTabPtr\n    ret =<< call (HashMap.hashMapContains hm) [hmPtr, symbolPtr]\n\nmkSymbolTableLookupIndex :: ModuleCodegen Operand\nmkSymbolTableLookupIndex = do\n  CGState ty symbolTy' _ hm <- ask\n  let args = [(ptr ty, \"table\"), (ptr symbolTy', \"symbol\")]\n\n  function \"eclair_symbol_table_lookup_index\" args i32 $ \\[symTabPtr, symbolPtr] -> do\n    hmPtr <- addr hashMapOf symTabPtr\n    ret =<< call (HashMap.hashMapLookup hm) [hmPtr, symbolPtr]\n\nmkSymbolTableLookupSymbol :: ModuleCodegen Operand\nmkSymbolTableLookupSymbol = do\n  CGState ty symbolTy' vec _ <- ask\n  let args = [(ptr ty, \"table\"), (i32, \"index\")]\n\n  function \"eclair_symbol_table_lookup_symbol\" args (ptr symbolTy') $ \\[symTabPtr, idx] -> do\n    -- assert(symbol_table_contains_index(table, index));\n    vecPtr <- addr vecOf symTabPtr\n    ret =<< call (Vector.vectorGetValue vec) [vecPtr, idx]\n\n-- Helpers\n\ndata Index\n  = SymbolTableIdx\n  | VecIdx\n  | HashMapIdx\n\nvecOf :: Path 'SymbolTableIdx 'VecIdx\nvecOf = mkPath [int32 0]\n\nhashMapOf :: Path 'SymbolTableIdx 'HashMapIdx\nhashMapOf = mkPath [int32 1]\n"
  },
  {
    "path": "lib/Eclair/LLVM/Table.hs",
    "content": "module Eclair.LLVM.Table\n  ( Table(..)\n  , IteratorParams(..)\n  ) where\n\nimport Eclair.LLVM.Codegen (Operand, Type, Template)\n\n-- A data type used as template params for 'fnInsertRange' defined below.\ndata IteratorParams\n  = IteratorParams\n  { ipIterCurrent :: Operand\n  , ipIterNext :: Operand\n  , ipIterIsEqual :: Operand\n  , ipTypeIter :: Type\n  }\n\n-- A data type representing all functionality of a Datalog table / container.\n-- This is similar to a vtable in C++, except here everything is guaranteed to be inlined\n-- because of specialization. Each of the operands refers to a different LLVM function.\n-- The types are also \"exported\" because they are used in other parts of the code.\ndata Table\n  = Table\n  { fnInit :: Operand\n  , fnInitEmpty :: Operand\n  , fnDestroy :: Operand\n  , fnPurge :: Operand\n  , fnSwap :: Operand\n  , fnBegin :: Operand\n  , fnEnd :: Operand\n  , fnIsEmpty :: Operand\n  , fnSize :: Operand\n  , fnLowerBound :: Operand\n  , fnUpperBound :: Operand\n  , fnContains :: Operand\n  , fnInsert :: Operand\n  , fnInsertRangeTemplate :: Template IteratorParams Operand\n  , fnIterIsEqual :: Operand\n  , fnIterCurrent :: Operand\n  , fnIterNext :: Operand\n  , typeObj :: Type\n  , typeIter :: Type\n  , typeValue :: Type\n  }\n"
  },
  {
    "path": "lib/Eclair/LLVM/Template.hs",
    "content": "{-# LANGUAGE UndecidableInstances, FunctionalDependencies #-}\n\nmodule Eclair.LLVM.Template\n  ( TemplateT\n  , Template\n  , HasSuffix(..)\n  , MonadTemplate(..)\n  , Suffix\n  , cmapParams\n  , instantiate\n  , partialInstantiate\n  , function\n  , typedef\n  ) where\n\n\nimport Control.Monad.Morph\nimport LLVM.Codegen hiding (function, typedef)\nimport qualified LLVM.Codegen as CG\nimport qualified Control.Monad.State.Lazy as LazyState\nimport qualified Control.Monad.State.Strict as StrictState\nimport qualified Control.Monad.RWS.Lazy as LazyRWS\nimport qualified Control.Monad.RWS.Strict as StrictRWS\nimport Eclair.LLVM.Config\n\n\ntype Suffix = Text\n\n-- | A MTL-like monad transformer that allows generating code in a way similar to C++ templates.\n-- Instead of complicated machinery in the compiler, this transformer just adds a suffix to all generated functions and types.\n-- It is up to the programmer to make sure all provided suffixes to one template are unique!\n-- The type variable 'p' is short for \"template parameters\" and can be used to tweak (specialize) the code generation.\n-- The type variable 'm' allows running this stack in a pure context, or in a stack on top of IO.\nnewtype TemplateT p m a\n  = TemplateT\n  { unTemplateT :: ReaderT (Suffix, p) (ModuleBuilderT m) a\n  } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadError e, MonadState s, MonadModuleBuilder)\n  via ReaderT (Suffix, p) (ModuleBuilderT m)\n\ntype Template p = TemplateT p Identity\n\ninstance MFunctor (TemplateT p) where\n  hoist nat = TemplateT . hoist (hoist nat) . unTemplateT\n\ninstance MonadReader r m => MonadReader r (TemplateT p m) where\n  ask = lift ask\n  local f (TemplateT m) =\n    TemplateT $ hoist (local f) m\n\nclass HasSuffix m where\n  getSuffix :: m Suffix\n\ninstance Monad m => HasSuffix (TemplateT p m) where\n  getSuffix = TemplateT $ asks ((\"_\" <>) . fst)\n-- The following instance makes 'function' behave the same as in llvm-codegen\ninstance Monad m => HasSuffix (ModuleBuilderT m) where\n  getSuffix = pure mempty\n-- This allows getting the suffix inside a function body with llvm-codegen\ninstance (Monad m, HasSuffix m) => HasSuffix (IRBuilderT m) where\n  getSuffix = lift getSuffix\n-- MTL boilerplate:\ninstance (Monad m, HasSuffix m) => HasSuffix (ReaderT r m) where\n  getSuffix = lift getSuffix\ninstance (Monad m, HasSuffix m, Monoid w) => HasSuffix (WriterT w m) where\n  getSuffix = lift getSuffix\ninstance (Monad m, HasSuffix m) => HasSuffix (LazyState.StateT w m) where\n  getSuffix = lift getSuffix\ninstance (Monad m, HasSuffix m) => HasSuffix (StrictState.StateT w m) where\n  getSuffix = lift getSuffix\ninstance (Monad m, HasSuffix m, Monoid w) => HasSuffix (LazyRWS.RWST r w s m) where\n  getSuffix = lift getSuffix\ninstance (Monad m, HasSuffix m, Monoid w) => HasSuffix (StrictRWS.RWST r w s m) where\n  getSuffix = lift getSuffix\ninstance (Monad m, HasSuffix m) => HasSuffix (ExceptT e m) where\n  getSuffix = lift getSuffix\ninstance (Monad m, HasSuffix m) => HasSuffix (ConfigT m) where\n  getSuffix = lift getSuffix\n\nclass MonadTemplate p m | m -> p where\n  getParams :: m p\n\ninstance Monad m => MonadTemplate p (TemplateT p m) where\n  getParams = TemplateT $ asks snd\n\ninstance (Monad m, MonadTemplate p m) => MonadTemplate p (ReaderT r m) where\n  getParams = lift getParams\ninstance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (WriterT w m) where\n  getParams = lift getParams\ninstance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (LazyState.StateT w m) where\n  getParams = lift getParams\ninstance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (StrictState.StateT w m) where\n  getParams = lift getParams\ninstance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (LazyRWS.RWST r w s m) where\n  getParams = lift getParams\ninstance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (StrictRWS.RWST r w s m) where\n  getParams = lift getParams\ninstance (Monad m, MonadTemplate p m) => MonadTemplate p (ExceptT e m) where\n  getParams = lift getParams\n-- This allows getting the params inside a function body with llvm-codegen\ninstance (Monad m, MonadTemplate p m) => MonadTemplate p (IRBuilderT m) where\n  getParams = lift getParams\ninstance (Monad m, MonadTemplate p m) => MonadTemplate p (ConfigT m) where\n  getParams = lift getParams\n\ninstance MonadTrans (TemplateT p) where\n  lift m =\n    TemplateT $ ReaderT $ const $ lift m\n\n-- \"contramap\" over the template params.\n-- Useful if you only need access to part of the template data\n-- and/or types of the params don't match.\ncmapParams :: (p2 -> p1) -> TemplateT p1 m a -> TemplateT p2 m a\ncmapParams f (TemplateT m) =\n  TemplateT $ flip withReaderT m $ second f\n\n-- This instantiates a template, given a template name suffix and some template parameters.\n-- The result is the underlying ModuleBuilerT which generates specialized code based on the parameters.\ninstantiate :: Suffix -> p -> TemplateT p m a -> ModuleBuilderT m a\ninstantiate suffix p (TemplateT t) =\n  runReaderT t (suffix, p)\n\n-- This instantiates a template and wraps it into another template.\n-- Useful for templated member functions on a templated object.\npartialInstantiate :: Monad m => p1 -> TemplateT p1 m a -> TemplateT p2 m a\npartialInstantiate p t = do\n  suffix <- getSuffix\n  embedIntoTemplate $ instantiate suffix p t\n  where\n    -- This embeds a plain ModuleBuilderT action into a template.\n    -- This action has no access to the actual template params from this point onwards.\n    embedIntoTemplate :: ModuleBuilderT m a -> TemplateT p m a\n    embedIntoTemplate m = TemplateT $ ReaderT $ const m\n\n\n-- The next functions replace the corresponding functions defined in llvm-codegen.\n-- The functions automatically add a suffix if needed, to guarantee unique function names.\n-- In the actual codegen, you will still need to call 'getParams' to get access to the params,\n-- to do the actual specialization based on them.\n\nfunction :: (MonadModuleBuilder m, HasSuffix m)\n         => Name -> [(Type, ParameterName)] -> Type -> ([Operand] -> IRBuilderT m a) -> m Operand\nfunction (unName -> name) args retTy body = do\n  suffix <- getSuffix\n  let nameWithSuffix = Name $ name <> suffix\n  CG.function nameWithSuffix args retTy body\n\ntypedef :: (MonadModuleBuilder m, HasSuffix m)\n        => Name -> Flag Packed -> [Type] -> m Type\ntypedef (unName -> name) packedFlag tys = do\n  suffix <- getSuffix\n  let nameWithSuffix = Name $ name <> suffix\n  CG.typedef nameWithSuffix packedFlag tys\n"
  },
  {
    "path": "lib/Eclair/LLVM/Vector.hs",
    "content": "{-# OPTIONS_GHC -Wno-unused-top-binds #-}\n{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\n\nmodule Eclair.LLVM.Vector\n  ( Vector(..)\n  , Types(..)\n  , Destructor\n  , codegen\n  , startPtrOf\n  ) where\n\nimport Prelude hiding (EQ, void)\nimport Control.Monad.Morph\nimport Eclair.LLVM.Codegen\nimport Eclair.LLVM.Externals\n\ndata Types\n  = Types\n  { tyIndex :: Type\n  , tyElement :: Type\n  , tyVector :: Type\n  }\n\ndata Vector\n  = Vector\n  { vectorTypes :: Types\n  , vectorInit :: Operand\n  , vectorDestroy :: Operand\n  , vectorPush :: Operand\n  , vectorSize :: Operand\n  , vectorGetValue :: Operand\n  }\n\n-- Type representing what to do when an element of the vector is destroyed.\n-- The operand is a pointer to an element type that needs to be cleaned up.\ntype Destructor = Operand -> IRCodegen ()\n\ndata CGState\n  = CGState\n  { externals :: Externals\n  , types :: Types\n  , sizeElement :: Word64\n  , destructor :: Maybe Destructor\n  }\n\ntype VectorParams = Type\ntype ModuleCodegen = ReaderT CGState (Template VectorParams)\ntype IRCodegen = IRBuilderT ModuleCodegen\n\n\ncodegen :: Externals -> Maybe Destructor -> ConfigT (TemplateT VectorParams IO) Vector\ncodegen exts dtor = do\n  tyElem <- getParams\n  (ctx, td) <- (cfgLLVMContext &&& cfgTargetData) <$> getConfig\n  sizeOfElem <- withLLVMTypeInfo ctx $ llvmSizeOf ctx td tyElem\n\n  hoist (hoist intoIO) $ lift $ do\n    tys <- generateTypes\n    runReaderT generateFunctions $ CGState exts tys sizeOfElem dtor\n  where\n    intoIO = pure . runIdentity\n\ngenerateTypes :: Template VectorParams Types\ngenerateTypes = do\n  tyElem <- getParams\n  tyVec <- typedef \"vector_t\" Off\n            [ ptr tyElem  -- pointer to start of the vector\n            , ptr tyElem  -- pointer to one element past end of the vector\n            , i32         -- capacity: how many elements can fit inside the vector\n            ]\n  pure $ Types\n    { tyIndex = i32\n    , tyElement = tyElem\n    , tyVector = tyVec\n    }\n\ngenerateFunctions :: ModuleCodegen Vector\ngenerateFunctions = do\n  tys <- asks types\n  vInit <- mkVectorInit\n  vDestroy <- mkVectorDestroy\n  vSize <- mkVectorSize\n  vPush <- mkVectorPush vSize\n  vGetValue <- mkVectorGetValue\n  pure $ Vector\n    { vectorTypes = tys\n    , vectorInit = vInit\n    , vectorDestroy = vDestroy\n    , vectorPush = vPush\n    , vectorSize = vSize\n    , vectorGetValue = vGetValue\n    }\n\ninitialCapacity :: Int\ninitialCapacity = 16  -- or 0?\n\ngrowFactor :: Int\ngrowFactor = 2  -- or 1.5? needs rounding then..\n\n-- NOTE: Assumes vector memory already allocated in other code\nmkVectorInit :: ModuleCodegen Operand\nmkVectorInit = do\n  CGState exts tys sizeOfElem _ <- ask\n  let (vecTy, elemTy) = (tyVector &&& tyElement) tys\n      mallocFn = extMalloc exts\n\n  function \"eclair_vector_init\" [(ptr vecTy, \"vec\")] void $ \\[vec] -> do\n    -- assert(vec && \"Vector should not be null\");\n    let numBytes = int32 . toInteger $ sizeOfElem * fromIntegral initialCapacity\n    memoryPtr <- ptrcast elemTy <$> call mallocFn [numBytes]\n    --   assert(memory && \"Failed to allocate memory!\");\n\n    assign startPtrOf vec memoryPtr\n    assign endPtrOf vec memoryPtr\n    assign capacityOf vec (int32 $ fromIntegral initialCapacity)\n\n-- NOTE: Assumes vector memory already allocated in other code\nmkVectorDestroy ::  ModuleCodegen Operand\nmkVectorDestroy = do\n  CGState exts tys _ elemDestructor <- ask\n  let (vecTy, elemTy) = (tyVector &&& tyElement) tys\n      freeFn = extFree exts\n\n  function \"eclair_vector_destroy\" [(ptr vecTy, \"vec\")] void $ \\[vec] -> do\n    -- assert(vec && \"Vector should not be null\");\n    for_ elemDestructor $ \\destructor' -> do\n      iterPtrPtr <- allocate (ptr elemTy) =<< deref startPtrOf vec\n      let hasNext = do\n            iterPtr <- load iterPtrPtr 0\n            endPtr <- deref endPtrOf vec\n            iterPtr `ne` endPtr\n      loopWhile hasNext $ do\n        iterPtr <- load iterPtrPtr 0\n        destructor' iterPtr\n        store iterPtrPtr 0 =<< incrementPtr iterPtr\n\n    startPtr <- ptrcast i8 <$> deref startPtrOf vec\n    call freeFn [startPtr]\n\n-- NOTE: Returns the index at which the element was inserted => no size necessary\n-- NOTE: does not check for uniqueness!\nmkVectorPush :: Operand -> ModuleCodegen Operand\nmkVectorPush vectorSize' = do\n  CGState exts tys sizeElem _ <- ask\n  let (vecTy, elemTy) = (tyVector &&& tyElement) tys\n      mallocFn = extMalloc exts\n      freeFn = extFree exts\n      memcpyFn = extMemcpy exts\n      sizeOfElem = int32 $ toInteger sizeElem\n\n  vectorGrow <- function \"eclair_vector_grow\" [(ptr vecTy, \"vec\")] void $ \\[vec] -> do\n    -- NOTE: size == capacity in this function\n    -- assert(vec && \"Vector should not be null\");\n    currentCapacity <- deref capacityOf vec\n    currentNumBytes <- mul currentCapacity sizeOfElem >>= (`zext` i64)\n\n    newCapacity <- mul currentCapacity (int32 $ toInteger growFactor)\n    newNumBytes <- mul newCapacity sizeOfElem\n    newMemoryPtr <- ptrcast elemTy <$> call mallocFn [newNumBytes]\n    -- assert(new_memory && \"Failed to allocate more memory for vector!\");\n    newMemoryEndPtr <- gep newMemoryPtr [currentCapacity]\n    startPtr <- ptrcast i8 <$> deref startPtrOf vec\n    let newMemoryPtrBytes = ptrcast i8 newMemoryPtr\n    _ <- call memcpyFn [newMemoryPtrBytes, startPtr, currentNumBytes, bit 0]\n    _ <- call freeFn [startPtr]\n\n    assign startPtrOf vec newMemoryPtr\n    assign endPtrOf vec newMemoryEndPtr\n    assign capacityOf vec newCapacity\n\n  function \"eclair_vector_push\" [(ptr vecTy, \"vec\"), (ptr elemTy, \"elem\")] i32 $ \\[vec, elem'] -> do\n    -- assert(vec && \"Vector should not be null\");\n    numElems <- call vectorSize' [vec]\n    capacity <- deref capacityOf vec\n    isFull <- numElems `eq` capacity\n    if' isFull $ do\n      call vectorGrow [vec]\n\n    -- Look up vec->end again, pointers can be invalidated due to potential resize!\n    endPtr <- deref endPtrOf vec\n    store endPtr 0 =<< load elem' 0\n    update endPtrOf vec incrementPtr\n    ret numElems\n\nmkVectorSize :: ModuleCodegen Operand\nmkVectorSize = do\n  CGState _ tys sizeElem _ <- ask\n  let vecTy = tyVector tys\n      sizeOfElem = int32 $ toInteger sizeElem\n\n  function \"eclair_vector_size\" [(ptr vecTy, \"vec\")] i32 $ \\[vec] -> do\n    -- assert(vec && \"Vector should not be null\");\n    startPtr <- deref startPtrOf vec\n    endPtr <- deref endPtrOf vec\n    byteDiff <- pointerDiff i32 endPtr startPtr\n    ret =<< udiv byteDiff sizeOfElem\n\nmkVectorGetValue :: ModuleCodegen Operand\nmkVectorGetValue = do\n  (vecTy, elemTy) <- asks ((tyVector &&& tyElement) . types)\n  function \"eclair_vector_get_value\" [(ptr vecTy, \"vec\"), (i32, \"idx\")] (ptr elemTy) $ \\[vec, idx] -> do\n    startPtr <- deref startPtrOf vec\n    -- We need a raw gep here, since this is a dynamically allocated pointer that we need to offset.\n    ret =<< gep startPtr [idx]\n\n\n-- Helper functions:\n\nincrementPtr :: Operand -> IRCodegen Operand\nincrementPtr = (`gep` [int32 1])\n\ndata Index\n  = VectorIdx\n  | StartPtrIdx\n  | EndPtrIdx\n  | CapacityIdx\n\nstartPtrOf :: Path 'VectorIdx 'StartPtrIdx\nstartPtrOf = mkPath [int32 0]\n\nendPtrOf :: Path 'VectorIdx 'EndPtrIdx\nendPtrOf = mkPath [int32 1]\n\ncapacityOf :: Path 'VectorIdx 'CapacityIdx\ncapacityOf = mkPath [int32 2]\n"
  },
  {
    "path": "lib/Eclair/LSP/Handlers/Diagnostics.hs",
    "content": "module Eclair.LSP.Handlers.Diagnostics\n  ( diagnosticsHandler\n  , DiagnosticSource(..)\n  , Severity(..)\n  , Diagnostic(..)\n  , DiagnosticsResult(..)\n  ) where\n\nimport Eclair\nimport Eclair.LSP.Monad\nimport Eclair.Common.Location\nimport Eclair.Error\n\ndata DiagnosticSource\n  = Parser\n  | Typesystem\n  | SemanticAnalysis\n  | Transpiler\n  deriving (Eq, Show)\n\ndata Severity\n  = Error  -- (for now Eclair only has errors)\n  deriving (Eq, Show)\n\ndata Diagnostic\n  = Diagnostic DiagnosticSource SourceSpan Severity Text\n  deriving (Eq, Show)\n\ndata DiagnosticsResult\n  = DiagnosticsOk [Diagnostic]\n  | DiagnosticsError FilePath (Maybe SourcePos) Text\n  deriving (Eq, Show)\n\ndiagnosticsHandler :: FilePath -> LspM DiagnosticsResult\ndiagnosticsHandler path = do\n  params <- getParams\n  mFileContents <- lift $ vfsLookupFile path\n  case mFileContents of\n    Nothing ->\n      pure $ DiagnosticsError path Nothing \"File not found in VFS!\"\n    Just _fileContents -> do\n      errs <- liftLSP $ emitDiagnostics params path\n      diagnostics <- mconcat <$> traverse errorToDiagnostics errs\n      pure $ DiagnosticsOk diagnostics\n  where\n    errorToDiagnostics :: EclairError -> LspM [Diagnostic]\n    errorToDiagnostics err = do\n      vfsVar <- lift getVfsVar\n      vfs <- liftIO $ readMVar vfsVar\n      let readSourceFile = unsafeReadFromVFS vfs\n          source = diagnosticSource err\n      issues <- liftLSP $ errorToIssues readSourceFile err\n      traverse (toDiagnostic source) issues\n\n    toDiagnostic source issue = do\n      let msg = renderIssueMessage issue\n          srcSpan = locationToSourceSpan $ issueLocation issue\n      pure $ Diagnostic source srcSpan Error msg\n\n    diagnosticSource :: EclairError -> DiagnosticSource\n    diagnosticSource = \\case\n      ParseErr {} -> Parser\n      TypeErr {} -> Typesystem\n      SemanticErr {} -> SemanticAnalysis\n      ConversionErr {} -> Transpiler\n"
  },
  {
    "path": "lib/Eclair/LSP/Handlers/DocumentHighlight.hs",
    "content": "module Eclair.LSP.Handlers.DocumentHighlight\n  ( documentHighlightHandler\n  , DocHLResult(..)\n  ) where\n\nimport Eclair\nimport Eclair.AST.IR\nimport Eclair.Common.Location\nimport Eclair.LSP.Monad\n\ndata DocHLResult\n  = DocHLOk [SourceSpan]\n  | DocHLError FilePath SourcePos Text\n  deriving (Eq, Show)\n\ndocumentHighlightHandler :: FilePath -> SourcePos -> LspM DocHLResult\ndocumentHighlightHandler path srcPos = do\n  mFileContents <- lift $ vfsLookupFile path\n  case mFileContents of\n    Nothing ->\n      pure $ DocHLError path srcPos \"File not found in VFS!\"\n    Just fileContents ->\n      case posToOffset srcPos fileContents of\n        Left err ->\n          pure $ DocHLError path srcPos err\n        Right fileOffset -> do\n          params <- getParams\n          parseResult <- liftLSP $ runExceptT $ do\n            (ast, spanMap) <- ExceptT (parse params path)\n            let mNodeId = lookupNodeId spanMap fileOffset\n            (ast, spanMap,) <$> liftEither (maybeToRight [] mNodeId)\n          processParseResult fileContents parseResult\n  where\n    processParseResult fileContents = \\case\n      Left _errs ->\n        pure $ DocHLError path srcPos \"Failed to get highlight information!\"\n      Right (ast, spanMap, nodeId) -> do\n        let refs = findReferences ast nodeId\n            highlights = getHighlights path fileContents spanMap refs\n        pure $ DocHLOk highlights\n\n    getHighlights file fileContent spanMap =\n      map (\\refNodeId ->\n        let span' = lookupSpan spanMap refNodeId\n            sourceSpan = spanToSourceSpan file fileContent span'\n        in sourceSpan)\n\n-- TODO make this a build task\n-- TODO implement for concepts besides variables\nfindReferences :: AST -> NodeId -> [NodeId]\nfindReferences ast nodeId =\n  fst <$> zygo getVarId getRefs ast\n  where\n    getVarId = \\case\n      PWildcardF {} ->\n        -- Wildcard matches with nothing.\n        mempty\n      VarF varNodeId var | nodeId == varNodeId ->\n        First (Just var)\n      astf ->\n        fold astf\n\n    getRefs = \\case\n      ModuleF _ decls ->\n        foldMap snd $ filter (isJust . getFirst . fst) decls\n      RuleF _ _ args clauses -> do\n        let subtrees = args <> clauses\n         in case getFirst $ foldMap fst subtrees of\n              Nothing -> mempty\n              Just var ->\n                filter ((== var) . snd) $ foldMap snd subtrees\n      VarF varNodeId var ->\n        [(varNodeId, var)]\n      astf ->\n        foldMap snd astf\n"
  },
  {
    "path": "lib/Eclair/LSP/Handlers/Hover.hs",
    "content": "module Eclair.LSP.Handlers.Hover\n  ( hoverHandler\n  , HoverResult(..)\n  ) where\n\nimport Eclair\nimport Eclair.Error\nimport Eclair.TypeSystem hiding (typeCheck)\nimport Eclair.Common.Location\nimport Eclair.LSP.Monad\nimport qualified Data.Map as M\n\ndata HoverResult\n  = HoverOk SourceSpan Type\n  | HoverError FilePath SourcePos Text\n  deriving (Eq, Show)\n\nhoverHandler :: FilePath -> SourcePos -> LspM HoverResult\nhoverHandler path srcPos = do\n  mFileContents <- lift $ vfsLookupFile path\n  case mFileContents of\n    Nothing ->\n      pure $ HoverError path srcPos \"File not found in VFS!\"\n    Just fileContents ->\n      case posToOffset srcPos fileContents of\n        Left err ->\n          pure $ HoverError path srcPos err\n        Right fileOffset -> do\n          processHoverOffset fileContents fileOffset\n  where\n    processHoverOffset fileContents fileOffset = do\n      params <- getParams\n      tcResult <- runExceptT $ do\n        (ast, spanMap) <- ExceptT (liftLSP $ parse params path)\n        (ast, spanMap,) <$> ExceptT (liftLSP $ typeCheck params path)\n      case tcResult of\n        Left errs ->\n          processErrors errs\n        Right (_, spanMap, typeInfo) ->\n          processTypeInfo fileContents fileOffset spanMap typeInfo\n\n    processTypeInfo fileContents fileOffset spanMap typeInfo = do\n      let maybeResult = do\n            nodeId <- lookupNodeId spanMap fileOffset\n            ty <- M.lookup nodeId (resolvedTypes typeInfo)\n            pure (nodeId, ty)\n      case maybeResult of\n        Nothing ->\n          pure $ HoverError path srcPos \"No type information for this position!\"\n        Just (nodeId, ty) -> do\n          let span' = lookupSpan spanMap nodeId\n              srcSpan = spanToSourceSpan path fileContents span'\n          pure $ HoverOk srcSpan ty\n\n    processErrors errs = do\n      vfsVar <- lift getVfsVar\n      vfs <- liftIO $ readMVar vfsVar\n      issues <- traverse (liftLSP . errorToIssues (unsafeReadFromVFS vfs)) errs\n      case findIssueAtPosition issues of\n        Nothing ->\n          pure $ HoverError path srcPos \"File contains errors!\"\n        Just issue -> do\n          let msg = renderIssueMessage issue\n          pure $ HoverError path srcPos msg\n\n    findIssueAtPosition issues =\n      flip find (concat issues) $ \\i ->\n        let loc = issueLocation i\n            startPos' = posToSourcePos $ locationStart loc\n            endPos' = posToSourcePos $ locationEnd loc\n         in startPos' <= srcPos && srcPos <= endPos'\n"
  },
  {
    "path": "lib/Eclair/LSP/Handlers.hs",
    "content": "module Eclair.LSP.Handlers\n  ( module Eclair.LSP.Handlers.Hover\n  , module Eclair.LSP.Handlers.Diagnostics\n  , module Eclair.LSP.Handlers.DocumentHighlight\n  ) where\n\nimport Eclair.LSP.Handlers.Hover\nimport Eclair.LSP.Handlers.Diagnostics\nimport Eclair.LSP.Handlers.DocumentHighlight\n"
  },
  {
    "path": "lib/Eclair/LSP/JSON.hs",
    "content": "module Eclair.LSP.JSON\n  ( responseToJSON\n  , diagnosticToJSON\n  , diagnosticSourceToJSON\n  , severityToJSON\n  , srcSpanToJSON\n  , srcPosToJSON\n  , typeToJSON\n  , commandDecoder\n  , hoverDecoder\n  , referencesDecoder\n  , diagnosticsDecoder\n  , updateVfsDecoder\n  , srcPosDecoder\n  ) where\n\nimport qualified Eclair.JSON as J\nimport qualified Data.Hermes as H\nimport Eclair.Common.Pretty\nimport Eclair.TypeSystem hiding (typeCheck)\nimport Eclair.LSP.Handlers\nimport Eclair.LSP.Types\nimport Eclair.Common.Location\n\ncommandDecoder :: H.Decoder Command\ncommandDecoder = H.object $ do\n  cmdType <- H.atKey \"type\" H.text\n  let mDecoder = case cmdType of\n        \"hover\" -> Just hoverDecoder\n        \"document-highlight\" -> Just referencesDecoder\n        \"diagnostics\" -> Just diagnosticsDecoder\n        \"update-vfs\" -> Just updateVfsDecoder\n        \"shutdown\" -> Nothing\n        _ -> Nothing -- TODO return exception?\n  case mDecoder of\n    Nothing -> pure Shutdown\n    Just decoder -> do\n      H.atKey \"command\" decoder\n\nhoverDecoder :: H.Decoder Command\nhoverDecoder = H.object $\n  Hover\n    <$> H.atKey \"file\" H.string\n    <*> H.atKey \"position\" srcPosDecoder\n\nreferencesDecoder :: H.Decoder Command\nreferencesDecoder = H.object $\n  DocumentHighlight\n    <$> H.atKey \"file\" H.string\n    <*> H.atKey \"position\" srcPosDecoder\n\ndiagnosticsDecoder :: H.Decoder Command\ndiagnosticsDecoder = H.object $\n  Diagnostics\n    <$> H.atKey \"file\" H.string\n\nupdateVfsDecoder :: H.Decoder Command\nupdateVfsDecoder = H.object $\n  UpdateVFS\n    <$> H.atKey \"file\" H.string\n    <*> H.atKey \"contents\" H.text\n\nsrcPosDecoder :: H.Decoder SourcePos\nsrcPosDecoder = H.object $\n  SourcePos\n    <$> H.atKey \"line\" H.int\n    <*> H.atKey \"column\" H.int\n\nsuccessResponse :: Text -> J.JSON -> J.JSON\nsuccessResponse responseKey response =\n  J.Object [\n    (\"type\", J.String \"success\"),\n    (responseKey, response)\n  ]\n\nerrorResponse :: J.JSON -> J.JSON\nerrorResponse response =\n  J.Object [\n    (\"type\", J.String \"error\"),\n    (\"error\", response)\n  ]\n\nresponseToJSON :: Response -> J.JSON\nresponseToJSON = \\case\n  HoverResponse (HoverOk srcSpan ty) ->\n    successResponse \"hover\" $ J.Object\n      [ (\"location\", srcSpanToJSON srcSpan)\n      , (\"type\", typeToJSON ty)\n      ]\n  HoverResponse (HoverError path pos err) ->\n    errorResponse $ J.Object\n      [ (\"file\", J.String $ toText path)\n      , (\"position\", srcPosToJSON pos)\n      , (\"message\", J.String err)\n      ]\n  DocumentHighlightResponse (DocHLOk refs) ->\n    successResponse \"highlights\" $ J.Array $ map srcSpanToJSON refs\n  DocumentHighlightResponse (DocHLError path pos err) ->\n    errorResponse $ J.Object\n      [ (\"file\", J.String $ toText path)\n      , (\"position\", srcPosToJSON pos)\n      , (\"message\", J.String err)\n      ]\n  DiagnosticsResponse (DiagnosticsOk diagnostics) ->\n    successResponse \"diagnostics\" $ J.Array $ map diagnosticToJSON diagnostics\n  DiagnosticsResponse (DiagnosticsError path mPos err) ->\n    errorResponse $ J.Object\n      [ (\"file\", J.String $ toText path)\n      , (\"position\", srcPosToJSON $ fromMaybe (SourcePos 0 0) mPos)\n      , (\"message\", J.String err)\n      ]\n  SuccessResponse ->\n    J.Object [(\"success\", J.Boolean True)]\n  ShuttingDown ->\n    J.Object [(\"shutdown\", J.Boolean True)]\n\ndiagnosticToJSON :: Diagnostic -> J.JSON\ndiagnosticToJSON (Diagnostic source srcSpan severity msg) =\n  J.Object\n    [ (\"location\", srcSpanToJSON srcSpan)\n    , (\"source\", diagnosticSourceToJSON source)\n    , (\"severity\", severityToJSON severity)\n    , (\"message\", J.String msg)\n    ]\n\ndiagnosticSourceToJSON :: DiagnosticSource -> J.JSON\ndiagnosticSourceToJSON src =\n  J.String $ show src\n\nseverityToJSON :: Severity -> J.JSON\nseverityToJSON Error =\n  J.String \"error\"\n\nsrcSpanToJSON :: SourceSpan -> J.JSON\nsrcSpanToJSON srcSpan =\n  J.Object\n    [ (\"file\", J.String $ toText path)\n    , (\"start\", J.Object\n        [ (\"line\", J.Number $ sourcePosLine start)\n        , (\"column\", J.Number $ sourcePosColumn start)\n        ]\n      )\n    , (\"end\", J.Object\n        [ (\"line\", J.Number $ sourcePosLine end)\n        , (\"column\", J.Number $ sourcePosColumn end)\n        ]\n      )\n    ]\n  where\n    path = sourceSpanFile srcSpan\n    start = sourceSpanBegin srcSpan\n    end = sourceSpanEnd srcSpan\n\nsrcPosToJSON :: SourcePos -> J.JSON\nsrcPosToJSON pos =\n  J.Object\n    [ (\"line\", J.Number $ sourcePosLine pos)\n    , (\"column\", J.Number $ sourcePosColumn pos)\n    ]\n\ntypeToJSON :: Type -> J.JSON\ntypeToJSON =\n  J.String . printDoc\n"
  },
  {
    "path": "lib/Eclair/LSP/Monad.hs",
    "content": "module Eclair.LSP.Monad\n  ( LspM\n  , runLSP\n  , liftLSP\n  , getParams\n  , module Eclair.LSP.VFS\n  , posToOffset\n  ) where\n\nimport Eclair (Parameters(..))\nimport Eclair.LSP.VFS\nimport Eclair.Common.Location\nimport qualified Text.Megaparsec as P\nimport qualified Text.Megaparsec.Char as P\n\ntype LspM = ReaderT Parameters (VFST IO)\n\nrunLSP :: LspM a -> IO a\nrunLSP m = do\n  runVFST $ runReaderT m placeHolderParams\n  where\n    -- TODO make number of cores configurable via CLI\n    placeHolderParams = Parameters 1 Nothing mempty\n\ngetParams :: LspM Parameters\ngetParams = ask\n\nliftLSP :: IO a -> LspM a\nliftLSP = lift . lift\n\n-- A hack to go from the LSP position to the offset in the file.\n-- TODO check for off by 1 errors!\n-- TODO move to Parser.hs\nposToOffset :: SourcePos -> Text -> Either Text Int\nposToOffset lspPos fileContents = do\n  case P.runParser p \"<lsp>\" fileContents of\n    Left _ ->\n      Left \"Error computing location offset in file!\"\n    Right offset ->\n      pure offset\n  where\n    p :: P.Parsec Void Text Int\n    p = do\n      -- Skip to correct line\n      replicateM_ (fromIntegral $ sourcePosLine lspPos) $ do\n        void $ P.takeWhileP Nothing (/= '\\n')\n        P.char '\\n'\n      -- Skip to correct column\n      void $ P.takeP Nothing (fromIntegral $ sourcePosColumn lspPos)\n      P.getOffset\n"
  },
  {
    "path": "lib/Eclair/LSP/Types.hs",
    "content": "module Eclair.LSP.Types\n  ( Command(..)\n  , Response(..)\n  ) where\n\nimport Eclair.Common.Location\nimport Eclair.LSP.Handlers\n\ndata Command\n  = Hover FilePath SourcePos\n  | DocumentHighlight FilePath SourcePos\n  | Diagnostics FilePath\n  | UpdateVFS FilePath Text\n  | Shutdown\n  deriving (Eq, Show)\n\ndata Response\n  = HoverResponse HoverResult\n  | DocumentHighlightResponse DocHLResult\n  | DiagnosticsResponse DiagnosticsResult\n  | SuccessResponse\n  | ShuttingDown\n"
  },
  {
    "path": "lib/Eclair/LSP/VFS.hs",
    "content": "module Eclair.LSP.VFS\n  ( VFS\n  , VFST\n  , runVFST\n  , getVfsVar\n  , vfsSetFile\n  , vfsLookupFile\n  , unsafeReadFromVFS\n  ) where\n\nimport qualified Data.Map as M\nimport Data.Maybe (fromJust)\n\n-- Virtual file system (some files might not be saved yet to disk!)\ntype VFS = M.Map FilePath Text\n\nnewtype VFST m a = VFST (ReaderT (MVar VFS) m a)\n  deriving ( Functor, Applicative, Monad\n           , MonadReader (MVar VFS), MonadIO)\n  via ReaderT (MVar VFS) m\n\ninstance MonadTrans VFST where\n  lift m =\n    VFST $ lift m\n\nrunVFST :: MonadIO m => VFST m a -> m a\nrunVFST (VFST m) = do\n  vfsVar <- liftIO $ newMVar mempty\n  runReaderT m vfsVar\n\ngetVfsVar :: Monad m => VFST m (MVar VFS)\ngetVfsVar = ask\n\n-- NOTE: Can be used both for adding and updating\nvfsSetFile :: MonadIO m => FilePath -> Text -> VFST m ()\nvfsSetFile path contents = do\n  vfsVar <- getVfsVar\n  liftIO $ modifyMVar_ vfsVar $\n    pure . M.insert path contents\n\nvfsLookupFile :: MonadIO m => FilePath -> VFST m (Maybe Text)\nvfsLookupFile path = do\n  vfsVar <- getVfsVar\n  liftIO $ do\n    vfs <- readMVar vfsVar\n    pure $! M.lookup path vfs\n\n-- Helper function used in a couple of handlers to directly read from VFS\n-- Can be used as a drop-in replacement for \"tryReadFile\" in the compiler\n-- pipeline once the VFS argument is applied.\nunsafeReadFromVFS :: VFS -> FilePath -> IO Text\nunsafeReadFromVFS vfs path =\n  pure . fromJust $ M.lookup path vfs\n"
  },
  {
    "path": "lib/Eclair/LSP.hs",
    "content": "module Eclair.LSP\n  ( lspMain\n  ) where\n\nimport Eclair (Parameters(..))\nimport Eclair.LSP.Handlers\nimport Eclair.LSP.Monad\nimport Eclair.LSP.Types\nimport Eclair.LSP.JSON\nimport qualified Eclair.JSON as J\nimport qualified Data.Hermes as H\nimport qualified Data.Text.IO as TIO\nimport qualified Data.ByteString as BS\nimport qualified Data.Map as M\n\ndata NextStep\n  = Continue\n  | Stop\n\nlspMain :: IO ()\nlspMain = do\n  env <- H.mkHermesEnv Nothing\n  runLSP $ do\n    vfsVar <- lift getVfsVar\n    let readVFS path = do\n          vfs <- readMVar vfsVar\n          pure $! M.lookup path vfs\n\n    -- TODO make numCores configurable via CLI\n    let params = Parameters 1 Nothing readVFS\n    local (const params) $ go env\n  where\n    go env = readCommand env >>= \\case\n      Left _err ->\n        sendResponse ShuttingDown\n      Right command -> do\n        (nextStep, result) <- processCommand command\n        sendResponse result\n        case nextStep of\n          Continue -> go env\n          Stop -> pass\n\nprocessCommand :: Command -> LspM (NextStep, Response)\nprocessCommand = \\case\n  Hover path srcPos -> do\n    result <- hoverHandler path srcPos\n    pure (Continue, HoverResponse result)\n  DocumentHighlight path srcPos -> do\n    hls <- documentHighlightHandler path srcPos\n    pure (Continue, DocumentHighlightResponse hls)\n  Diagnostics path -> do\n    diagnostics <- diagnosticsHandler path\n    pure (Continue, DiagnosticsResponse diagnostics)\n  UpdateVFS path fileContents -> do\n    lift $ vfsSetFile path fileContents\n    pure (Continue, SuccessResponse)\n  Shutdown ->\n    pure (Stop, ShuttingDown)\n\nreadCommand :: H.HermesEnv -> LspM (Either H.HermesException Command)\nreadCommand env = liftLSP $\n  H.parseByteString env commandDecoder <$> BS.hGetLine stdin\n\nsendResponse :: Response -> LspM ()\nsendResponse resp =\n  liftLSP $ do\n    TIO.hPutStrLn stdout txt\n    hFlush stdout\n  where\n    txt = J.encodeJSON json\n    json = responseToJSON resp\n"
  },
  {
    "path": "lib/Eclair/Parser.hs",
    "content": "module Eclair.Parser\n  ( parseFile\n  , parseText\n  , Parser\n  , ParseError\n  , CustomParseErr\n  , ParsingError(..)\n  ) where\n\nimport Data.Char\nimport Eclair.AST.IR\nimport Eclair.Common.Id\nimport Eclair.Common.Location\nimport qualified Data.Text as T\nimport qualified Data.Vector as V\nimport qualified Data.Text.Read as TR\nimport qualified Data.List.NonEmpty as NE\nimport qualified Text.Megaparsec as P\nimport qualified Text.Megaparsec.Internal as P\nimport qualified Text.Megaparsec.Char as P\nimport qualified Text.Megaparsec.Char.Lexer as L\nimport qualified Control.Monad.Combinators.Expr as L\n\ndata CustomParseErr\n  = TooManyInputOptions\n  | TooManyOutputOptions\n  deriving (Eq, Ord, Show)\n\ninstance P.ShowErrorComponent CustomParseErr where\n  showErrorComponent = \\case\n    TooManyInputOptions ->\n      \"More than one option of type 'input' is not allowed.\"\n    TooManyOutputOptions ->\n      \"More than one option of type 'output' is not allowed.\"\n\ntype ParseError = P.ParseErrorBundle Text CustomParseErr\n\ntype ParserState = (Word32, SpanMap)\ntype Parser = P.ParsecT CustomParseErr Text (State ParserState)\n\ndata ParsingError\n  = FileNotFound FilePath\n  | ParsingError ParseError\n  deriving (Show)\n\nparseFile\n  :: (FilePath -> IO (Maybe Text))\n  -> FilePath -> IO (AST, NodeId, SpanMap, Maybe ParsingError)\nparseFile tryReadFile path = do\n  mContents <- tryReadFile path\n  case mContents of\n    Nothing ->\n      pure (emptyModule, NodeId 0, SpanMap path mempty, Just $ FileNotFound path)\n    Just contents ->\n      pure $ parseText path contents\n\nparseText :: FilePath -> Text -> (AST, NodeId, SpanMap, Maybe ParsingError)\nparseText path text =\n  f $ runState (runParserT path text astParser) (0, SpanMap path mempty)\n  where\n    f ((mAst, mErr), s) =\n      (fromMaybe emptyModule mAst, NodeId $ fst s, snd s, mErr)\n\nemptyModule :: AST\nemptyModule = Module (NodeId 0) mempty\n\n-- Uses the internals of Megaparsec to try and return a parse result along\n-- with possible parse errors.\nrunParserT :: FilePath -> Text -> Parser a -> State ParserState (Maybe a, Maybe ParsingError)\nrunParserT path text p = do\n  let s = initialState path text\n  (P.Reply s' _ result) <- P.runParsecT p s\n  let toBundle es =\n        P.ParseErrorBundle\n          { P.bundleErrors = NE.sortWith P.errorOffset es,\n            P.bundlePosState = P.statePosState s\n          }\n  pure $ case result of\n    P.Error fatalError ->\n      (Nothing, Just $ ParsingError $ toBundle $ fatalError :| P.stateParseErrors s')\n    P.OK _ x ->\n      let nonFatalErrs = viaNonEmpty toBundle (P.stateParseErrors s')\n       in (Just x, ParsingError <$> nonFatalErrs)\n  where\n    initialState name s =\n      P.State\n        { P.stateInput = s,\n          P.stateOffset = 0,\n          P.statePosState =\n            P.PosState\n              { P.pstateInput = s,\n                P.pstateOffset = 0,\n                P.pstateSourcePos = P.initialPos name,\n                P.pstateTabWidth = P.defaultTabWidth,\n                P.pstateLinePrefix = \"\"\n              },\n          P.stateParseErrors = []\n        }\n\nfreshNodeId :: Parser NodeId\nfreshNodeId = do\n  nodeId <- gets (NodeId . fst)\n  modify $ first (+1)\n  pure nodeId\n\nwithNodeId :: (NodeId -> Parser a) -> Parser a\nwithNodeId f = do\n  nodeId <- freshNodeId\n  begin <- P.getOffset\n  parsed <- f nodeId\n  end <- P.getOffset\n  modify $ second (insertSpan nodeId (Span begin end))\n  pure parsed\n\nastParser :: Parser AST\nastParser = withNodeId $ \\nodeId -> do\n  whitespace\n  decls <-  withRecovery '.' declParser `P.endBy` whitespace\n  P.eof\n  pure $ Module nodeId $ catMaybes decls\n\ndeclParser :: Parser AST\ndeclParser = do\n  c <- P.lookAhead P.anySingle\n  case c of\n    '@' -> do\n      withNodeId $ \\nodeId ->\n        typedefParser nodeId <|> externParser nodeId\n    _ -> factOrRuleParser\n\nexternParser :: NodeId -> Parser AST\nexternParser nodeId = do\n  void $ lexeme $ P.chunk \"@extern\"\n  name <- lexeme identifier\n  args <- lexeme $ betweenParens $ argParser `P.sepBy1` lexeme comma\n  mRetTy <- optional typeParser\n  void $ P.char '.'\n  pure $ ExternDefinition nodeId name args mRetTy\n\ntypedefParser :: NodeId -> Parser AST\ntypedefParser nodeId = do\n  void $ lexeme $ P.chunk \"@def\"\n  name <- lexeme identifier\n  args <- lexeme $ betweenParens $ argParser `P.sepBy1` lexeme comma\n  attrs <- attributesParser\n  void $ P.char '.'\n  pure $ DeclareType nodeId name args attrs\n  where\n    attributesParser = map (fromMaybe Internal) $ lexeme $ optional $ do\n      options <- some attrParser\n      let (inputs, outputs) = partitionEithers options\n          inputLength = length inputs\n          outputLength = length outputs\n      when (inputLength > 1) $ do\n        P.customFailure TooManyInputOptions\n      when (outputLength > 1) $ do\n        P.customFailure TooManyOutputOptions\n\n      pure $ case (inputLength, outputLength) of\n        (0, 1) -> Output\n        (1, 0) -> Input\n        _      -> InputOutput\n\n    attrParser = lexeme $\n      Left <$> P.chunk \"input\" <|> Right <$> P.chunk \"output\"\n\nargParser :: Parser (Maybe Id, Type)\nargParser =\n  P.try argWithoutName <|> argWithName\n  where\n    argWithoutName = (Nothing,) <$> typeParser\n    argWithName = P.label \"field name\" $ do\n      mFieldName <- optional $ do\n        name <- lexeme identifier\n        _ <- lexeme $ P.char ':'\n        pure name\n      ty <- typeParser\n      pure (mFieldName, ty)\n\ntypeParser :: Parser Type\ntypeParser = P.label \"type\" $ lexeme $ u32 <|> str\n  where\n    u32 = U32 <$ P.chunk \"u32\"\n    str = Str <$ P.chunk \"string\"\n\ndata FactOrRule = FactType | RuleType\n\nfactOrRuleParser :: Parser AST\nfactOrRuleParser = withNodeId $ \\nodeId -> do\n  name <- lexeme identifier\n  args <- lexeme $ betweenParens $ exprParser `P.sepBy1` comma\n  declType <- lexeme (RuleType <$ P.chunk \":-\") <|> (FactType <$ P.chunk \".\")\n  case declType of\n    RuleType -> do\n      body <- ruleClauseParser `P.sepBy1` comma <* period\n      pure $ Rule nodeId name args body\n    FactType -> pure $ Atom nodeId name args\n  where\n    period = P.char '.'\n\ncomma :: Parser Char\ncomma = lexeme $ P.char ','\n\nruleClauseParser :: Parser AST\nruleClauseParser = do\n  negationParser\n    <|> P.try (atomParser <* P.notFollowedBy opParser)\n    <|> constraintParser\n  where\n    opParser =\n      void constraintOpParser <|> void arithmeticOpParser\n    arithmeticOpParser =\n      P.choice $ concatMap (map $ P.char . snd) arithmeticOps\n\nnegationParser :: Parser AST\nnegationParser = withNodeId $ \\nodeId -> do\n  void $ lexeme $ P.char '!'\n  Not nodeId <$> atomParser\n\natomParser :: Parser AST\natomParser = lexeme $ do\n  withNodeId $ \\nodeId -> do\n    name <- lexeme identifier\n    args <- betweenParens $ exprParser `P.sepBy1` comma\n    pure $ Atom nodeId name args\n\nconstraintParser :: Parser AST\nconstraintParser = withNodeId $ \\nodeId -> do\n  lhs <- lexeme exprParser\n  op <- constraintOpParser\n  rhs <- lexeme exprParser\n  pure $ Constraint nodeId op lhs rhs\n\nexprParser :: Parser AST\nexprParser =\n  lexeme $ withNodeId (L.makeExprParser termParser . precedenceTable)\n  where\n    precedenceTable nodeId =\n      map (map (uncurry (binOp nodeId))) arithmeticOps\n    binOp nodeId op c =\n      L.InfixL (BinOp nodeId op <$ lexeme (P.char c))\n\n    termParser =\n      lexeme $ betweenParens exprParser <|> value\n      where\n        value = withNodeId $ \\nodeId ->\n          Hole nodeId <$ P.char '?' <|>\n          P.try varParser <|>\n          atomParser <|>\n          Lit nodeId <$> literal\n\narithmeticOps :: [[(ArithmeticOp, Char)]]\narithmeticOps =\n  [ [(Multiply, '*'), (Divide, '/')]\n  , [(Plus, '+'), (Minus, '-')]\n  ]\n\nvarParser :: Parser AST\nvarParser = lexeme $ withNodeId $ \\nodeId -> do\n  v <- Var nodeId <$> (identifier <|> wildcard)\n  P.notFollowedBy $ P.char '('\n  pure v\n\nconstraintOpParser :: Parser LogicalOp\nconstraintOpParser = P.label \"equality or comparison operator\" $ lexeme $ do\n  toOp Equals (P.char '=') <|>\n    toOp LessOrEqual (P.string \"<=\") <|>\n    toOp LessThan (P.char '<') <|>\n    toOp GreaterOrEqual (P.string \">=\") <|>\n    toOp GreaterThan (P.char '>') <|>\n    toOp NotEquals (P.string \"!=\")\n  where toOp op p = op <$ lexeme p\n\n-- Not sure if we want to support something like _abc?\nwildcard :: Parser Id\nwildcard =\n  Id . one <$> P.char '_'\n\nidentifier :: Parser Id\nidentifier = Id <$> do\n  firstLetter <- P.letterChar P.<?> \"start of identifier\"\n  rest <- P.takeWhileP (Just \"rest of identifier\") isIdentifierChar\n  let parsed = T.cons firstLetter rest\n  when (parsed `V.elem` reserved) $ do\n    fail . toString $ \"Reserved keyword: \" <> parsed\n  pure parsed\n  where\n    isIdentifierChar c = isAlphaNum c || c == '_'\n\n-- List of reserved words, not allowed to be used in identifiers.\nreserved :: V.Vector Text\nreserved = V.fromList []\n\nliteral :: Parser Literal\nliteral = number <|> string\n\ndigitVector :: V.Vector Char\ndigitVector = V.fromList ['1'..'9']\n\nnumber :: Parser Literal\nnumber = LNumber <$> do\n  positiveNumber <|> zero\n  where\n    zero = 0 <$ P.char '0'\n\n    positiveNumber = do\n      firstDigit <- P.satisfy (`V.elem` digitVector) P.<?> \"non-zero digit\"\n      digits <- P.takeWhileP Nothing isDigit\n      P.notFollowedBy P.letterChar\n      case TR.decimal $ T.cons firstDigit digits of\n        Right (result, _) -> pure result\n        Left err -> panic . toText $ \"Error occurred during parsing of decimal number: \" <> err\n\nstring :: Parser Literal\nstring = LString <$> do\n  P.between (\"\\\"\" P.<?> \"string literal\") \"\\\"\" $\n    toText <$> many (P.try escaped <|> normal)\n  where\n    escaped = do\n      void $ P.char '\\\\'\n      toEscapedChar <$> P.satisfy isEscapeChar\n    isEscapeChar c =\n      c `elem` ['\"', '\\\\', 'n', 'r', 't', 'b', 'f', 'v', '0']\n    toEscapedChar = \\case\n      '\"' -> '\\\"'\n      '\\\\' -> '\\\\'\n      'n' -> '\\n'\n      'r' -> '\\r'\n      't' -> '\\t'\n      'b' -> '\\b'\n      'f' -> '\\f'\n      'v' -> '\\v'\n      '0' -> '\\0'\n      _ -> panic \"Unreachable code in string parser!\"\n    normal = P.anySingleBut '\"'\n\nlexeme :: Parser a -> Parser a\nlexeme = L.lexeme whitespace\n\nwhitespace :: Parser ()\nwhitespace = L.space spaceParser commentParser blockCommentParser where\n  spaceParser = P.skipSome wsChar\n  wsChar = void (P.satisfy $ \\c -> c == ' ' || c == '\\n') P.<?> \"whitespace\"\n  commentParser = L.skipLineComment \"//\"\n  blockCommentParser = L.skipBlockComment \"/*\" \"*/\"\n\nbetweenParens :: Parser a -> Parser a\nbetweenParens =\n  P.between (lexeme $ P.char '(') (P.char ')') . lexeme\n\n-- | Helper for parsers that can recover from errors.\n--   In case of error, keeps parsing up to and including 'endChar'\nwithRecovery :: Char -> Parser a -> Parser (Maybe a)\nwithRecovery endChar p =\n  P.withRecovery handleErr $ map Just p\n  where\n    handleErr err = do\n      P.registerParseError err\n      _ <- P.takeWhileP Nothing (/= endChar)\n      _ <- P.char endChar\n      pure Nothing\n"
  },
  {
    "path": "lib/Eclair/RA/Codegen.hs",
    "content": "module Eclair.RA.Codegen\n  ( CodegenM\n  , runCodegen\n  , LowerState(..)\n  , mkLowerState\n  , CGState(..)\n  , CGInfo(..)\n  , getLowerState\n  , Relation\n  , Alias\n  , getFirstFieldOffset\n  , getContainerInfoByOffset\n  , idxFromConstraints\n  , lookupRelationByIndex\n  , lookupAlias\n  , withUpdatedAlias\n  , withEndLabel\n  , withSearchState\n  , block\n  , declareProgram\n  , fn\n  , apiFn\n  , fnArg\n  , call\n  , primOp\n  , fieldAccess\n  , heapAllocProgram\n  , freeProgram\n  , stackAlloc\n  , loop\n  , jump\n  , labelId\n  , label\n  , parallel\n  , ret\n  , var\n  , assign\n  , if'\n  , not'\n  , and'\n  , equals\n  , notEquals\n  , lessThan\n  , lessOrEqual\n  , greaterThan\n  , greaterOrEqual\n  , mkExternOp\n  , mkArithOp\n  , plus\n  , minus\n  , multiply\n  , divide\n  , lit\n  ) where\n\nimport Data.Maybe (fromJust)\nimport qualified Data.List as List\nimport qualified Data.Map as M\nimport qualified Data.Set as S\nimport Eclair.RA.IndexSelection\nimport Eclair.TypeSystem\nimport Eclair.Common.Id\nimport Eclair.Common.Literal\nimport qualified Eclair.EIR.IR as EIR\nimport qualified Eclair.RA.IR as RA\nimport qualified Eclair.LLVM.Metadata as Meta\n\n\ntype Alias = RA.Alias\ntype Relation = EIR.Relation\ntype EIR = EIR.EIR\ntype AliasMap = Map Alias EIR\n\n-- Helper data type that pre-computes some data that is used a lot,\n-- to speed up the compiler.\ndata CGInfo\n  = CGInfo\n  { relInfo :: [(Relation, Index)]\n  , offsetsForRelationAndIndex :: Map (Relation, Index) Int\n  , offsetsForRelation :: Map Relation Int  -- only tracks first one\n  }\n\ndata LowerState\n  = LowerState\n  { typeEnv :: TypedefInfo\n  , idxMap :: IndexMap\n  , idxSelector :: IndexSelector\n  , cgInfo :: CGInfo\n  , endLabel :: EIR.LabelId\n  , aliasMap :: AliasMap\n  }\n\nmkLowerState :: TypedefInfo -> IndexMap -> IndexSelector -> [(Relation, Index)] -> EIR.LabelId -> LowerState\nmkLowerState typedefInfo indexMap getIndexForSearch relInfos end =\n  LowerState typedefInfo indexMap getIndexForSearch codegenInfo end mempty\n    where\n      codegenInfo = CGInfo relInfos offsetsByRelationAndIndex offsetsByRelation\n      offsetsByRelationAndIndex =\n        M.fromDistinctAscList\n          [ (ri, offset)\n          | ri <- sortNub relInfos\n          -- + 1 due to symbol table at position 0 in program struct\n          , let offset = 1 + fromJust (List.elemIndex ri relInfos)\n          ]\n      offsetsByRelation =\n        let rs = map fst relInfos\n         in M.fromDistinctAscList\n            [ (r, offset)\n            | r <- sortNub rs\n            -- + 1 due to symbol table at position 0 in program struct\n            , let offset = 1 + fromJust (List.elemIndex r rs)\n            ]\n\ndata CGState\n  = Normal LowerState\n  | Search Alias EIR LowerState\n\ntype Count = Int\ntype IdMapping = Map Text Count\n\ndata Mapping\n  = Mapping\n  { labelMapping :: IdMapping\n  , varMapping :: IdMapping\n  }\n\ninstance Semigroup Mapping where\n  (Mapping lbls1 vars1) <> (Mapping lbls2 vars2) =\n    Mapping (combine lbls1 lbls2) (combine vars1 vars2)\n    where combine = M.unionWith (+)\n\ninstance Monoid Mapping where\n  mempty = Mapping mempty mempty\n\nnewtype CodegenM a\n  = CodeGenM (RWS CGState () Mapping a)\n  deriving ( Functor, Applicative, Monad\n           , MonadReader CGState\n           , MonadState Mapping\n           )\n  via (RWS CGState () Mapping)\n\nrunCodegen :: LowerState -> CodegenM EIR -> EIR\nrunCodegen ls (CodeGenM m) =\n  fst $ evalRWS m (Normal ls) mempty\n\nwithSearchState :: Alias -> EIR -> CodegenM a -> CodegenM a\nwithSearchState alias value m = do\n  ls <- getLowerState\n  local (const $ Search alias value ls) m\n\ngetLowerState :: CodegenM LowerState\ngetLowerState = asks getLS\n  where\n    getLS = \\case\n      Normal ls -> ls\n      Search _ _ ls -> ls\n\nblock :: [CodegenM EIR] -> CodegenM EIR\nblock ms = do\n  actions <- sequence ms\n  pure $ EIR.Block $ flattenBlocks actions\n\nflattenBlocks :: [EIR] -> [EIR]\nflattenBlocks actions = flip concatMap actions $ \\case\n  EIR.Block stmts -> stmts\n  stmt -> [stmt]\n\ndeclareProgram :: [(Relation, Meta.Metadata)] -> CodegenM EIR\ndeclareProgram metas = pure $ EIR.DeclareProgram metas\n\nfn :: Text -> [EIR.Type] -> EIR.Type -> [CodegenM EIR] -> CodegenM EIR\nfn name tys retTy body = EIR.Function EIR.Private name tys retTy <$> block body\n\napiFn :: Text -> [EIR.Type] -> EIR.Type -> [CodegenM EIR] -> CodegenM EIR\napiFn name tys retTy body = EIR.Function EIR.Public name tys retTy <$> block body\n\nfnArg :: Int -> CodegenM EIR\nfnArg n = pure $ EIR.FunctionArg n\n\ncall :: Relation -> Index -> EIR.Function -> [CodegenM EIR] -> CodegenM EIR\ncall r idx fn' =\n  primOp (EIR.RelationOp r idx fn')\n\nprimOp :: EIR.Op -> [CodegenM EIR] -> CodegenM EIR\nprimOp op args =\n  EIR.PrimOp op <$> sequence args\n\nfieldAccess :: CodegenM EIR -> Int -> CodegenM EIR\nfieldAccess struct n = flip EIR.FieldAccess n <$> struct\n\nheapAllocProgram :: CodegenM EIR\nheapAllocProgram =\n  pure EIR.HeapAllocateProgram\n\nfreeProgram :: CodegenM EIR -> CodegenM EIR\nfreeProgram ptr = EIR.FreeProgram <$> ptr\n\nstackAlloc :: Relation -> Index -> EIR.Type -> CodegenM EIR\nstackAlloc r idx ty =\n  pure $ EIR.StackAllocate (stripIdPrefixes r) idx ty\n\nloop :: [CodegenM EIR] -> CodegenM EIR\nloop ms = do\n  actions <- sequence ms\n  pure $ EIR.Loop $ flattenBlocks actions\n\njump :: EIR.LabelId -> CodegenM EIR\njump lbl = pure $ EIR.Jump lbl\n\n-- NOTE: labelId and label are split up, so label can be used in 2 ways:\n-- 1) \"endLabel\" can also be passed into 'label'\n-- 2) dynamic labels used for control flow can be generated with 'labelId' and passed to 'label'\n\nlabelId :: Text -> CodegenM EIR.LabelId\nlabelId name = do\n  mapping <- gets labelMapping\n  (lblId, updatedMapping) <- lookupId name mapping\n  modify $ \\s -> s { labelMapping = updatedMapping }\n  pure . EIR.LabelId $ lblId\n\nlabel :: EIR.LabelId -> CodegenM EIR\nlabel = pure . EIR.Label\n\nparallel :: [CodegenM EIR] -> CodegenM EIR\nparallel ms = do\n  actions <- sequence ms\n  pure $ EIR.Par $ flattenBlocks actions\n\nret :: CodegenM EIR -> CodegenM EIR\nret = map EIR.Return\n\n-- NOTE: 2nd layer is for easy integration with other helper functions\n-- e.g.:\n--\n-- do\n--   v <- var \"...\"\n--   sequence [ ... ]\nvar :: Text -> CodegenM (CodegenM EIR)\nvar name = do\n  mapping <- gets varMapping\n  (varId, updatedMapping) <- lookupId name mapping\n  modify $ \\s -> s { varMapping = updatedMapping }\n  pure . pure . EIR.Var $ varId\n\nassign :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nassign var' value = do\n  v <- var'\n  value >>= \\case\n    EIR.Block stmts ->\n      let lastStmt = List.last stmts\n          firstStmts = List.init stmts\n       in block (map pure $ firstStmts ++ [EIR.Assign v lastStmt])\n    val -> pure $ EIR.Assign v val\n\nif' :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nif' cond body = do\n  condition <- var \"condition\"\n  block\n    [ assign condition cond\n    , EIR.If <$> condition <*> body\n    ]\n\nnot' :: CodegenM EIR -> CodegenM EIR\nnot' bool' = EIR.Not <$> bool'\n\nand' :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nand' lhs rhs = do\n  lhsResult <- var \"bool\"\n  rhsResult <- var \"bool\"\n  block\n    [ assign lhsResult lhs\n    , assign rhsResult rhs\n    , EIR.And <$> lhsResult <*> rhsResult\n    ]\n\nmkArithOp :: EIR.ArithmeticOp -> CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nmkArithOp op lhs rhs =\n  let args = sequence [lhs, rhs]\n   in EIR.PrimOp (EIR.ArithOp op) <$> args\n\nmkExternOp :: Id -> [CodegenM EIR] -> CodegenM EIR\nmkExternOp name args = do\n  let program = EIR.FunctionArg 0\n      symbolTable = EIR.FieldAccess program 0\n  args' <- sequence args\n  pure $ EIR.PrimOp (EIR.ExternOp name) $ symbolTable : args'\n\nplus :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nplus = mkArithOp EIR.Plus\n\nminus :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nminus = mkArithOp EIR.Minus\n\nmultiply :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nmultiply = mkArithOp EIR.Multiply\n\ndivide :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\ndivide = mkArithOp EIR.Divide\n\nmkConstrainOp :: EIR.LogicalOp -> CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nmkConstrainOp op lhs rhs =\n  let args = sequence [lhs, rhs]\n   in EIR.PrimOp (EIR.ComparisonOp op) <$> args\n\nequals :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nequals = mkConstrainOp EIR.Equals\n\nnotEquals :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nnotEquals = mkConstrainOp EIR.NotEquals\n\nlessThan :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nlessThan = mkConstrainOp EIR.LessThan\n\nlessOrEqual :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\nlessOrEqual = mkConstrainOp EIR.LessOrEqual\n\ngreaterThan :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\ngreaterThan = mkConstrainOp EIR.GreaterThan\n\ngreaterOrEqual :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR\ngreaterOrEqual = mkConstrainOp EIR.GreaterOrEqual\n\nlit :: Word32 -> CodegenM EIR\nlit x =\n  pure $ EIR.Lit $ LNumber x\n\nlookupId :: Text -> IdMapping -> CodegenM (Text, IdMapping)\nlookupId name mapping = do\n  let (mValue, updatedMapping) = M.insertLookupWithKey update name defaultValue mapping\n      value = case mValue of\n        Nothing -> name\n        Just val -> name <> \"_\" <> show val\n  pure (value, updatedMapping)\n  where\n    defaultValue = 1\n    update _ _ prevValue = prevValue + 1\n\ngetFieldOffset :: Relation -> Index -> CodegenM Int\ngetFieldOffset r idx = do\n  fromJust . M.lookup (r, idx) . offsetsForRelationAndIndex . cgInfo <$> getLowerState\n\ngetFirstFieldOffset :: Relation -> CodegenM Int\ngetFirstFieldOffset r = do\n  fromJust . M.lookup r . offsetsForRelation . cgInfo <$> getLowerState\n\ngetContainerInfoByOffset :: Int -> CodegenM (Relation, Index)\ngetContainerInfoByOffset offset =\n  -- - 1 due to symbol table at position 0 in program struct\n  (List.!! (offset - 1)) . relInfo . cgInfo <$> getLowerState\n\nlookupAlias :: Alias -> CodegenM EIR\nlookupAlias a = ask >>= \\case\n  Normal ls -> lookupAlias' ls\n  Search _ _ ls -> lookupAlias' ls\n  where\n    lookupAlias' ls =\n      pure $ fromJust $ M.lookup a (aliasMap ls)\n\nwithUpdatedAlias :: Alias -> EIR -> CodegenM a -> CodegenM a\nwithUpdatedAlias a curr m = do\n  state' <- ask <&> \\case\n    Normal ls -> Normal (updateAlias ls curr)\n    Search a' v ls -> Search a' v (updateAlias ls curr)\n  local (const state') m\n  where\n    updateAlias ls curr' =\n      ls { aliasMap = M.insert a curr' (aliasMap ls) }\n\nwithEndLabel :: EIR.LabelId -> CodegenM a -> CodegenM a\nwithEndLabel end m = do\n  local setLabel m\n  where\n    setLabel = \\case\n      Normal ls -> Normal (set ls)\n      Search a v ls -> Search a v (set ls)\n    set ls = ls { endLabel = end }\n\nidxFromConstraints :: Relation -> Alias -> [(Relation, Column)] -> CodegenM Index\nidxFromConstraints r a constraints = do\n  lowerState <- getLowerState\n  let (getIndexForSearch, indexMap) = (idxSelector &&& idxMap) lowerState\n      r' = stripIdPrefixes r\n  if null constraints\n    then do\n      -- NOTE: no constraints so we pick the longest index (total search)\n      let mIndex = do\n            indices <- M.lookup r' indexMap\n            viaNonEmpty head $ sortOn (negate . length . unIndex) $ toList indices\n      pure $ fromJust mIndex\n    else do\n      let columns = mapMaybe (columnsForRelation a) constraints\n          signature = SearchSignature $ S.fromList columns\n          idx = getIndexForSearch r signature\n      pure idx\n\nlookupRelationByIndex :: Relation -> Index -> CodegenM EIR\nlookupRelationByIndex r idx = do\n  field <- getFieldOffset r idx\n  fieldAccess (fnArg 0) field\n"
  },
  {
    "path": "lib/Eclair/RA/IR.hs",
    "content": "{-# LANGUAGE TemplateHaskell #-}\n\nmodule Eclair.RA.IR\n  ( Relation\n  , RA(..)\n  , RAF(..)\n  , Alias\n  , Clause\n  , Action\n  , ColumnIndex\n  , LogicalOp(..)\n  , ArithmeticOp(..)\n  , Op(..)\n  ) where\n\nimport Eclair.Common.Id\nimport Eclair.Common.Pretty\nimport Eclair.Common.Operator\nimport Eclair.Common.Location (NodeId(..))\n\n\ntype Relation = Id\ntype Alias = Id\ntype Clause = RA\ntype Action = RA\ntype ColumnIndex = Int\n\ndata Op\n  = BuiltinOp ArithmeticOp\n  | ExternOp Id\n  deriving (Eq, Show)\n\n-- NOTE: removed Insert, couldn't find a use?\ndata RA\n  = Search NodeId Relation Alias [Clause] Action\n  | Project NodeId Relation [RA]\n  | Merge NodeId Relation Relation\n  | Swap NodeId Relation Relation\n  | Purge NodeId Relation\n  | Par NodeId [RA]\n  | Loop NodeId [RA]\n  -- NOTE: counttuples check is 'builtin' atm\n  -- Later this needs to be changed to Clause to deal with 'X<100' etc as well.\n  | Exit NodeId [Relation]\n  | Module NodeId [RA]\n  | Lit NodeId Word32\n  | Undef NodeId\n  | ColumnIndex NodeId Relation ColumnIndex\n  | CompareOp NodeId LogicalOp RA RA\n  | PrimOp NodeId Op [RA]\n  | NotElem NodeId Relation [RA]\n  | If NodeId RA RA  -- NOTE: args are condition and body\n  deriving (Eq, Show)\n\nmakeBaseFunctor ''RA\n\n\nprettyBlock :: Pretty a => [a] -> Doc ann\nprettyBlock = indentBlock . vsep . map pretty\n\nindentBlock :: Doc ann -> Doc ann\nindentBlock block = nest indentation (hardline <> block)\n\ninstance Pretty Op where\n  pretty = \\case\n    BuiltinOp op -> pretty op\n    ExternOp opName -> pretty opName\n\ninstance Pretty RA where\n  pretty = \\case\n    Search _ r alias clauses inner ->\n      let clausesText =\n            if null clauses\n              then \"\"\n              else \"where\" <+> parens (withAnds $ map pretty clauses) <> space\n       in \"search\" <+> pretty r <+> \"as\" <+> pretty alias <+> clausesText <> \"do\" <>\n            prettyBlock [inner]\n    Project _ r terms ->\n      \"project\" <+> prettyValues terms <+>\n      \"into\" <+> pretty r\n    Merge _ r1 r2 -> \"merge\" <+> pretty r1 <+> pretty r2\n    Swap _ r1 r2 -> \"swap\" <+> pretty r1 <+> pretty r2\n    Purge _ r -> \"purge\" <+> pretty r\n    Par _ stmts -> \"parallel do\" <> prettyBlock stmts\n    Loop _ stmts -> \"loop do\" <> prettyBlock stmts\n    If _ cond stmt ->\n      \"if\" <+> pretty cond <+> \"do\" <> prettyBlock [stmt]\n    Exit _ rs ->\n      let texts = map formatExitCondition rs\n      in \"exit if\" <+> withAnds texts\n    Module _ stmts ->\n      vsep $ map pretty stmts\n    Lit _ x -> pretty x\n    Undef _ -> \"undefined\"\n    ColumnIndex _ r idx -> pretty r <> brackets (pretty idx)\n    CompareOp _ op lhs rhs -> pretty lhs <+> pretty op <+> pretty rhs\n    PrimOp _ op args ->\n      case (op, args) of\n        (BuiltinOp{}, [lhs, rhs]) ->\n          parens $ pretty lhs <+> pretty op <+> pretty rhs\n        _ ->\n          pretty op <> parens (withCommas $ map pretty args)\n    NotElem _ r terms -> prettyValues terms <+> \"∉\" <+> pretty r\n    where\n      prettyValues terms = parens (withCommas $ map pretty terms)\n      formatExitCondition r =\n        \"counttuples\" <> parens (pretty r) <+> \"=\" <+> \"0\"\n"
  },
  {
    "path": "lib/Eclair/RA/IndexSelection.hs",
    "content": "module Eclair.RA.IndexSelection\n  ( IndexMap\n  , IndexSelector\n  , Index(..)\n  , SearchSignature(..)\n  , Column\n  , runIndexSelection\n  , columnsForRelation\n  , NormalizedEquality(..)\n  , normalizedEqToConstraints\n  , extractEqualities\n  , definedColumnsFor\n  ) where\n\n-- Based on the paper \"Automatic Index Selection for Large-Scale Datalog Computation\"\n-- http://www.vldb.org/pvldb/vol12/p141-subotic.pdf\n\nimport Data.Maybe (fromJust)\nimport Eclair.Common.Id\nimport Eclair.Common.Pretty\nimport Eclair.RA.IR\nimport Eclair.Comonads\nimport Eclair.TypeSystem (TypedefInfo)\nimport Algebra.Graph.Bipartite.AdjacencyMap\nimport Algebra.Graph.Bipartite.AdjacencyMap.Algorithm hiding (matching)\nimport qualified Data.Set as Set\nimport qualified Data.Map as Map\nimport qualified Data.List as List\nimport qualified Data.DList.DNonEmpty as NE\n\n\ntype Column = Int\n\nnewtype SearchSignature\n  = SearchSignature (Set Column)\n  deriving (Eq, Ord, Show)\n\nnewtype Index\n  = Index\n  { unIndex :: [Column]  -- TODO: use NonEmpty\n  } deriving (Eq, Ord, Show)\n\ninstance Pretty Index where\n  pretty (Index columns) =\n    brackets $ withCommas $ map pretty columns\n\ntype SearchSet = Set SearchSignature\ntype SearchChain = NonEmpty SearchSignature\ntype SearchMap = Map Relation SearchSet\ntype SearchGraph = AdjacencyMap SearchSignature SearchSignature\ntype SearchMatching = Matching SearchSignature SearchSignature\ntype IndexSelection = [(Relation, Map SearchSignature Index)]\n\ntype IndexMap = Map Relation (Set Index)\ntype IndexSelector = Relation -> SearchSignature -> Index\n\nrunIndexSelection :: TypedefInfo -> RA -> (IndexMap, IndexSelector)\nrunIndexSelection defInfo ra =\n  let searchMap = searchesForProgram defInfo ra\n      indexSelection :: IndexSelection\n      indexSelection = Map.foldrWithKey (\\r searchSet acc ->\n        let graph = buildGraph searchSet\n            matching = maxMatching graph\n            chains = getChainsFromMatching graph matching\n            indices = indicesFromChains searchSet chains\n         in (r, indices):acc) mempty searchMap\n      combineIdxs idxs idx = idxs <> one idx\n      indexMap = foldl' combineIdxs mempty <$> Map.fromList indexSelection\n      indexSelector r s = fromJust $ do\n        indexMapping <- snd <$> List.find ((== r) . fst) indexSelection\n        Map.lookup s indexMapping\n  in (indexMap, indexSelector)\n\ndata SearchFact\n  = SearchOn Relation SearchSignature\n  | Related Relation Relation\n  deriving (Eq, Ord)\n\n-- All relations (including delta_XXX, new_XXX) need atleast one index for full order search\n-- Swap operation requires indices of r1 and r2 to be related.\nsearchesForProgram :: TypedefInfo -> RA -> SearchMap\nsearchesForProgram defInfo ra =\n  let raSearchFacts = execState (gcata (dsitribute extractEqualities) constraintsForRA ra) mempty\n      relationFullSearches = getFullSearchesForRelations defInfo\n      facts = raSearchFacts <> relationFullSearches\n   in solve facts\n  where\n    addFact fact = modify (fact:)\n    constraintsForRA = \\case\n      SearchF _ r a (foldMap tSnd -> eqs) (tThd -> action) -> do\n        -- 1. Only direct constraints in the search matter, since that is what\n        --    is used to select the index with, afterwards we are already\n        --    looping over the value!\n        -- 2. Only equality constraints matter, not \"NoElem\" constraints!\n        let cs = concatMap normalizedEqToConstraints eqs\n            relevantCols = mapMaybe (columnsForRelation a) cs\n            signature = SearchSignature $ Set.fromList relevantCols\n        unless (null relevantCols) $ do\n          addFact $ SearchOn r signature\n        action\n      NotElemF _ r cols -> do\n        let values = map tFst cols\n            cs = definedColumnsFor values\n            signature = SearchSignature $ Set.fromList cs\n        addFact $ SearchOn r signature\n      MergeF _ from' _ -> do\n        -- Always add a full search signature for the from relation, so we don't lose any data.\n        let columns = columnsFor . fromJust $ Map.lookup (stripIdPrefixes from') defInfo\n            signature = SearchSignature $ Set.fromList columns\n        addFact $ SearchOn from' signature\n      SwapF _ r1 r2  ->\n        addFact $ Related r1 r2\n      raf ->\n        traverse_ tThd raf\n\n    dsitribute\n      :: Corecursive t\n      => (Base t (t, b) -> b)\n      -> (Base t (Triple t b a) -> Triple t b (Base t a))\n    dsitribute g m =\n      let base_t_t = map tFst m\n          base_t_tb = map (tFst &&& tSnd) m\n          base_t_a = map tThd m\n        in Triple (embed base_t_t) (g base_t_tb) base_t_a\n\n-- For every relation we add atleast 1 one index with all columns.\ngetFullSearchesForRelations :: TypedefInfo -> [SearchFact]\ngetFullSearchesForRelations defInfo =\n  [ SearchOn r . toSearchSignature $ unsafeLookup r\n  | r <- Map.keys defInfo\n  ]\n  where\n    unsafeLookup r = fromJust $ Map.lookup r defInfo\n    toSearchSignature = SearchSignature . Set.fromList . columnsFor\n\ndata NormalizedEquality\n  = NormalizedEquality Alias Column RA\n  deriving Show\n\nextractEqualities :: RAF (RA, [NormalizedEquality]) -> [NormalizedEquality]\nextractEqualities = \\case\n  CompareOpF _ Equals (lhs, _) (rhs, _) | isDefined lhs && isDefined rhs ->\n    toEqualities lhs rhs\n  raf ->\n    foldMap snd raf\n  where\n    isDefined = \\case\n      Undef {} -> False\n      _ -> True\n    toEqualities lhs rhs = case (lhs, rhs) of\n      (ColumnIndex _ lA lCol, ColumnIndex _ rA rCol) ->\n        [ NormalizedEquality lA lCol rhs\n        , NormalizedEquality rA rCol lhs\n        ]\n      (ColumnIndex _ lA lCol, _) ->\n        [NormalizedEquality lA lCol rhs]\n      (_, ColumnIndex {}) ->\n        toEqualities rhs lhs\n      _ ->\n        mempty\n\nnormalizedEqToConstraints :: NormalizedEquality -> [(Relation, Column)]\nnormalizedEqToConstraints = \\case\n  NormalizedEquality a1 c1 ra -> case ra of\n    ColumnIndex _ a2 c2 ->\n      [(a1, c1), (a2, c2)]\n    _ ->\n      [(a1, c1)]\n\ncolumnsForRelation :: Relation -> (Relation, Column) -> Maybe Column\ncolumnsForRelation r (r', col)\n  | r == r'   = Just col\n  | otherwise = Nothing\n\nsolve :: [SearchFact] -> SearchMap\nsolve facts = execState (traverse solveOne $ sort facts) mempty\n  where\n    solveOne = \\case\n      SearchOn r signature ->\n        modify (Map.insertWith (<>) r (one signature))\n      Related r1 r2 -> do\n        signatures1 <- gets (Map.findWithDefault mempty r1)\n        signatures2 <- gets (Map.findWithDefault mempty r2)\n        let combined = signatures1 <> signatures2\n        modify $ Map.insert r1 combined\n               . Map.insert r2 combined\n\nbuildGraph :: SearchSet -> SearchGraph\nbuildGraph searchSet =\n  vertices searches searches\n  `overlay`\n  edges [(l, r) | l <- searches, r <- searches, l `isSubsetOf` r]\n  where\n    searches = toList searchSet\n    isSubsetOf (SearchSignature xs) (SearchSignature ys) =\n      xs `Set.isProperSubsetOf` ys\n\ngetChainsFromMatching :: SearchGraph -> SearchMatching -> Set SearchChain\ngetChainsFromMatching g m =\n  let (covered, uncovered) = List.partition (`leftCovered` m) $ leftVertexList g\n      uncoveredChains = map one uncovered\n      coveredChains = map (\\n -> NE.toNonEmpty $ getChain (pure n) n) covered\n   in Set.fromList $ uncoveredChains <> coveredChains\n  where\n    leftCovered :: Ord a => a -> Matching a b -> Bool\n    leftCovered a = Map.member a . pairOfLeft\n    -- getChain alternates between U and V side of the bipartite graph\n    -- A lookup is done on V side:\n    --   - if it finds no match, we have reached end of the chain\n    --   - Otherwise, we found the next node in the chain, and use\n    --     this node to find rest of the chain.\n    getChain acc u =\n      case Map.lookup u (pairOfLeft m) of\n        Nothing ->\n          -- Longest chain at end, needed in indexForChain\n          acc\n        Just v ->\n          -- Implicitly swap U and V side by passing in v as u:\n          getChain (NE.snoc acc v) v\n\nindicesFromChains :: SearchSet -> Set SearchChain -> Map SearchSignature Index\nindicesFromChains (Set.toList -> searchSet) (Set.toList -> chains) =\n  Map.fromList [ (signature, indexForChain chain)\n               | chain <- chains\n               , signature <- searchSet\n               , signature `elem` chain\n               ]\n\n-- NOTE: assumes chain is sorted from shortest to longest\nindexForChain :: SearchChain -> Index\nindexForChain chain = Index $ foldMap Set.toList columns\n  where\n    SearchSignature shortest :| rest = chain\n    diffColumns = zipWith columnDiff rest (toList chain)\n    columns = shortest : diffColumns\n    columnDiff (SearchSignature long) (SearchSignature short) =\n      long Set.\\\\ short\n\ncolumnsFor :: [a] -> [Int]\ncolumnsFor = zipWith const [0..]\n\ndefinedColumnsFor :: [RA] -> [Int]\ndefinedColumnsFor values =\n  catMaybes $ zipWith f [0..] values\n  where\n    f c = \\case\n      Undef {} -> Nothing\n      _ -> Just c\n"
  },
  {
    "path": "lib/Eclair/RA/Lower.hs",
    "content": "\nmodule Eclair.RA.Lower ( compileToEIR ) where\n\nimport Prelude\nimport Data.Maybe (fromJust)\n\nimport qualified Data.List as List\nimport qualified Data.Map as Map\nimport qualified Data.Set as Set\n\nimport Eclair.Common.Id\nimport Eclair.Common.Literal\nimport Eclair.Comonads hiding (Quad)\nimport Eclair.TypeSystem\nimport Eclair.AST.Transforms.ReplaceStrings (StringMap)\nimport Eclair.RA.Codegen\nimport Eclair.EIR.IR (EIR)\nimport Eclair.RA.IR (RA)\nimport Eclair.RA.IndexSelection\nimport qualified Eclair.EIR.IR as EIR\nimport qualified Eclair.RA.IR as RA\nimport qualified Eclair.LLVM.Metadata as M\n\n\ncompileToEIR :: StringMap -> TypedefInfo -> RA -> EIR\ncompileToEIR stringMap typedefInfo ra =\n  let (indexMap, getIndexForSearch) = runIndexSelection typedefInfo ra\n      containerInfos' = getContainerInfos indexMap typedefInfo\n      end = \"the.end\"\n      lowerState = mkLowerState typedefInfo indexMap getIndexForSearch (map fst containerInfos') end\n      moduleStmts :: [CodegenM EIR]\n      moduleStmts =\n        [ declareProgram $ map (\\((r, _), m) -> (r, m)) containerInfos'\n        , compileInit stringMap\n        , compileDestroy\n        , compileRun ra\n        ]\n   in EIR.Module $ map (runCodegen lowerState) moduleStmts\n\ncompileInit :: StringMap -> CodegenM EIR\ncompileInit stringMap = do\n  program <- var \"program\"\n  let symbolTable = fieldAccess program 0\n      symbolTableInitAction = primOp EIR.SymbolTableInit [symbolTable]\n  relationInitActions <- forEachRelation program $ \\(r, idx) relationPtr ->\n    call r idx EIR.InitializeEmpty [relationPtr]\n  let addSymbolActions = toSymbolTableInsertActions symbolTable stringMap\n      initActions = symbolTableInitAction : relationInitActions <> addSymbolActions\n  apiFn \"eclair_program_init\" [] (EIR.Pointer EIR.Program) $\n    assign program heapAllocProgram\n    : initActions\n    -- Open question: if some facts are known at compile time, search for derived facts up front?\n    <> [ ret program ]\n\ntoSymbolTableInsertActions :: CodegenM EIR -> StringMap -> [CodegenM EIR]\ntoSymbolTableInsertActions symbolTable stringMap =\n  map (doInsert . fst) $ sortWith snd $ Map.toList stringMap\n  where\n    doInsert symbol =\n      primOp EIR.SymbolTableInsert [symbolTable, litStr symbol]\n    litStr =\n      pure . EIR.Lit . LString\n\n\ncompileDestroy :: CodegenM EIR\ncompileDestroy = do\n  let program = fnArg 0\n      symbolTableDestroyAction = primOp EIR.SymbolTableDestroy [fieldAccess program 0]\n  relationDestroyActions <- forEachRelation program $ \\(r, idx) relationPtr ->\n    call r idx EIR.Destroy [relationPtr]\n  let destroyActions = symbolTableDestroyAction : relationDestroyActions\n  apiFn \"eclair_program_destroy\" [EIR.Pointer EIR.Program] EIR.Void $\n    destroyActions\n    <> [ freeProgram program ]\n\ncompileRun :: RA -> CodegenM EIR\ncompileRun ra = do\n  apiFn \"eclair_program_run\" [EIR.Pointer EIR.Program] EIR.Void\n    [generateProgramInstructions ra]\n\ngenerateProgramInstructions :: RA -> CodegenM EIR\ngenerateProgramInstructions = gcata (distribute extractEqualities) $ \\case\n  RA.ModuleF _ (map extract -> actions) -> block actions\n  RA.ParF _ (map extract -> actions) -> parallel actions\n  RA.SearchF _ r alias clauses (extract -> action) -> do\n    let eqsInSearch = foldMap tSnd clauses\n        eqs = concatMap normalizedEqToConstraints eqsInSearch\n    idx <- idxFromConstraints r alias eqs\n    let relationPtr = lookupRelationByIndex r idx\n        isConstrain = \\case\n          RA.CompareOp {} -> True\n          _ -> False\n        queryClauses = map extract $ filter ((not . isConstrain) . tFst) clauses\n        query = List.foldl1' and' queryClauses\n    (initBoundValues, lbValue, ubValue) <- initValue r idx alias eqsInSearch\n    block\n      [ initBoundValues\n      , rangeQuery r idx relationPtr lbValue ubValue $ \\iter -> do\n          current <- var \"current\"\n          block\n            [ assign current $ call r idx EIR.IterCurrent [iter]\n            , do\n              currentValue <- current\n              case length queryClauses of\n                0 -> -- No query to check: always matches\n                  withUpdatedAlias alias currentValue action\n                _ -> do\n                  withSearchState alias currentValue $\n                    withUpdatedAlias alias currentValue $\n                      if' query action\n            ]\n      ]\n  RA.ProjectF _ r (map extract -> unresolvedValues) -> do\n    values <- sequence unresolvedValues\n    let values' = map pure values\n    indices <- indexesForRelation r\n    var' <- var \"value\"\n    let -- NOTE: for allocating a value, the index does not matter\n        -- (a value is always represented as [N x i32] internally)\n        -- This saves us doing a few stack allocations.\n        firstIdx = fromJust $ viaNonEmpty head indices\n        allocValue = assign var' $ stackAlloc r firstIdx EIR.Value\n        assignStmts = zipWith (assign . fieldAccess var') [0..] values'\n        insertStmts = flip map indices $ \\idx ->\n          -- NOTE: The insert function is different for each r + idx combination though!\n          call r idx EIR.Insert [lookupRelationByIndex r idx, var']\n    block $ allocValue : assignStmts <> insertStmts\n  RA.PurgeF _ r ->\n    block =<< relationUnaryFn r EIR.Purge\n  RA.SwapF _ r1 r2 ->\n    block =<< relationBinFn r1 r2 EIR.Swap\n  RA.MergeF _ r1 r2 -> do\n    -- NOTE: r1 = from/src, r2 = to/dst\n    -- TODO: which idx? just select first matching? or idx on all columns?\n    idxR1 <- fromJust . viaNonEmpty head <$> indexesForRelation r1\n    let relation1Ptr = lookupRelationByIndex r1 idxR1\n\n    indices2 <- indexesForRelation r2\n    block $ flip map indices2 $ \\idxR2 -> do\n      beginIter <- var \"begin_iter\"\n      endIter <- var \"end_iter\"\n      let relation2Ptr = lookupRelationByIndex r2 idxR2\n      block\n        [ assign beginIter $ stackAlloc r1 idxR1 EIR.Iter\n        , assign endIter $ stackAlloc r1 idxR1 EIR.Iter\n        , call r1 idxR1 EIR.IterBegin [relation1Ptr, beginIter]\n        , call r1 idxR1 EIR.IterEnd [relation1Ptr, endIter]\n        , call r2 idxR2 (EIR.InsertRange r1 idxR1) [relation2Ptr, beginIter, endIter]\n        ]\n  RA.LoopF _ (map extract -> actions) -> do\n    end <- labelId \"loop.end\"\n    block [withEndLabel end $ loop actions, label end]\n  RA.IfF _ (extract -> condition) (extract -> action) -> do\n    if' condition action\n  RA.PrimOpF _ (RA.BuiltinOp op) (map extract -> args) ->\n    case args of\n      [lhs, rhs] -> do\n        let toArithmetic = case op of\n              RA.Plus -> plus\n              RA.Minus -> minus\n              RA.Multiply -> multiply\n              RA.Divide -> divide\n        toArithmetic lhs rhs\n      _ ->\n        panic \"Unexpected case in 'generateProgramInstructions' while lowering RA!\"\n  RA.PrimOpF _ (RA.ExternOp opName) (map extract -> args) -> do\n    mkExternOp opName args\n  RA.CompareOpF _ op (extract -> lhs) (extract -> rhs) -> do\n    let toComparison = case op of\n          RA.Equals -> equals\n          RA.NotEquals -> notEquals\n          RA.LessThan -> lessThan\n          RA.LessOrEqual -> lessOrEqual\n          RA.GreaterThan -> greaterThan\n          RA.GreaterOrEqual -> greaterOrEqual\n    toComparison lhs rhs\n  RA.ExitF _ rs -> do\n    end <- endLabel <$> getLowerState\n    foldl' f (jump end) =<< traverse getFirstFieldOffset rs\n    where\n      f inner field = do\n        (r, idx) <- getContainerInfoByOffset field\n        let programPtr = fnArg 0\n            relationPtr = fieldAccess programPtr field\n            isEmpty = call r idx EIR.IsEmpty [relationPtr]\n        if' isEmpty inner\n  RA.LitF _ x -> lit x\n  RA.NotElemF _ r values -> do\n    if containsUndefined $ map tFst values\n      then existenceCheckPartialSearch\n      else existenceCheckTotalSearch\n    where\n      containsUndefined = isJust . find (\\case\n        RA.Undef {} -> True\n        _ -> False)\n      existenceCheckPartialSearch = do\n        getIndexForSearch <- idxSelector <$> getLowerState\n        lbValue <- var \"lower_bound_value\"\n        ubValue <- var \"upper_bound_value\"\n        beginIter <- var \"begin_iter\"\n        endIter <- var \"end_iter\"\n        let values' = map tFst values\n            cs = definedColumnsFor values'\n            signature = SearchSignature $ Set.fromList cs\n            idx = getIndexForSearch r signature\n            lbValueWithCols = zipWith (curry $ second $ lowerConstrainValue (lit 0x00000000)) [0..] values'\n            ubValueWithCols = zipWith (curry $ second $ lowerConstrainValue (lit 0xffffffff)) [0..] values'\n            lbAssignStmts = block $ map (\\(i, val) -> assign (fieldAccess lbValue i) val) lbValueWithCols\n            ubAssignStmts = block $ map (\\(i, val) -> assign (fieldAccess ubValue i) val) ubValueWithCols\n            relationPtr = lookupRelationByIndex r idx\n        block\n          [ assign lbValue $ stackAlloc r idx EIR.Value\n          , lbAssignStmts\n          , assign ubValue $ stackAlloc r idx EIR.Value\n          , ubAssignStmts\n          , assign beginIter $ stackAlloc r idx EIR.Iter\n          , assign endIter $ stackAlloc r idx EIR.Iter\n          , call r idx EIR.IterLowerBound [relationPtr, lbValue, beginIter]\n          , call r idx EIR.IterUpperBound [relationPtr, ubValue, endIter]\n          , call r idx EIR.IterIsEqual [beginIter, endIter]\n          ]\n      existenceCheckTotalSearch = do\n        let columnValues = map extract values\n        value <- var \"value\"\n        let idx = mkFindIndex columnValues\n            relationPtr = lookupRelationByIndex r idx\n            allocValue = assign value $ stackAlloc r idx EIR.Value\n        containsVar <- var \"contains_result\"\n        let assignActions = zipWith (assign . fieldAccess value) [0..] columnValues\n            containsAction = assign containsVar $ call r idx EIR.Contains [relationPtr, value]\n        block $ allocValue : assignActions <> [containsAction, not' containsVar]\n  RA.ColumnIndexF _ a' col -> ask >>= \\case\n    Search a value _ ->\n      if a == a'\n        then getColumn value col\n        else do\n          currentAliasValue <- lookupAlias a'\n          getColumn currentAliasValue col\n    Normal _ -> do\n      currentAliasValue <- lookupAlias a'\n      getColumn currentAliasValue col\n    where\n      getColumn value =\n        fieldAccess (pure value)\n  RA.UndefF {} ->\n    panic \"Undef should not occur when lowering to EIR!\"\n  where\n    distribute :: Corecursive t\n               => (Base t (t, a) -> a)\n               -> (Base t (Triple t a b) -> Triple t a (Base t b))\n    distribute f m =\n      let base_t_t = map tFst m\n          base_t_ta = map (tFst &&& tSnd) m\n          base_t_b = map tThd m\n      in Triple (embed base_t_t) (f base_t_ta) base_t_b\n\nrangeQuery :: Relation\n           -> Index\n           -> CodegenM EIR\n           -> CodegenM EIR\n           -> CodegenM EIR\n           -> (CodegenM EIR -> CodegenM EIR)\n           -> CodegenM EIR\nrangeQuery r idx relationPtr lbValue ubValue loopAction = do\n  beginIter <- var \"begin_iter\"\n  endIter <- var \"end_iter\"\n  endLabel' <- labelId \"range_query.end\"\n  let allocBeginIter = assign beginIter $ stackAlloc r idx EIR.Iter\n      allocEndIter = assign endIter $ stackAlloc r idx EIR.Iter\n      initLB = call r idx EIR.IterLowerBound [relationPtr, lbValue, beginIter]\n      initUB = call r idx EIR.IterUpperBound [relationPtr, ubValue, endIter]\n      advanceIter = call r idx EIR.IterNext [beginIter]\n      isAtEnd = call r idx EIR.IterIsEqual [beginIter, endIter]\n      stopIfFinished = if' isAtEnd (jump endLabel')\n      loopStmts = [stopIfFinished, loopAction beginIter, advanceIter]\n  block [allocBeginIter, allocEndIter, initLB, initUB, loop loopStmts, label endLabel']\n\n-- NOTE: only supports unsigned integers for now!\ninitValue :: Relation -> Index -> RA.Alias -> [NormalizedEquality]\n          -> CodegenM (CodegenM EIR, CodegenM EIR, CodegenM EIR)\ninitValue r idx a eqs = do\n  let r' = stripIdPrefixes r\n  typeInfo <- fromJust . Map.lookup r' . typeEnv <$> getLowerState\n  lbValue <- var \"lower_bound_value\"\n  ubValue <- var \"upper_bound_value\"\n  let columnNrs = take (length typeInfo) [0..]\n      lbAllocValue = assign lbValue $ stackAlloc r idx EIR.Value\n      ubAllocValue = assign ubValue $ stackAlloc r idx EIR.Value\n      -- TODO stack allocate all values so they are not computed twice?\n      lbValuesWithCols = [(nr, x) | nr <- columnNrs, let x = constrain (lit 0x00000000) nr]\n      ubValuesWithCols = [(nr, x) | nr <- columnNrs, let x = constrain (lit 0xffffffff) nr]\n      lbAssignStmts = map (\\(i, val) -> assign (fieldAccess lbValue i) val) lbValuesWithCols\n      ubAssignStmts = map (\\(i, val) -> assign (fieldAccess ubValue i) val) ubValuesWithCols\n  pure (block $ lbAllocValue : ubAllocValue : lbAssignStmts <> ubAssignStmts, lbValue, ubValue)\n  where\n    constrain bound col =\n      case find (\\(NormalizedEquality a' col' _) -> a == a' && col == col') eqs of\n        Nothing ->\n          bound\n        Just (NormalizedEquality _ _ ra) ->\n          lowerConstrainValue bound ra\n      -- let NormalizedEquality _ _ ra = fromJust $ find (\\(NormalizedEquality a' col' _) -> a == a' && col == col') eqs\n      --  in lowerConstrainValue bound ra\n\nlowerConstrainValue :: CodegenM EIR -> RA -> CodegenM EIR\nlowerConstrainValue bound = \\case\n  RA.Lit _ x ->\n    lit x\n  RA.Undef _ ->\n    bound\n  RA.ColumnIndex _ a' col' ->\n    fieldAccess (lookupAlias a') col'\n  RA.PrimOp _ (RA.BuiltinOp op) [lhs, rhs] ->\n    mkArithOp op (lowerConstrainValue bound lhs) (lowerConstrainValue bound rhs)\n  RA.PrimOp _ (RA.ExternOp opName) args ->\n    mkExternOp opName $ map (lowerConstrainValue bound) args\n  _ -> panic \"Unsupported initial value while lowering to RA\"\n\nforEachRelation :: CodegenM EIR -> ((Relation, Index) -> CodegenM EIR -> CodegenM EIR) -> CodegenM [CodegenM EIR]\nforEachRelation program f = do\n  cis <- relInfo . cgInfo <$> getLowerState\n  pure $ zipWith doCall [1..] cis\n  where\n    doCall fieldOffset relationInfo =\n      f relationInfo (fieldAccess program fieldOffset)\n\nrelationUnaryFn :: Relation -> EIR.Function -> CodegenM [CodegenM EIR]\nrelationUnaryFn r fn' = forEachIndex r $ \\idx -> do\n  call r idx fn' [lookupRelationByIndex r idx]\n\n-- NOTE: assumes r1 and r2 have same underlying representation\n-- (guaranteed by earlier compiler stages)\nrelationBinFn :: Relation -> Relation -> EIR.Function -> CodegenM [CodegenM EIR]\nrelationBinFn r1 r2 fn' = forEachIndex r1 $ \\idx -> do\n  call r1 idx fn'\n    [ lookupRelationByIndex r1 idx\n    , lookupRelationByIndex r2 idx\n    ]\n\nforEachIndex :: Relation\n             -> (Index -> CodegenM EIR)\n             -> CodegenM [CodegenM EIR]\nforEachIndex r f = do\n  indices <- indexesForRelation r\n  pure $ map f indices\n\ngetContainerInfos :: IndexMap -> TypedefInfo -> [((Relation, Index), M.Metadata)]\ngetContainerInfos indexMap typedefInfo = containerInfos'\n  where\n    combinations r idxs =\n      (r,) <$> Set.toList idxs\n    toContainerInfo r idx =\n      let r' = stripIdPrefixes r\n          meta = M.mkMeta idx $ fromJust $ Map.lookup r' typedefInfo\n       in ((r, idx), meta)\n    storesList = Map.foldMapWithKey combinations indexMap\n    containerInfos' = map (uncurry toContainerInfo) storesList\n\n-- NOTE: only use this index for a total search (all columns constrained)\nmkFindIndex :: [a] -> Index\nmkFindIndex =\n  Index . zipWith const [0..]\n\nindexesForRelation :: Relation -> CodegenM [Index]\nindexesForRelation r =\n  Set.toList . fromJust . Map.lookup r . idxMap <$> getLowerState\n"
  },
  {
    "path": "lib/Eclair/RA/Transforms/HoistConstraints.hs",
    "content": "module Eclair.RA.Transforms.HoistConstraints\n  ( transform\n  ) where\n\nimport Eclair.Transform\nimport Eclair.RA.IR\nimport Eclair.Comonads\nimport Eclair.Common.Id\nimport Eclair.Common.Location (NodeId(..))\nimport Data.List (partition)\n\n\ndata HoistState\n  = HoistState\n  { seenAliases :: [Id]\n  , remainingConstraints :: [(NodeId, [Id], RA)]\n  }\n\ntransform :: Transform RA RA\ntransform =\n  let beginState = HoistState mempty mempty\n   in Transform $ usingReaderT beginState . rewrite\n  where\n    rewrite = gcata (distribute collectAliases collectConstraints) hoistConstraints\n\n    distribute :: Corecursive t\n               => (Base t a -> a)\n               -> (Base t (t, a, b) -> b)\n               -> (Base t (Quad t a b c) -> Quad t a b (Base t c))\n    distribute f g m =\n      let base_t_t = map qFirst m\n          base_t_a = map qSecond m\n          base_t_tab = map (\\q -> (qFirst q, qSecond q, qThird q)) m\n          base_t_c = map qFourth m\n       in Quad (embed base_t_t) (f base_t_a) (g base_t_tab) base_t_c\n\n    collectAliases = \\case\n      ColumnIndexF _ alias _ -> one alias\n      raf -> fold raf\n\n    collectConstraints = \\case\n      IfF nodeId condition inner -> do\n        let cond = getCondition nodeId condition\n        cond : getValues inner\n      raf ->\n        foldMap getValues raf\n      where\n        getCondition nodeId (ra, aliases, _) = (nodeId, aliases, ra)\n        getValues (_, _, values) = values\n\n    hoistConstraints = \\case\n      SearchF nodeId r a cs inner -> do\n        isFirstSearch <- asks (null . seenAliases)\n        let innerConstraints = qThird inner\n            withUpdatedEnv = local $\n              if isFirstSearch\n                then const $ HoistState [a] innerConstraints\n                else \\s -> s { seenAliases = a : seenAliases s }\n\n        withUpdatedEnv $ do\n          HoistState aliases constraints <- ask\n          -- partition all the things!\n          let (covered, rest) = partition (coversAllAliases aliases) constraints\n              (indexable, nonIndexable) = partition supportsIndex covered\n              (relationChecks, nonRelationChecks) = partition dependsOnARelation nonIndexable\n              cs' = map qFirst cs <> map (\\(_, _, c) -> c) indexable\n              addNonRelationConstraints =\n                nonRelationChecks\n                & map (\\(nodeId', _, cond) -> If nodeId' cond)\n                & foldl' (.) id\n              addNonIndexableConstraints =\n                relationChecks\n                & map (\\(nodeId', _, cond) -> If nodeId' cond)\n                & foldl' (.) id\n\n          let transformSearch =\n                addNonRelationConstraints\n                . Search nodeId r a cs'\n                . addNonIndexableConstraints\n          local (\\s -> s { remainingConstraints = rest }) $ do\n            transformSearch <$> qFourth inner\n\n      ProjectF nodeId r vals -> do\n        -- NOTE: remaining conditions are not removed here, to support multiple projections in the future.\n        remaining <- asks (map (\\(nodeId', _, cond) -> (nodeId', cond)) . remainingConstraints)\n        projectStmt <- Project nodeId r <$> traverse qFourth vals\n        pure $ foldr (\\(nodeId', cond) inner -> If nodeId' cond inner) projectStmt remaining\n\n      IfF _ _ inner ->\n        -- NOTE: constraints are already in the state and handled in\n        -- project and search, so we don't do anything here.\n        qFourth inner\n\n      raf ->\n        embed <$> traverse qFourth raf\n\n    coversAllAliases aliases (_, as, _) =\n      all (`elem` aliases) as\n\n    supportsIndex = \\case\n      (_, _, ra) -> isIndexable ra\n\n    isIndexable = \\case\n      CompareOp _ op (ColumnIndex _ a1 _) (ColumnIndex _ a2 _) ->\n        isIndexableOp op && a1 /= a2\n      -- Only other relations or constants are allowed to appear in a value.\n      -- Other relations besides the current alias are \"constant\" due to the\n      -- way the algorithm works.\n      CompareOp _ op (ColumnIndex _ a1 _) value ->\n        isIndexableOp op && a1 `notElem` cata collectAliases value\n      CompareOp _ op value (ColumnIndex _ a1 _) ->\n        isIndexableOp op && a1 `notElem` cata collectAliases value\n      _ ->\n        False\n\n    -- TODO add pass for RA so <= and >= can be used in an index\n    isIndexableOp = (== Equals)\n\n    dependsOnARelation (_, _, ra) =\n      let dependencies = flip cata ra $ \\case\n            ColumnIndexF _ a _ -> [a]\n            raf -> fold raf\n       in not $ null dependencies\n"
  },
  {
    "path": "lib/Eclair/RA/Transforms.hs",
    "content": "module Eclair.RA.Transforms\n  ( simplify\n  ) where\n\nimport Eclair.Common.Location (NodeId(..))\nimport Eclair.RA.IR\nimport Eclair.Transform\nimport qualified Eclair.RA.Transforms.HoistConstraints as HoistConstraints\n\nsimplify :: RA -> RA\nsimplify = runTransform (NodeId 0)\n  HoistConstraints.transform\n"
  },
  {
    "path": "lib/Eclair/Souffle/IR.hs",
    "content": "module Eclair.Souffle.IR\n  ( Souffle(..)\n  , ConversionError(..)\n  , toSouffle\n  ) where\n\nimport qualified Eclair.AST.IR as AST\nimport Prettyprinter\nimport Eclair.Common.Pretty\nimport Eclair.Common.Literal\nimport Eclair.Common.Id\nimport Eclair.Common.Location (NodeId)\nimport Eclair.Common.Operator\n\ntype AST = AST.AST\n\ndata Type\n  = Unsigned\n  | Symbol\n\ndata UsageMode\n  = Input\n  | Output\n\ndata Souffle\n  = Lit Literal\n  | Var Id\n  | BinOp ArithmeticOp Souffle Souffle\n  | Constraint LogicalOp Souffle Souffle\n  | Rule Id [Souffle] [Souffle]\n  | Not Souffle\n  | Atom Id [Souffle]\n  | DeclareType Id [(Maybe Id, Type)]\n  | DeclareUsage Id UsageMode\n  | Module [Souffle]\n\ndata ConversionError loc\n  = HoleNotSupported loc\n  | UnsupportedType loc AST.Type\n  | UnsupportedCase loc\n  deriving Functor\n\ntoSouffle :: AST -> Either (ConversionError NodeId) Souffle\ntoSouffle = map Module . cata f\n  where\n    f :: AST.ASTF (Either (ConversionError NodeId) [Souffle])\n      -> Either (ConversionError NodeId) [Souffle]\n    f = \\case\n      AST.ModuleF _ decls -> do\n        map mconcat $ sequence decls\n      AST.DeclareTypeF nodeId name tys usageMode -> do\n        souffleTys <- traverse (toSouffleArg nodeId) tys\n        let declType = DeclareType name souffleTys\n            usageDecls = case usageMode of\n              AST.Input ->\n                one $ DeclareUsage name Input\n              AST.Output ->\n                one $ DeclareUsage name Output\n              AST.InputOutput ->\n                [ DeclareUsage name Input\n                , DeclareUsage name Output\n                ]\n              AST.Internal ->\n                mempty\n        pure $ declType : usageDecls\n      AST.AtomF _ name args -> do\n        args' <- mconcat <$> sequence args\n        pure $ one $ Atom name args'\n      AST.RuleF _ name args clauses -> do\n        args' <- mconcat <$> sequence args\n        clauses' <- mconcat <$> sequence clauses\n        pure $ one $ Rule name args' clauses'\n      AST.VarF _ name ->\n        pure $ one $ Var name\n      AST.LitF _ lit ->\n        pure $ one $ Lit lit\n      AST.HoleF nodeId ->\n        throwError $ HoleNotSupported nodeId\n      AST.BinOpF _ op lhs rhs -> do\n        lhs' <- lhs\n        rhs' <- rhs\n        pure $ BinOp op <$> lhs' <*> rhs'\n      AST.ConstraintF _ op lhs rhs -> do\n        lhs' <- lhs\n        rhs' <- rhs\n        pure $ Constraint op <$> lhs' <*> rhs'\n      AST.NotF _ inner -> do\n        inner' <- inner\n        pure $ Not <$> inner'\n      astf ->\n        let nodeId = AST.getNodeIdF astf\n        in throwError $ UnsupportedCase nodeId\n\n    toSouffleArg :: NodeId -> (Maybe Id, AST.Type) -> Either (ConversionError NodeId) (Maybe Id, Type)\n    toSouffleArg nodeId = \\case\n      (mName, AST.U32) -> pure (mName, Unsigned)\n      (mName, AST.Str) -> pure (mName, Symbol)\n      ty -> throwError $ UnsupportedType nodeId $ snd ty\n\n\ninstance Pretty Type where\n  pretty = \\case\n    Unsigned -> \"unsigned\"\n    Symbol -> \"symbol\"\n\ndata RenderPosition = TopLevel | Nested\n\ninstance Pretty Souffle where\n  pretty souffleIR = runReader (pretty' souffleIR) TopLevel\n    where\n      pretty' = \\case\n        Lit x ->\n          pure $ pretty x\n        Var v ->\n          pure $ pretty v\n        BinOp op lhs rhs -> do\n          lhs' <- pretty' lhs\n          rhs' <- pretty' rhs\n          pure $ parens $ lhs' <+> pretty op <+> rhs'\n        Constraint op lhs rhs -> do\n          lhs' <- pretty' lhs\n          rhs' <- pretty' rhs\n          pure $ lhs' <+> pretty op <+> rhs'\n        Not clause ->\n          (\"!\" <>) <$> pretty' clause\n        Atom name values -> do\n          end <- ask <&> \\case\n            TopLevel -> \".\"\n            Nested -> mempty\n          values' <- traverse pretty' values\n          pure $ pretty name <> parens (withCommas values') <> end\n        Rule name values clauses -> do\n          (values', clauses') <- local (const Nested) $ do\n            (,) <$> traverse pretty' values <*> traverse pretty' clauses\n          let separators = replicate (length clauses - 1) \",\" <> [\".\"]\n          pure $ pretty name <> parens (withCommas values') <+> \":-\" <> hardline <>\n                indent 2 (vsep (zipWith (<>) clauses' separators))\n        DeclareType name args -> do\n          let defaultArgNames = map (\\col -> Id $ \"arg_\" <> show col) [0 :: Int ..]\n              argNames = zipWith prettyArg args defaultArgNames\n              argDocs = zipWith (\\argName arg -> argName <> \":\" <+> pretty (snd arg)) argNames args\n          pure $ \".decl\" <+> pretty name <> parens (withCommas argDocs)\n          where\n            prettyArg arg defaultArg =\n              pretty $ fromMaybe defaultArg $ fst arg\n        DeclareUsage name usageMode ->\n          pure $ case usageMode of\n            Input -> \".input\" <+> pretty name\n            Output -> \".output\" <+> pretty name\n        Module decls -> do\n          decls' <- traverse pretty' decls\n          pure $ vsep $ intersperse mempty decls'\n"
  },
  {
    "path": "lib/Eclair/Transform.hs",
    "content": "module Eclair.Transform\n  ( Transform(..)\n  , pureTransform\n  , TransformM\n  , runTransform\n  , fixTransform\n  , freshNodeId\n  , RewriteRule\n  , RewriteRuleT\n  ) where\n\nimport Eclair.Common.Location (NodeId(..))\n\n\n-- A helper monad that provides a fresh supply of unused node IDs.\nnewtype TransformM a\n  = TransformM (State NodeId a)\n  deriving (Functor, Applicative, Monad) via State NodeId\n\n-- | Generates a fresh 'NodeId'.\nfreshNodeId :: TransformM NodeId\nfreshNodeId = TransformM $ do\n  node <- get\n  modify $ \\(NodeId y) -> NodeId (y + 1)\n  pure node\n\n-- | The main type in this module. A transform is an effectful function from\n-- one type to another. Usually the type variables 'a' and 'b' represent IRs in\n-- the compiler. (These IRs can potentially be the same, which can be useful\n-- for creating \"rewrite rules\"). Transforms can be composed together using the\n-- instances and functions defined in this module.\nnewtype Transform a b\n  = Transform (a -> TransformM b)\n  deriving (Semigroup, Monoid) via Ap (Kleisli TransformM a) b\n  deriving (Category, Arrow) via Kleisli TransformM\n\n-- | Helper function for creating a transform that doesn't require any effects.\npureTransform :: (a -> b) -> Transform a b\npureTransform f =\n  Transform $ pure . f\n\n-- | Converts a 'Transform' to an equivalent pure function.\n--   The 'NodeId' that is passed in is used as a starting point for generating\n--   additional IDs during the transform (if necessary).\nrunTransform :: NodeId -> Transform ir1 ir2 -> ir1 -> ir2\nrunTransform nodeId (Transform f) ir1 =\n  runTransformM (f ir1)\n  where\n    runTransformM :: TransformM a -> a\n    runTransformM (TransformM m) =\n      evalState m nodeId\n\n-- | A function that recursively keeps applying a 'Transform' until a fixpoint\n--   is reached and no more changes occur.\nfixTransform :: Eq ir => Transform ir ir -> Transform ir ir\nfixTransform (Transform f) =\n  Transform $ fix $ \\recur ir -> do\n    ir' <- f ir\n    if ir' == ir\n      then pure ir'\n      else recur ir'\n\n-- | Helper type synonym for a rewrite rule, making use of the recursion-schemes library.\n--   This is the simplest form of rewrite rule, which allows no extra effects\n--   except for the state managed by the 'Transform' itself.\ntype RewriteRule ir = Base ir (TransformM ir) -> TransformM ir\n\n-- | Helper type synonym for a rewrite rule, making use of the recursion-schemes library.\n--   This type of rewrite rule allows adding additional effects via the\n--   monad transformer 't'.\ntype RewriteRuleT t ir = Base ir (t TransformM ir) -> t TransformM ir\n\n"
  },
  {
    "path": "lib/Eclair/TypeSystem.hs",
    "content": "module Eclair.TypeSystem\n  ( Type(..)\n  , TypeError(..)\n  , Context(..)\n  , getContextLocation\n  , TypeInfo(..)\n  , DefinitionType(..)\n  , TypedefInfo\n  , ExternDefInfo\n  , typeCheck\n  ) where\n\nimport qualified Data.Map as Map\nimport qualified Data.IntMap as IntMap\nimport qualified Data.DList as DList\nimport Data.DList (DList)\nimport qualified Data.DList.DNonEmpty as DNonEmpty\nimport Data.DList.DNonEmpty (DNonEmpty)\nimport Control.Monad.Extra\nimport Eclair.AST.IR\nimport Eclair.Common.Id\nimport Eclair.Common.Location (NodeId)\n\n-- NOTE: This module contains a lot of partial functions due to the fact\n-- that the rest of the compiler relies heavily on recursion-schemes.\n-- This is however one place where I have not figured out how to apply a\n-- recursion-scheme here.. yet!\n--\n-- TODO: Maybe a variant of a mutumorphism might work here?\n\ndata DefinitionType\n  = ConstraintType [Type]\n  | FunctionType [Type] Type\n  deriving (Eq, Show)\n\ntype ExternDefInfo = Map Id DefinitionType\n\ntype TypedefInfo = Map Id [Type]\n\ndata TypeInfo\n  = TypeInfo\n  { infoTypedefs :: TypedefInfo  -- NOTE: only typedefs are needed, no external definitions\n  , resolvedTypes :: Map NodeId Type\n  } deriving Show\n\ninstance Semigroup TypeInfo where\n  TypeInfo tdInfo1 resolved1 <> TypeInfo tdInfo2 resolved2 =\n    TypeInfo (tdInfo1 <> tdInfo2)  (resolved1 <> resolved2)\n\ninstance Monoid TypeInfo where\n  mempty =\n    TypeInfo mempty mempty\n\ndata Context loc\n  = WhileChecking loc\n  | WhileInferring loc\n  | WhileUnifying loc\n  deriving (Eq, Ord, Show, Functor)\n\n-- NOTE: for now, no actual types are checked since everything is a u32.\ndata TypeError loc\n  = UnknownConstraint loc Id\n  | UnknownFunction loc Id\n  | ArgCountMismatch Id (loc, Int) (loc, Int)\n  | TypeMismatch loc Type Type (NonEmpty (Context loc))  -- 1st type is actual, 2nd is expected\n  | UnificationFailure Type Type (NonEmpty (Context loc))\n  | HoleFound loc (NonEmpty (Context loc)) Type (Map Id Type)\n  -- 1st is error location, 2nd is definition location.\n  | UnexpectedFunctionType loc loc\n  | UnexpectedConstraintType loc loc\n  deriving (Eq, Ord, Show, Functor)\n\n-- Only used internally in this module.\ntype Ctx = Context NodeId\ntype TypeErr = TypeError NodeId\n\ntype DefMap = Map Id (NodeId, DefinitionType)\ntype UnresolvedHole = Type -> Map Id Type -> TypeErr\n\ndata Env\n  = Env\n  { defs :: DefMap\n  , tcContext :: DNonEmpty Ctx\n  }\n\n-- State used to report type information back to the user (via LSP)\ndata TrackingState\n  = TrackingState\n  { directlyResolvedTypes :: Map NodeId Type  -- literals are immediately resolved\n  , trackedVariables :: [(Id, NodeId)]\n  }\n\ndata CheckState\n  = CheckState\n  { typeEnv :: Map Id Type\n  , substitution :: IntMap Type\n  , varCounter :: Int\n  , errors :: DList TypeErr\n  , unresolvedHoles :: DList (Type, UnresolvedHole)\n  -- The tracking state is not needed for the typechecking algorithm,\n  -- but is used to report information back to the user (via LSP).\n  , trackingState :: TrackingState\n  }\n\nnewtype TypeCheckM a =\n  TypeCheckM (RWS Env () CheckState a)\n  deriving (Functor, Applicative, Monad, MonadState CheckState, MonadReader Env)\n  via (RWS Env () CheckState)\n\n\ntypeCheck :: AST -> Either [TypeErr] TypeInfo\ntypeCheck ast\n  | null typeErrors = pure $ TypeInfo (map snd typeDefMap) resolvedTys\n  | otherwise = throwError typeErrors\n  where\n    (typeDefMap, externDefMap) = cata (combine extractTypedefs extractExternDefs) ast\n    defMap = map (map ConstraintType) typeDefMap <> externDefMap\n    (typeErrors, resolvedTys) = checkDecls defMap ast\n\n    combine f g = f . map fst &&& g . map snd\n\n    extractTypedefs = \\case\n      DeclareTypeF nodeId name args _ ->\n        one (name, (nodeId, map snd args))\n      AtomF {} -> mempty\n      RuleF {} -> mempty\n      astf -> fold astf\n\n    extractExternDefs = \\case\n      ExternDefinitionF nodeId name args mRetTy ->\n        let tys = map snd args\n        in one (name, (nodeId, maybe (ConstraintType tys) (FunctionType tys) mRetTy))\n      AtomF {} -> mempty\n      RuleF {} -> mempty\n      astf -> fold astf\n\n-- TODO: try to merge with checkDecl?\ncheckDecls :: DefMap -> AST -> ([TypeErr], Map NodeId Type)\ncheckDecls defMap = \\case\n  Module _ decls ->\n    -- NOTE: By using runM once per decl, each decl is checked with a clean state\n    let results = map (\\d -> runM (beginContext d) defMap $ typecheckDecl d) decls\n     in bimap fold fold $ partitionEithers results\n  _ ->\n    panic \"Unexpected AST node in 'checkDecls'\"\n  where\n    beginContext d = WhileChecking (getNodeId d)\n    typecheckDecl d = do\n      checkDecl d\n      processUnresolvedHoles\n\ncheckDecl :: AST -> TypeCheckM ()\ncheckDecl ast = case ast of\n  DeclareType {} ->\n    pass\n  ExternDefinition {} ->\n    pass\n  Atom nodeId name args -> do\n    ctx <- getContext\n    -- TODO find better way to update context only for non-top level atoms\n    let isTopLevelFact = isTopLevel ctx\n        updateCtx = if isTopLevelFact then id else addCtx\n    updateCtx $ do\n      lookupRelationType name >>= \\case\n        Just (nodeId', ConstraintType types) -> do\n          checkArgCount name (nodeId', types) (nodeId, args)\n          zipWithM_ checkExpr args types\n        Just (nodeId', FunctionType {}) -> do\n          emitError $ UnexpectedFunctionType nodeId nodeId'\n        Nothing ->\n          emitError $ UnknownConstraint nodeId name\n\n  Rule nodeId name args clauses -> do\n    lookupRelationType name >>= \\case\n      Just (nodeId', ConstraintType types) -> do\n        checkArgCount name (nodeId', types) (nodeId, args)\n        zipWithM_ checkExpr args types\n      Just (nodeId', FunctionType {}) -> do\n        emitError $ UnexpectedFunctionType nodeId nodeId'\n      Nothing ->\n        emitError $ UnknownConstraint nodeId name\n\n    traverse_ checkDecl clauses\n\n  Constraint nodeId op lhs rhs -> addCtx $ do\n    if isEqualityOp op\n      then do\n        lhsTy <- inferExpr lhs\n        rhsTy <- inferExpr rhs\n        -- NOTE: Because inferred types of vars can contain unification variables,\n        -- we need to try and unify them.\n        unifyType nodeId lhsTy rhsTy\n      else do\n        -- Comparison => both sides need to be numbers\n        checkExpr lhs U32\n        checkExpr rhs U32\n  Not _ clause -> addCtx $ do\n    checkDecl clause\n  _ ->\n    panic \"Unexpected case in 'checkDecl'\"\n  where\n    addCtx = addContext (WhileChecking $ getNodeId ast)\n    isTopLevel = (== 1) . length\n\ncheckArgCount :: Id -> (NodeId, [Type]) -> (NodeId, [AST]) -> TypeCheckM ()\ncheckArgCount name (nodeIdTypedef, types) (nodeId, args) = do\n  let actualArgCount = length args\n      expectedArgCount = length types\n  when (actualArgCount /= expectedArgCount) $\n    emitError $ ArgCountMismatch name (nodeIdTypedef, expectedArgCount) (nodeId, actualArgCount)\n\ncheckExpr :: AST -> Type -> TypeCheckM ()\ncheckExpr ast expectedTy = do\n  let nodeId = getNodeId ast\n  addContext (WhileChecking nodeId) $ case ast of\n    l@Lit {} -> do\n      actualTy <- inferExpr l\n      -- NOTE: No need to call 'unifyType', types of literals are always concrete types.\n      when (actualTy /= expectedTy) $ do\n        ctx <- getContext\n        emitError $ TypeMismatch nodeId actualTy expectedTy ctx\n    PWildcard {} ->\n      -- NOTE: no checking happens for wildcards, since they always refer to\n      -- unique vars (and thus cannot cause type errors)\n      trackDirectlyResolvedType nodeId expectedTy\n    Var _ var -> do\n      trackVariable nodeId var\n      lookupVarType var >>= \\case\n        Nothing ->\n          -- TODO: also store in context/state a variable was bound here for better errors?\n          bindVar var expectedTy\n        Just actualTy -> do\n          when (actualTy /= expectedTy) $ do\n            ctx <- getContext\n            emitError $ TypeMismatch nodeId actualTy expectedTy ctx\n    Hole {} -> do\n      holeTy <- emitHoleFoundError nodeId\n      unifyType nodeId holeTy expectedTy\n    BinOp _ _ lhs rhs -> do\n      -- Arithmetic expressions always need to be numbers.\n      checkExpr lhs U32\n      checkExpr rhs U32\n    Atom _ name args -> do\n      lookupRelationType name >>= \\case\n        Just (nodeId', FunctionType types actualRetTy) -> do\n          checkArgCount name (nodeId', types) (nodeId, args)\n          zipWithM_ checkExpr args types\n          when (actualRetTy /= expectedTy) $ do\n            ctx <- getContext\n            emitError $ TypeMismatch nodeId actualRetTy expectedTy ctx\n        Just (nodeId', ConstraintType {}) -> do\n          emitError $ UnexpectedConstraintType nodeId nodeId'\n        Nothing ->\n          emitError $ UnknownFunction nodeId name\n    e -> do\n      -- Basically an unexpected / unhandled case => try inferring as a last resort.\n      actualTy <- inferExpr e\n      unifyType (getNodeId e) actualTy expectedTy\n\ninferExpr :: AST -> TypeCheckM Type\ninferExpr ast = do\n  let nodeId = getNodeId ast\n  addContext (WhileInferring nodeId) $ case ast of\n    Lit _ lit -> do\n      let ty = case lit of\n            LNumber {} -> U32\n            LString {} -> Str\n      trackDirectlyResolvedType nodeId ty\n      pure ty\n    Var _ var -> do\n      trackVariable nodeId var\n      lookupVarType var >>= \\case\n        Nothing -> do\n          ty <- freshType\n          -- This introduces potentially a unification variable into the type environment, but we need this\n          -- for complicated rule bodies where a variable occurs in more than one place.\n          -- (Otherwise a variable is treated as new each time).\n          bindVar var ty\n          pure ty\n        Just ty ->\n          pure ty\n    Hole {} ->\n      emitHoleFoundError nodeId\n    BinOp _ _ lhs rhs -> do\n      -- Arithmetic expressions always need to be numbers.\n      checkExpr lhs U32\n      checkExpr rhs U32\n      pure U32\n    Atom _ name args -> do\n      lookupRelationType name >>= \\case\n        Just (nodeId', FunctionType types retTy) -> do\n          checkArgCount name (nodeId', types) (nodeId, args)\n          zipWithM_ checkExpr args types\n          pure retTy\n        Just (nodeId', ConstraintType {}) -> do\n          emitError $ UnexpectedConstraintType nodeId nodeId'\n          -- We generate a fresh type which will always unify, but typechecking will fail anyway\n          freshType\n        Nothing -> do\n          emitError $ UnknownFunction nodeId name\n          -- We generate a fresh type which will always unify, but typechecking will fail anyway\n          freshType\n    _ ->\n      panic \"Unexpected case in 'inferExpr'\"\n\nrunM :: Ctx -> DefMap -> TypeCheckM a -> Either [TypeErr] (Map NodeId Type)\nrunM ctx defMap action =\n  let (TypeCheckM m) = action *> computeTypeInfoById\n      trackState = TrackingState mempty mempty\n      tcState = CheckState mempty mempty 0 mempty mempty trackState\n      env = Env defMap (pure ctx)\n      (typeInfoById, endState, _) = runRWS m env tcState\n      errs = toList . errors $ endState\n   in if null errs then Right typeInfoById else Left errs\n\nemitError :: TypeErr -> TypeCheckM ()\nemitError err =\n  modify $ \\s -> s { errors = DList.snoc (errors s) err }\n\nbindVar :: Id -> Type -> TypeCheckM ()\nbindVar var ty =\n  modify $ \\s -> s { typeEnv = Map.insert var ty (typeEnv s) }\n\nlookupRelationType :: Id -> TypeCheckM (Maybe (NodeId, DefinitionType))\nlookupRelationType name =\n  asks (Map.lookup name . defs)\n\n-- A variable can either be a concrete type, or a unification variable.\n-- In case of unification variable, try looking it up in current substitution.\n-- As a result, this will make it so variables in a rule body with same name have the same type.\n-- NOTE: if this ends up not being powerful enough, need to upgrade to SCC + a solver for each group of \"linked\" variables.\nlookupVarType :: Id -> TypeCheckM (Maybe Type)\nlookupVarType var = do\n  (maybeVarTy, subst) <- gets (Map.lookup var . typeEnv &&& substitution)\n  case maybeVarTy of\n    Just (TUnknown u) -> pure $ IntMap.lookup u subst\n    maybeTy -> pure maybeTy\n\n-- Generates a fresh unification variable.\nfreshType :: TypeCheckM Type\nfreshType = do\n  x <- gets varCounter\n  modify $ \\s -> s { varCounter = varCounter s + 1 }\n  pure $ TUnknown x\n\n-- | Tries to unify 2 types, emitting an error in case it fails to unify.\nunifyType :: NodeId -> Type -> Type -> TypeCheckM ()\nunifyType nodeId t1 t2 = addContext (WhileUnifying nodeId) $ do\n  subst <- gets substitution\n  unifyType' (substituteType subst t1) (substituteType subst t2)\n  where\n    unifyType' ty1 ty2 =\n      case (ty1, ty2) of\n        (U32, U32) ->\n          pass\n        (Str, Str) ->\n          pass\n        (TUnknown x, TUnknown y) | x == y ->\n          pass\n        (TUnknown u, ty) ->\n          updateSubst u ty\n        (ty, TUnknown u) ->\n          updateSubst u ty\n        _ -> do\n          ctx <- getContext\n          emitError $ UnificationFailure ty1 ty2 ctx\n\n    -- Update the current substitutions\n    updateSubst :: Int -> Type -> TypeCheckM ()\n    updateSubst u ty =\n      modify $ \\s ->\n        s { substitution = IntMap.insert u ty (substitution s) }\n\n-- Recursively applies a substitution to a type\nsubstituteType :: IntMap Type -> Type -> Type\nsubstituteType subst = \\case\n  U32 ->\n    U32\n  Str ->\n    Str\n  TUnknown unknown ->\n    case IntMap.lookup unknown subst of\n      Nothing ->\n        TUnknown unknown\n      Just (TUnknown unknown') | unknown == unknown' ->\n        TUnknown unknown\n      Just ty ->\n        substituteType subst ty\n\ncomputeTypeInfoById :: TypeCheckM (Map NodeId Type)\ncomputeTypeInfoById = do\n  ts <- gets trackingState\n  let vars = Map.toList $ Map.fromListWith (<>) $ one <<$>> trackedVariables ts\n  resolvedVarTypes <- flip concatMapM vars $ \\(var, nodeIds) -> do\n    varTy <- lookupVarType var\n    pure $ mapMaybe (\\nodeId -> (nodeId,) <$> varTy) nodeIds\n\n  pure $ directlyResolvedTypes ts <> Map.fromList resolvedVarTypes\n\naddContext :: Ctx -> (TypeCheckM a -> TypeCheckM a)\naddContext ctx = local $ \\env ->\n  env { tcContext = tcContext env `DNonEmpty.snoc` ctx }\n\ngetContext :: TypeCheckM (NonEmpty Ctx)\ngetContext =\n  asks (DNonEmpty.toNonEmpty . tcContext)\n\ngetContextLocation :: Context loc -> loc\ngetContextLocation = \\case\n  WhileChecking loc -> loc\n  WhileInferring loc -> loc\n  WhileUnifying loc -> loc\n\nemitHoleFoundError :: NodeId -> TypeCheckM Type\nemitHoleFoundError nodeId = do\n  ty <- freshType   -- We generate a fresh type, and use this later to figure out the type of the hole.\n  ctx <- getContext\n  modify' $ \\s ->\n    s { unresolvedHoles = DList.snoc (unresolvedHoles s) (ty, HoleFound nodeId ctx) }\n  pure ty\n\nprocessUnresolvedHoles :: TypeCheckM ()\nprocessUnresolvedHoles = do\n  holes <- gets (toList . unresolvedHoles)\n  unless (null holes) $ do\n    (env, subst) <- gets (typeEnv &&& substitution)\n    let solvedEnv = map (substituteType subst) env\n    forM_ holes $ \\(holeTy, hole) ->\n      emitError $ hole (substituteType subst holeTy) solvedEnv\n\n    modify' $ \\s -> s { unresolvedHoles = mempty }\n\ntrackDirectlyResolvedType :: NodeId -> Type -> TypeCheckM ()\ntrackDirectlyResolvedType nodeId ty = do\n  ts <- gets trackingState\n  let ts' = ts { directlyResolvedTypes = directlyResolvedTypes ts <> one (nodeId, ty) }\n  modify $ \\s -> s { trackingState = ts' }\n\ntrackVariable :: NodeId -> Id -> TypeCheckM ()\ntrackVariable nodeId var = do\n  ts <- gets trackingState\n  let ts' = ts { trackedVariables = (var, nodeId) : trackedVariables ts }\n  modify $ \\s -> s { trackingState = ts' }\n"
  },
  {
    "path": "lib/Eclair.hs",
    "content": "{-# LANGUAGE GADTs, StandaloneDeriving #-}\n\nmodule Eclair\n  ( parse\n  , semanticAnalysis\n  , typeCheck\n  , emitDiagnostics\n  , emitTransformedAST\n  , emitRA\n  , emitTransformedRA\n  , emitEIR\n  , emitLLVM\n  , emitSouffle\n  , Parameters(..)\n  , EclairError(..)\n  , handleErrorsCLI\n  ) where\n\nimport Control.Exception\nimport Eclair.AST.Lower\nimport Eclair.RA.Lower\nimport Eclair.EIR.Lower\nimport Eclair.Parser\nimport Eclair.Common.Pretty\nimport Eclair.Error\nimport Eclair.Common.Id\nimport Eclair.Common.Location\nimport Eclair.Common.Extern\nimport Eclair.Common.Config (Target(..))\nimport Eclair.AST.IR\nimport Eclair.AST.Transforms (StringMap)\nimport Eclair.Souffle.IR\nimport qualified Eclair.AST.Transforms as AST\nimport qualified Eclair.RA.IR as RA\nimport qualified Eclair.RA.Transforms as RA\nimport qualified Eclair.EIR.IR as EIR\nimport qualified Eclair.TypeSystem as TS\nimport qualified Eclair.AST.Analysis as SA\nimport LLVM.Codegen (Module, ppllvm)\nimport qualified Rock\nimport Data.GADT.Compare\nimport Data.Some\nimport Data.Type.Equality\n\n\ntype RA = RA.RA\ntype EIR = EIR.EIR\n\n\ndata Query a where\n  Parse :: FilePath -> Query (AST, NodeId, SpanMap)\n  RunSemanticAnalysis :: FilePath -> Query SA.SemanticInfo\n  Typecheck :: FilePath -> Query TS.TypeInfo\n  EmitDiagnostics :: FilePath -> Query ()\n  TransformAST :: FilePath -> Query (AST, StringMap)\n  EmitTransformedAST :: FilePath -> Query ()\n  CompileRA :: FilePath -> Query RA\n  TransformRA :: FilePath -> Query RA\n  EmitRA :: FilePath -> Query ()\n  EmitTransformedRA :: FilePath -> Query ()\n  CompileEIR :: FilePath -> Query EIR\n  EmitEIR :: FilePath -> Query ()\n  CompileLLVM :: FilePath -> Query Module\n  EmitLLVM :: FilePath -> Query ()\n  EmitSouffle :: FilePath -> Query ()\n  StringMapping :: FilePath -> Query (Map Text Word32)\n  UsageMapping :: FilePath -> Query (Map Id UsageMode)\n  ExternDefinitions :: FilePath -> Query [Extern]\n\nderiving instance Eq (Query a)\n\ninstance GEq Query where\n  geq a b = case (a, b) of\n    (Parse file1, Parse file2) | file1 == file2 -> Just Refl\n    (RunSemanticAnalysis file1, RunSemanticAnalysis file2) | file1 == file2 -> Just Refl\n    (Typecheck file1, Typecheck file2) | file1 == file2 -> Just Refl\n    (EmitDiagnostics file1, EmitDiagnostics file2) | file1 == file2 -> Just Refl\n    (TransformAST file1, TransformAST file2) | file1 == file2 -> Just Refl\n    (EmitTransformedAST file1, EmitTransformedAST file2) | file1 == file2 -> Just Refl\n    (CompileRA file1, CompileRA file2) | file1 == file2 -> Just Refl\n    (TransformRA file1, TransformRA file2) | file1 == file2 -> Just Refl\n    (EmitRA file1, EmitRA file2) | file1 == file2 -> Just Refl\n    (EmitTransformedRA file1, EmitTransformedRA file2) | file1 == file2 -> Just Refl\n    (CompileEIR file1, CompileEIR file2) | file1 == file2 -> Just Refl\n    (EmitEIR file1, EmitEIR file2) | file1 == file2 -> Just Refl\n    (CompileLLVM file1, CompileLLVM file2) | file1 == file2 -> Just Refl\n    (EmitLLVM file1, EmitLLVM file2) | file1 == file2 -> Just Refl\n    (EmitSouffle file1, EmitSouffle file2) | file1 == file2 -> Just Refl\n    (StringMapping file1, StringMapping file2) | file1 == file2 -> Just Refl\n    (UsageMapping file1, UsageMapping file2) | file1 == file2 -> Just Refl\n    (ExternDefinitions file1, ExternDefinitions file2) | file1 == file2 -> Just Refl\n    _ -> Nothing\n\nqueryFilePath :: Query a -> FilePath\nqueryFilePath = \\case\n  Parse path               -> path\n  RunSemanticAnalysis path -> path\n  Typecheck path           -> path\n  EmitDiagnostics path     -> path\n  TransformAST path        -> path\n  EmitTransformedAST path  -> path\n  CompileRA path           -> path\n  TransformRA path         -> path\n  EmitRA path              -> path\n  EmitTransformedRA path   -> path\n  CompileEIR path          -> path\n  EmitEIR path             -> path\n  CompileLLVM path         -> path\n  EmitLLVM path            -> path\n  EmitSouffle path         -> path\n  StringMapping path       -> path\n  UsageMapping path        -> path\n  ExternDefinitions path   -> path\n\nqueryEnum :: Query a -> Int\nqueryEnum = \\case\n  Parse {}               -> 0\n  RunSemanticAnalysis {} -> 1\n  Typecheck {}           -> 2\n  EmitDiagnostics {}     -> 3\n  TransformAST {}        -> 4\n  EmitTransformedAST {}  -> 5\n  CompileRA {}           -> 6\n  TransformRA {}         -> 7\n  EmitRA {}              -> 8\n  EmitTransformedRA {}   -> 9\n  CompileEIR {}          -> 10\n  EmitEIR {}             -> 11\n  CompileLLVM {}         -> 12\n  EmitLLVM {}            -> 13\n  EmitSouffle {}         -> 14\n  StringMapping {}       -> 15\n  UsageMapping {}        -> 16\n  ExternDefinitions {}   -> 17\n\ninstance Hashable (Query a) where\n  hashWithSalt salt =\n    hashWithSalt salt . (queryFilePath &&& queryEnum)\n\ninstance Hashable (Some Query) where\n  hashWithSalt salt (Some query) =\n    hashWithSalt salt query\n\ndata Parameters\n  = Parameters\n  { paramsNumCores :: Word\n  , paramsTarget :: !(Maybe Target)\n  , paramsReadSourceFile :: FilePath -> IO (Maybe Text)\n  }\n\nrules :: Rock.Task Query ()\n      -> Parameters\n      -> Rock.GenRules (Rock.Writer [EclairError] Query) Query\nrules abortOnError params (Rock.Writer query) = case query of\n  Parse path -> liftIO $ do\n    (ast, nodeId, spanMap, mParseErr) <- parseFile (paramsReadSourceFile params) path\n    pure ((ast, nodeId, spanMap), ParseErr path <$> maybeToList mParseErr)\n  RunSemanticAnalysis path -> do\n    (ast, _, spans) <- Rock.fetch (Parse path)\n    result <- liftIO $ SA.runAnalysis (paramsNumCores params) ast\n    let errs = if SA.hasSemanticErrors result\n                 then one $ SemanticErr path spans $ SA.semanticErrors result\n                 else mempty\n    pure (SA.semanticInfo result, errs)\n  Typecheck path -> do\n    (ast, _, spans) <- Rock.fetch (Parse path)\n    case TS.typeCheck ast of\n      Left err ->\n        pure (mempty, one $ TypeErr path spans err)\n      Right typeInfo ->\n        pure (typeInfo, mempty)\n  EmitDiagnostics path -> noError $ do\n    -- Just triggering these tasks collects all the corresponding errors.\n    _ <- Rock.fetch (Parse path)\n    _ <- Rock.fetch (RunSemanticAnalysis path)\n    _ <- Rock.fetch (Typecheck path)\n    pass\n  TransformAST path -> noError $ do\n    -- Need to run SA and typechecking before any transformations / lowering\n    -- to ensure we don't perform work on invalid programs.\n    -- And thanks to rock, the results will be cached anyway.\n    analysis <- Rock.fetch (RunSemanticAnalysis path)\n    _ <- Rock.fetch (Typecheck path)\n    -- Past this point, the code should be valid!\n    -- We abort if this is not the case.\n    abortOnError\n    (ast, nodeId, _) <- Rock.fetch (Parse path)\n    externDefs <- Rock.fetch (ExternDefinitions path)\n    pure $ AST.simplify nodeId externDefs analysis ast\n  EmitTransformedAST path -> noError $ do\n    (ast, _) <- Rock.fetch (TransformAST path)\n    liftIO $ putTextLn $ printDoc ast\n  StringMapping path -> noError $ do\n    (_, mapping) <- Rock.fetch (TransformAST path)\n    pure mapping\n  UsageMapping path -> noError $ do\n    (ast, _, _) <- Rock.fetch (Parse path)\n    pure $ SA.computeUsageMapping ast\n  ExternDefinitions path -> noError $ do\n    (ast, _, _) <- Rock.fetch (Parse path)\n    pure $ getExternDefs ast\n  CompileRA path -> noError $ do\n    ast <- fst <$> Rock.fetch (TransformAST path)\n    externDefs <- Rock.fetch (ExternDefinitions path)\n    pure $ compileToRA externDefs ast\n  TransformRA path -> noError $ do\n    ra <- Rock.fetch (CompileRA path)\n    pure $ RA.simplify ra\n  EmitRA path -> noError $ do\n    ra <- Rock.fetch (CompileRA path)\n    liftIO $ putTextLn $ printDoc ra\n  EmitTransformedRA path -> noError $ do\n    ra <- Rock.fetch (TransformRA path)\n    liftIO $ putTextLn $ printDoc ra\n  CompileEIR path -> noError $ do\n    stringMapping <- Rock.fetch (StringMapping path)\n    ra <- Rock.fetch (TransformRA path)\n    typeInfo <- Rock.fetch (Typecheck path)\n    pure $ compileToEIR stringMapping (TS.infoTypedefs typeInfo) ra\n  EmitEIR path -> noError $ do\n    eir <- Rock.fetch (CompileEIR path)\n    liftIO $ putTextLn $ printDoc eir\n  CompileLLVM path -> noError $ do\n    eir <- Rock.fetch (CompileEIR path)\n    stringMapping <- Rock.fetch (StringMapping path)\n    usageMapping <- Rock.fetch (UsageMapping path)\n    externDefs <- Rock.fetch (ExternDefinitions path)\n    liftIO $ compileToLLVM (paramsTarget params) stringMapping usageMapping externDefs eir\n  EmitLLVM path -> noError $ do\n    llvmModule <- Rock.fetch (CompileLLVM path)\n    liftIO $ putTextLn $ ppllvm llvmModule\n  EmitSouffle path -> do\n    (ast, _, spanMap) <- Rock.fetch (Parse path)\n    case toSouffle ast of\n      Left err ->\n        pure ((), one $ ConversionErr path spanMap err)\n      Right souffleIR -> do\n        liftIO $ putTextLn $ printDoc souffleIR\n        pure ((), mempty)\n\n-- Helper function for tasks that don't emit any errors.\nnoError :: Rock.Task Query a -> Rock.Task Query (a, [EclairError])\nnoError task =\n  (, mempty) <$> task\n\ntype CompilerM a = IO (Either [EclairError] a)\n\ndata Aborted = Aborted\n  deriving (Show, Exception)\n\nrunQuery :: Parameters -> Query a -> CompilerM a\nrunQuery params query = do\n  memoVar <- newIORef mempty\n  errRef <- newIORef mempty\n\n  let onError :: q -> [EclairError] -> Rock.Task Query ()\n      onError _ errs =\n        liftIO $ modifyIORef errRef (<> errs)\n      abortOnError = do\n        errs <- readIORef errRef\n        unless (null errs) $\n          liftIO $ throwIO Aborted\n      handleAbort =\n        handle $ \\Aborted -> do\n          errs <- readIORef errRef\n          pure $ Left errs\n      task = Rock.fetch query\n\n  handleAbort $ do\n    result <- Rock.runTask (Rock.memoise memoVar $ Rock.writer onError $ rules abortOnError params) task\n    errs <- readIORef errRef\n    pure $ if null errs then Right result else Left errs\n\nparse :: Parameters -> FilePath -> CompilerM (AST, SpanMap)\nparse params =\n  map (map (\\(ast, _, spanMap) -> (ast, spanMap))) . runQuery params . Parse\n\nsemanticAnalysis :: Parameters -> FilePath -> CompilerM SA.SemanticInfo\nsemanticAnalysis params =\n  runQuery params . RunSemanticAnalysis\n\ntypeCheck :: Parameters -> FilePath -> CompilerM TS.TypeInfo\ntypeCheck params =\n  runQuery params . Typecheck\n\nemitDiagnostics :: Parameters -> FilePath -> IO [EclairError]\nemitDiagnostics params = do\n  f <<$>> runQuery params . EmitDiagnostics\n  where\n    f = fromLeft mempty\n\nemitTransformedAST :: Parameters -> FilePath -> CompilerM ()\nemitTransformedAST params =\n  runQuery params . EmitTransformedAST\n\nemitRA :: Parameters -> FilePath -> CompilerM ()\nemitRA params =\n  runQuery params . EmitRA\n\nemitTransformedRA :: Parameters -> FilePath -> CompilerM ()\nemitTransformedRA params =\n  runQuery params . EmitTransformedRA\n\nemitEIR :: Parameters -> FilePath -> CompilerM ()\nemitEIR params =\n  runQuery params . EmitEIR\n\nemitLLVM :: Parameters -> FilePath -> CompilerM ()\nemitLLVM params =\n  runQuery params . EmitLLVM\n\nemitSouffle :: Parameters -> FilePath -> CompilerM ()\nemitSouffle params =\n  runQuery params . EmitSouffle\n"
  },
  {
    "path": "lib/Prelude.hs",
    "content": "module Prelude\n  ( module Relude\n  , module Control.Arrow\n  , module Control.Monad.Writer.Strict\n  , module Control.Monad.RWS.Strict\n  , module Control.Monad.Except\n  , module Control.Monad.Fix\n  , module Control.Category\n  , module Control.Comonad\n  , module Data.Functor.Foldable\n  , module Data.Functor.Foldable.TH\n  , module GHC.TypeLits\n  , module GHC.Generics\n  , module GHC.Records\n  , IsString(..)\n  , map\n  , panic\n  , groupBy\n  , modifyMVar_\n  , uniqOrderPreserving\n  ) where\n\nimport Relude hiding ( Type, Constraint, Op\n                     , and, or, id, (.), map, first\n                     , absurd\n                     )\nimport Control.Arrow hiding (second, loop, (<+>))\nimport Control.Comonad\nimport Control.Category\nimport Control.Concurrent.MVar (modifyMVar_)\nimport Control.Monad.Writer.Strict hiding (pass)\nimport Control.Monad.RWS.Strict hiding (pass)\nimport Control.Monad.Except\nimport Control.Monad.Fix\nimport Data.Functor.Foldable hiding (fold, unfold, refold, hoist)\nimport Data.Functor.Foldable.TH\nimport GHC.TypeLits (KnownSymbol, Symbol, symbolVal)\nimport GHC.Generics (Rep, K1(..), U1(..), M1(..), (:*:)(..), from, to)\nimport GHC.Records (HasField(..))\nimport qualified Data.List.Extra as E\n\n\nmap :: Functor f => (a -> b) -> f a -> f b\nmap = fmap\n{-# INLINABLE map #-}\n\npanic :: Text -> a\npanic = error\n\ngroupBy :: (a -> a -> Bool) -> [a] -> [NonEmpty a]\ngroupBy eq  = \\case\n  [] -> []\n  (x:xs) ->  (x :| ys) : groupBy eq zs\n    where (ys,zs) = span (eq x) xs\n\nuniqOrderPreserving :: Ord a => [a] -> [a]\nuniqOrderPreserving =\n  map snd . sortWith fst . E.nubOrdOn snd . zip [0 :: Int ..]\n"
  },
  {
    "path": "src/eclair/Main.hs",
    "content": "module Main (main) where\n\nimport Eclair.ArgParser\nimport Eclair.LSP\nimport Eclair\nimport GHC.IO.Encoding\nimport System.Directory\nimport qualified Data.Text.IO as TIO\n\n\ntryReadFile :: FilePath -> IO (Maybe Text)\ntryReadFile file = do\n  fileExists <- doesFileExist file\n  if fileExists\n    then Just . decodeUtf8 <$> readFileBS file\n    else pure Nothing\n\nmain :: IO ()\nmain = do\n  setLocaleEncoding utf8\n  arguments <- getArgs\n  parseArgs arguments >>= \\case\n    Compile cfg -> do\n      let file = mainFile cfg\n          fn = case emitKind cfg of\n            EmitTransformedAST -> emitTransformedAST\n            EmitRA -> emitRA\n            EmitTransformedRA -> emitTransformedRA\n            EmitEIR -> emitEIR\n            EmitLLVM -> emitLLVM\n            EmitSouffle -> emitSouffle\n          params = Parameters (numCores cfg) (cpuTarget cfg) tryReadFile\n      whenLeftM_ (fn params file) $ \\errs -> do\n        let errActions =\n              errs & map handleErrorsCLI\n                   & intersperse (TIO.hPutStr stderr \"\\n\")\n        sequence_ errActions\n\n    LSP ->\n      lspMain\n"
  },
  {
    "path": "tests/.gitignore",
    "content": "/**/.lit_test_times.txt\nOutput/\n"
  },
  {
    "path": "tests/ast_transforms/constant_folding.eclair",
    "content": "// RUN: split-file %s %t\n// RUN: %eclair compile --emit ast-transformed %t/program.eclair > %t/actual.out\n// RUN: diff %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def number(u32) input.\n@def arithmetic(u32) output.\n@def arithmetic2(u32, u32) output.\n\narithmetic(123 + 456).\narithmetic(456 - 123).\narithmetic(123 * 456).\narithmetic(123 / 456).\narithmetic(123 * 456 + 789).\n\narithmetic(x) :-\n  number(x),\n  x = 123 + (456 * 789).\n\narithmetic(x) :-\n  number(x),\n  number(y),\n  x = y + 1,\n  x = 1 + y,\n  x = y + y.\n\narithmetic2(x, y) :-\n  number(x),\n  number(y),\n  x = 1 + y,\n  y = x + 1.\n\n//--- expected.out\n@def number(u32) input.\n\n@def arithmetic(u32) output.\n\n@def arithmetic2(u32, u32) output.\n\narithmetic(579).\n\narithmetic(333).\n\narithmetic(56088).\n\narithmetic(0).\n\narithmetic(56877).\n\narithmetic(x) :-\n  number(x),\n  x = 359907.\n\narithmetic(x) :-\n  number(x),\n  number(y),\n  x = (y + 1),\n  x = (1 + y),\n  x = (y + y).\n\narithmetic2(x, y) :-\n  number(x),\n  number(y),\n  x = (1 + y),\n  y = (x + 1).\n"
  },
  {
    "path": "tests/ast_transforms/copy_propagation.eclair",
    "content": "// RUN: split-file %s %t\n// RUN: %eclair compile --emit ast-transformed %t/program.eclair > %t/actual.out\n// RUN: diff %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32, u32) input.\n@def fact2(u32) output.\n@def fact3(u32) input.\n@def fact4(u32, u32) output.\n@def fact5(u32) output.\n\nfact2(x) :-\n  x = y,\n  fact1(x, z),\n  y = z.\n\nfact2(x) :-\n  x = y,\n  y = z,\n  fact1(x, z).\n\nfact2(x) :-\n  z = x,\n  x = y,\n  y = z,\n  fact1(x, z).\n\nfact2(x) :-\n  y = x,\n  y = z,\n  fact1(x, z).\n\nfact2(x) :-\n  z = x,\n  fact1(x, z).\n\nfact4(y, z) :-\n  fact3(x),\n  y = x + 3,\n  y = x - 1,\n  y = z + y,\n  z = y + x.\n\nfact5(y) :-\n  fact3(x),\n  y = x + 3.\n\n//--- expected.out\n@def fact1(u32, u32) input.\n\n@def fact2(u32) output.\n\n@def fact3(u32) input.\n\n@def fact4(u32, u32) output.\n\n@def fact5(u32) output.\n\nfact2(x) :-\n  fact1(x, z),\n  x = z.\n\nfact2(x) :-\n  fact1(x, z),\n  x = z.\n\nfact2(x) :-\n  fact1(x, z),\n  z = x,\n  x = z.\n\nfact2(x) :-\n  fact1(x, z),\n  z = x.\n\nfact2(x) :-\n  fact1(x, z),\n  z = x.\n\nfact4((x - 1), ((x - 1) + x)) :-\n  fact3(x),\n  (x - 1) = (x + 3),\n  (x - 1) = (((x - 1) + x) + (x - 1)).\n\nfact5((x + 3)) :-\n  fact3(x).\n"
  },
  {
    "path": "tests/ast_transforms/dead_code_elimination.eclair",
    "content": "// RUN: split-file %s %t\n// RUN: %eclair compile %t/program1.eclair --emit ast-transformed > %t/actual1.out\n// RUN: diff %t/expected1.out %t/actual1.out\n\n//--- program1.eclair\n@def empty_output(u32) output.\n@def unused_input(u32) input.\n@def unused_internal(u32).\n@def unused_rule(u32).\n@def another_input(u32) input.\n\nunused_internal(1).\n\nunused_rule(x) :-\n  another_input(x).\n\n@def live1(u32) input output.\n@def live2(u32) output.\n@def live3(u32).\n@def live4(u32) output.\n@def live5(u32) input.\n@def live6(u32).\n@def live7(u32) output.\n@def live8(u32) output.\n@extern func(field1: u32) u32.\n@extern func2(u32) u32.\n@extern constraint(field1: u32).\n\nlive2(123).\n\nlive3(x) :-\n  live2(x).\n\nlive4(x) :-\n  live3(x).\n\nlive6(x) :-\n  live5(x).\n\nlive7(x) :-\n  live6(x),\n  x = func(123),\n  constraint(x).\n\nlive8(x) :-\n  live6(x),\n  func2(123) = x.\n\n// NOTE: Rule with contradictions is tested in another file already.\n\n//--- expected1.out\n@def live1(u32) input output.\n\n@def live2(u32) output.\n\n@def live3(u32).\n\n@def live4(u32) output.\n\n@def live5(u32) input.\n\n@def live6(u32).\n\n@def live7(u32) output.\n\n@def live8(u32) output.\n\n@extern func(field1: u32) u32.\n\n@extern func2(u32) u32.\n\n@extern constraint(field1: u32).\n\nlive2(123).\n\nlive3(x) :-\n  live2(x).\n\nlive4(x) :-\n  live3(x).\n\nlive6(x) :-\n  live5(x).\n\nlive7(x) :-\n  live6(x),\n  constraint(x),\n  x = func(123).\n\nlive8(x) :-\n  live6(x),\n  func2(123) = x.\n"
  },
  {
    "path": "tests/ast_transforms/remove_contradictions.eclair",
    "content": "// RUN: split-file %s %t\n// RUN: %eclair compile --emit ast-transformed %t/program.eclair > %t/actual.out\n// RUN: diff %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32) input.\n@def fact2(u32) output.\n\nfact2(x) :-\n  123 = 456,\n  fact1(x).\n\nfact2(x) :-\n  \"abc\" = \"def\",\n  fact1(x).\n\nfact2(x) :-\n  x = 123,\n  x = 456,\n  fact1(x).\n\nfact2(x) :-\n  y = \"abc\",\n  y = \"def\",\n  fact1(x).\n\nfact2(x) :-\n  x = 123,\n  x = y,\n  y = z,\n  fact1(x),\n  z = 456.\n\n//--- expected.out\n@def fact1(u32) input.\n\n@def fact2(u32) output.\n"
  },
  {
    "path": "tests/ast_transforms/shift_assignments.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ast-transformed %t/program1.eclair > %t/actual1.out\n// RUN: diff %t/expected1.out %t/actual1.out\n\n// RUN: %eclair compile --emit ast-transformed %t/program2.eclair > %t/actual2.out\n// RUN: diff %t/expected2.out %t/actual2.out\n\n//--- program1.eclair\n@def fact1(u32, u32) input.\n@def fact2(u32, u32) output.\n\nfact2(x, 1) :-\n  z = x,\n  fact1(x, z),\n  y = 123,\n  fact1(y, x).\n\nfact2(x, y) :-\n  123 = x,\n  fact1(y, x).\n//--- expected1.out\n@def fact1(u32, u32) input.\n\n@def fact2(u32, u32) output.\n\nfact2(x, 1) :-\n  fact1(x, z),\n  fact1(y, x),\n  z = x,\n  y = 123.\n\nfact2(x, y) :-\n  fact1(y, x),\n  123 = x.\n//--- program2.eclair\n@def edge(u32, u32) input.\n@def path(u32, u32) output.\n\nedge(1,2).\n\npath(x, y) :-\n  edge(x, z),\n  path(z, y).\n//--- expected2.out\n@def edge(u32, u32) input.\n\n@def path(u32, u32) output.\n\nedge(1, 2).\n\npath(x, y) :-\n  edge(x, z),\n  path(z, y).\n"
  },
  {
    "path": "tests/check.sh",
    "content": "#!/bin/bash\n\ngrep -rE \"(fdescribe|fit)\" tests/eclair\n\nif [ \"$?\" == \"0\" ]; then\n  echo \"Found disabled tests (marked with fdescribe / fit), aborting!\"\n  exit 1\nfi\n\ngrep -rE \"(\\sxit|pending)\" tests/eclair/Test\nif [ \"$?\" == \"0\" ]; then\n  echo \"Found pending tests, aborting!\"\n  exit 1\nfi\n\necho \"All tests are enabled!\"\nexit 0\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/ArgParserSpec.hs",
    "content": "module Test.Eclair.ArgParserSpec\n  ( module Test.Eclair.ArgParserSpec\n  ) where\n\nimport Test.Hspec\nimport qualified Data.Text as T\nimport Eclair.ArgParser\nimport Control.Exception\nimport System.IO.Silently\nimport System.Exit\n\n\nparseArgs' :: Text -> IO Config\nparseArgs' args =\n  parseArgs (map toString $ T.split (== ' ') args)\n\nshouldFail :: IO a -> IO ()\nshouldFail m = hSilence [stderr] $ do\n  void m `catch` handler\n  where\n    handler = \\case\n      ExitFailure 1 -> pass\n      e -> panic $ \"Unknown error: \" <> show e\n\nspec :: Spec\nspec = describe \"argument parsing\" $ do\n  describe \"compile mode\" $ parallel $ do\n    it \"supports 'compile' as the command\" $ do\n      cfg <- parseArgs' \"compile test.dl\"\n      cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM Nothing 1)\n\n    it \"supports 'c' as the command\" $ do\n      cfg <- parseArgs' \"c test.dl\"\n      cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM Nothing 1)\n\n    it \"supports no other commands\" $ do\n      shouldFail $ parseArgs' \"unknown\"\n      shouldFail $ parseArgs' \"unknown arg1\"\n\n    it \"requires a main file\" $ do\n      shouldFail $ parseArgs' \"c\"\n\n    it \"supports emitting RA\" $ do\n      for_ [\"ra\", \"RA\"] $ \\ra -> do\n        cfg <- parseArgs' $ \"c test.dl --emit \" <> ra\n        cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitRA Nothing 1)\n\n    it \"supports emitting EIR\" $ do\n      for_ [\"eir\", \"EIR\"] $ \\eir -> do\n        cfg <- parseArgs' $ \"c test.dl --emit \" <> eir\n        cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitEIR Nothing 1)\n\n    it \"supports emitting LLVM IR\" $ do\n      for_ [\"llvm\", \"LLVM\"] $ \\llvm -> do\n        cfg <- parseArgs' $ \"c test.dl --emit \" <> llvm\n        cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM Nothing 1)\n\n    it \"does not support emitting anything else\" $ do\n      shouldFail $ parseArgs' \"c test.dl --emit unknown-ir\"\n\n    it \"defaults to emitting LLVM IR\" $ do\n      cfg <- parseArgs' \"c test.dl\"\n      cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM Nothing 1)\n\n    it \"parses wasm32 as target architecture\" $ do\n      cfg <- parseArgs' \"c test.dl -t wasm32\"\n      cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM (Just Wasm32) 1)\n\n      cfg2 <- parseArgs' \"c test.dl --target wasm32\"\n      cfg2 `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM (Just Wasm32) 1)\n\n    it \"defaults to using 1 job\" $ do\n      cfg <- parseArgs' \"c test.dl --emit llvm\"\n      cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM Nothing 1)\n\n    it \"is possible to configure number of jobs\" $ do\n      cfg <- parseArgs' \"c test.dl --emit llvm -j 8\"\n      cfg `shouldBe` Compile (CompileConfig \"test.dl\" EmitLLVM Nothing 8)\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/JSONSpec.hs",
    "content": "{-# LANGUAGE QuasiQuotes #-}\n\nmodule Test.Eclair.JSONSpec\n  ( module Test.Eclair.JSONSpec\n  ) where\n\nimport Eclair.JSON\nimport Test.Hspec\nimport NeatInterpolation\n\nspec :: Spec\nspec = describe \"JSON encoding\" $ parallel $ do\n  it \"can encode null\" $ do\n    encodeJSON Null `shouldBe` \"null\"\n\n  it \"can encode booleans\" $ do\n    encodeJSON (Boolean True) `shouldBe` \"true\"\n    encodeJSON (Boolean False) `shouldBe` \"false\"\n\n  it \"can encode strings\" $ do\n    encodeJSON (String \"abcdef\") `shouldBe` [text|\"abcdef\"|]\n    encodeJSON (String \"123\") `shouldBe` [text|\"123\"|]\n\n  it \"can encode integers\" $ do\n    encodeJSON (Number 42) `shouldBe` \"42\"\n    encodeJSON (Number 123) `shouldBe` \"123\"\n\n  it \"can encode objects\" $ do\n    encodeJSON (Object [(\"line\", Number 10), (\"column\", Number 33)]) `shouldBe` [text|\n      {\"line\":10,\"column\":33}\n    |]\n    encodeJSON (Object [(\"a\", Null), (\"b\", Boolean True)]) `shouldBe` [text|\n      {\"a\":null,\"b\":true}\n    |]\n\n  it \"can encode arrays\" $ do\n    encodeJSON (Array []) `shouldBe` \"[]\"\n    encodeJSON (Array [Number 123, String \"abc\", Null]) `shouldBe` [text|\n      [123,\"abc\",null]\n    |]\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/Allocator/MallocSpec.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.Allocator.MallocSpec\n  ( module Test.Eclair.LLVM.Allocator.MallocSpec\n  ) where\n\nimport Prelude hiding (void)\nimport Eclair.LLVM.Allocator.Malloc\nimport Eclair.LLVM.Allocator.Common\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport System.Directory.Extra\nimport System.Posix.DynamicLinker\nimport Control.Exception\nimport Foreign.Ptr\nimport Foreign hiding (void)\nimport Test.Eclair.LLVM.Allocator.Utils\nimport Test.Hspec\n\n\ndata Mallocator\n\nspec :: Spec\nspec = describe \"Mallocator\" $ aroundAll (setupAndTeardown testDir) $ parallel $ do\n  it \"can be initialized and destroyed\" $ \\bindings ->\n    withAlloc bindings $ \\obj -> do\n      fnInit bindings obj\n      fnDestroy bindings obj\n\n  it \"can allocate and free memory\" $ \\bindings -> do\n    let numBytes = 1\n        value = 42\n    withAlloc bindings $ \\obj -> do\n      fnInit bindings obj\n      memory <- fnAlloc bindings obj numBytes\n      memory `shouldNotBe` nullPtr\n      poke memory value\n      value' <- peek memory\n      fnFree bindings obj memory numBytes\n      fnDestroy bindings obj\n      value' `shouldBe` value\n\nsetupAndTeardown :: FilePath -> ActionWith (Bindings Mallocator) -> IO ()\nsetupAndTeardown dir =\n  bracket (setup dir) teardown\n\nsetup :: FilePath -> IO (Bindings Mallocator)\nsetup dir = do\n  createDirectoryIfMissing False dir\n  compileAllocatorCode allocator prefix cgExternals cgTestCode dir\n  loadNativeCode prefix dir\n\nteardown :: Bindings Mallocator -> IO ()\nteardown =\n  dlclose . dynamicLib\n\ncgExternals :: ModuleBuilderT IO Externals\ncgExternals = do\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  freeFn <- extern \"free\" [ptr i8] void\n  pure $ Externals mallocFn freeFn notUsed notUsed notUsed notUsed notUsed\n\n-- Helper test code for initializing and freeing a struct from native code:\ncgTestCode :: Type -> Externals -> ModuleBuilderT IO ()\ncgTestCode ty exts = do\n  let mallocFn = extMalloc exts\n      freeFn = extFree exts\n  _ <- function \"mallocator_new\" [] (ptr ty) $ \\[] ->\n    ret =<< call mallocFn [int32 1]\n  _ <- function \"mallocator_delete\" [(ptr ty, \"allocator\")] void $ \\[alloc] ->\n    call freeFn [alloc]\n  pass\n\nprefix :: Text\nprefix = \"mallocator\"\n\ntestDir :: FilePath\ntestDir = \"/tmp/eclair-mallocator\"\n\nnotUsed :: a\nnotUsed = panic \"Not used\"\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/Allocator/PageSpec.hs",
    "content": "{-# OPTIONS_GHC -Wno-deprecations -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.Allocator.PageSpec\n  ( module Test.Eclair.LLVM.Allocator.PageSpec\n  ) where\n\nimport Prelude hiding (void)\nimport Control.Monad.Morph\nimport Eclair.LLVM.Allocator.Page\nimport Eclair.LLVM.Allocator.Common\nimport Test.Eclair.LLVM.Allocator.Utils\nimport Eclair.LLVM.Codegen hiding (retVoid)\nimport System.Directory.Extra\nimport System.Posix.DynamicLinker\nimport Test.Hspec\nimport Control.Exception (bracket)\nimport Foreign hiding (void)\nimport Foreign.LibFFI\n\ndata PageAllocator\n\nspec :: Spec\nspec = describe \"PageAllocator\" $\n  aroundAll (setupAndTeardown testDir) $ parallel $ do\n    it \"can be initialized and destroyed\" $ \\bindings ->\n      withAlloc bindings $ \\obj -> do\n        fnInit bindings obj\n        fnDestroy bindings obj\n\n    it \"can allocate and free memory\" $ \\bindings -> do\n      let numBytes = 1\n          value = 42\n      withAlloc bindings $ \\obj -> do\n        fnInit bindings obj\n        memory <- fnAlloc bindings obj numBytes\n        let memoryEnd = memory `plusPtr` 4095\n        poke memory value\n        poke memoryEnd value\n        value' <- peek memory\n        valueEnd <- peek memoryEnd\n        fnFree bindings obj memory numBytes\n        fnDestroy bindings obj\n        value' `shouldBe` value\n        valueEnd `shouldBe` value\n\n    it \"rounds up to the nearest page size\" $ \\_ -> do\n      withNearestPageSize $ \\roundFn -> do\n        result1 <- roundFn 1\n        result2 <- roundFn 4096\n        result3 <- roundFn 4097\n        result4 <- roundFn 0\n        result5 <- roundFn 12345678\n        result1 `shouldBe` 4096\n        result2 `shouldBe` 4096\n        result3 `shouldBe` (4096 * 2)\n        result4 `shouldBe` 0\n        result5 `shouldBe` 12349440\n\nsetupAndTeardown :: FilePath -> ActionWith (Bindings PageAllocator) -> IO ()\nsetupAndTeardown dir =\n  bracket (setup dir) teardown\n\nsetup :: FilePath -> IO (Bindings PageAllocator)\nsetup dir = do\n  createDirectoryIfMissing False dir\n  compileAllocatorCode allocator prefix cgExternals cgTestCode dir\n  loadNativeCode prefix dir\n\nteardown :: Bindings PageAllocator -> IO ()\nteardown =\n  dlclose . dynamicLib\n\ncgExternals :: ModuleBuilderT IO Externals\ncgExternals = do\n  -- Need malloc and free to allocate the allocator itself\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  freeFn <- extern \"free\" [ptr i8] void\n  -- mmap [hint, numBytes', prot, flags, noFd, offset]\n  mmapFn <- extern \"mmap\" [ptr void, i64, i32, i32, i32, i32] (ptr void)\n  -- munmap [memory, len']\n  munmapFn <- extern \"munmap\" [ptr void, i64] i32\n  pure $ Externals mallocFn freeFn notUsed notUsed notUsed mmapFn munmapFn\n\n-- Helper test code for allocating and freeing a struct from native code:\ncgTestCode :: Type -> Externals -> ModuleBuilderT IO ()\ncgTestCode ty exts = do\n  let mallocFn = extMalloc exts\n      freeFn = extFree exts\n  _ <- function \"pageallocator_new\" [] (ptr ty) $ \\[] ->\n    ret =<< call mallocFn [int32 1]\n  _ <- function \"pageallocator_delete\" [(ptr ty, \"allocator\")] void $ \\[alloc] ->\n    call freeFn [alloc]\n  let roundToNearestInstructions numBytes =\n        hoist (hoist intoIO) $ hoist (`evalStateT` exts) $ roundToNearestPageSize numBytes\n  _ <- function \"nearest_page_size\" [(i32, \"num_bytes\")] i32 $ \\[num] ->\n    ret =<< roundToNearestInstructions num\n  pass\n\nwithNearestPageSize :: ((Word32 -> IO Word32) -> IO ()) -> IO ()\nwithNearestPageSize f =\n  bracket open close (\\(_, roundFn) -> f roundFn)\n  where\n    open = do\n      dl <- dlopen (soFile testDir) [RTLD_LAZY]\n      roundingFn <- dlsym dl \"nearest_page_size\"\n      let roundFn numBytes =\n            fromIntegral <$> callFFI roundingFn retCUInt [argCUInt $ fromIntegral numBytes]\n      pure (dl, roundFn)\n    close = dlclose . fst\n\nprefix :: Text\nprefix = \"pageallocator\"\n\ntestDir :: FilePath\ntestDir = \"/tmp/eclair-pageallocator\"\n\nnotUsed :: a\nnotUsed = panic \"Not used\"\n\nintoIO :: Identity a -> IO a\nintoIO = pure . runIdentity\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/Allocator/Utils.hs",
    "content": "{-# OPTIONS_GHC -Wno-deprecations #-}\nmodule Test.Eclair.LLVM.Allocator.Utils\n  ( Bindings(..)\n  , compileAllocatorCode\n  , loadNativeCode\n  , soFile\n  ) where\n\nimport System.Process.Extra\nimport System.FilePath\nimport System.Posix.DynamicLinker\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport Eclair.LLVM.Allocator.Common\nimport Control.Monad.Morph\nimport Control.Exception\nimport Foreign.LibFFI\nimport Foreign.Ptr\nimport Foreign.C\n\ntype I8 = CUChar\n\ndata Bindings a\n  = Bindings\n  { dynamicLib :: DL\n  , withAlloc :: (Ptr a -> IO ()) -> IO ()\n  , fnAlloc :: Ptr a -> CSize -> IO (Ptr I8)\n  , fnFree :: Ptr a -> Ptr I8 -> CSize -> IO ()\n  , fnInit :: Ptr a -> IO ()\n  , fnDestroy :: Ptr a -> IO ()\n  }\n\ncompileAllocatorCode\n  :: Allocator a\n  -> Text\n  -> ModuleBuilderT IO Externals\n  -> (Type -> Externals -> ModuleBuilderT IO ())\n  -> FilePath -> IO ()\ncompileAllocatorCode allocator prefix cgExts cgHelperCode dir = do\n  llvmIR <- runModuleBuilderT $ do\n    exts <- cgExts\n    let cgBlueprint = flip evalStateT exts $ cgAlloc prefix allocator\n    blueprint <- hoist intoIO cgBlueprint\n    cgHelperCode (bpType blueprint) exts\n  let llvmIRText = ppllvm llvmIR\n  writeFileText (llFile dir) llvmIRText\n  callProcess \"clang\" [\"-fPIC\", \"-shared\", \"-O0\", \"-o\", soFile dir, llFile dir]\n\nintoIO :: Identity a -> IO a\nintoIO = pure . runIdentity\n\nllFile, soFile :: FilePath -> FilePath\nllFile dir = dir </> \"allocator.ll\"\nsoFile dir = dir </> \"allocator.so\"\n\nloadNativeCode :: Text -> FilePath -> IO (Bindings a)\nloadNativeCode (toString -> pfx) dir = do\n  lib <- dlopen (soFile dir) [RTLD_LAZY]\n  newFn <- dlsym lib (pfx <> \"_new\")\n  deleteFn <- dlsym lib (pfx <> \"_delete\")\n  allocFn <- dlsym lib (pfx <> \"_alloc\")\n  freeFn <- dlsym lib (pfx <> \"_free\")\n  initFn <- dlsym lib (pfx <> \"_init\")\n  destroyFn <- dlsym lib (pfx <> \"_destroy\")\n  pure $ Bindings\n    { dynamicLib = lib\n    , withAlloc = mkWithAlloc newFn deleteFn\n    , fnAlloc = mkAlloc allocFn\n    , fnFree = mkFree freeFn\n    , fnInit = mkInit initFn\n    , fnDestroy = mkDestroy destroyFn\n    }\n  where\n    mkAlloc fn mallocator numBytes =\n      callFFI fn (retPtr retCUChar)\n        [ argPtr mallocator\n        , argCUInt $ fromIntegral numBytes\n        ]\n    mkFree fn mallocator memory numBytes =\n      callFFI fn retVoid\n        [ argPtr mallocator\n        , argPtr memory\n        , argCSize $ fromIntegral numBytes\n        ]\n    mkInit fn mallocator =\n      callFFI fn retVoid [argPtr mallocator]\n    mkDestroy fn mallocator =\n      callFFI fn retVoid [argPtr mallocator]\n    mkNew fn =\n      callFFI fn (retPtr retVoid) []\n    mkDelete fn mallocator =\n      callFFI fn retVoid [argPtr mallocator]\n    mkWithAlloc newFn deleteFn =\n      bracket (castPtr <$> mkNew newFn) (mkDelete deleteFn)\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/BTreeSpec.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.BTreeSpec\n  ( module Test.Eclair.LLVM.BTreeSpec\n  ) where\n\nimport Prelude hiding (void)\nimport qualified Relude as R\nimport System.Directory.Extra\nimport System.Process.Extra\nimport System.Posix.DynamicLinker\nimport System.FilePath\nimport Control.Exception\nimport Control.Monad.Morph\nimport System.Random\nimport Data.Array.IO hiding (index)\nimport Foreign.LibFFI\nimport Foreign hiding (void, newArray)\nimport Eclair.LLVM.BTree\nimport Eclair.LLVM.Table\nimport Eclair.LLVM.Externals\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport qualified LLVM.C.API as LibLLVM\nimport Test.Hspec\n\n\ndata BTree\ndata Iter\ntype Value = Word32\n\ndata Bindings\n  = Bindings\n  { dynamicLib :: DL\n  , withTree :: (Ptr BTree -> IO ()) -> IO ()\n  , withIter :: forall a. (Ptr Iter -> IO a) -> IO a\n  , withValue :: forall a. Value -> (Ptr Value -> IO a) -> IO a\n  , bInit :: Ptr BTree -> IO ()\n  , bDestroy :: Ptr BTree -> IO ()\n  , bPurge :: Ptr BTree -> IO ()\n  , bSwap :: Ptr BTree -> Ptr BTree -> IO ()\n  , bBegin :: Ptr BTree -> Ptr Iter -> IO ()\n  , bEnd :: Ptr BTree -> Ptr Iter -> IO ()\n  , bInsert :: Ptr BTree -> Ptr Value -> IO Bool\n  , bMerge :: Ptr BTree -> Ptr BTree -> IO ()\n  , bEmpty :: Ptr BTree -> IO Bool\n  , bSize :: Ptr BTree -> IO Word64\n  , bNodeCount :: Ptr BTree -> IO Word64\n  , bDepth :: Ptr BTree -> IO Word32\n  , bLowerBound :: forall a. Ptr BTree -> Ptr Value -> (Ptr Iter -> IO a) -> IO a\n  , bUpperBound :: forall a. Ptr BTree -> Ptr Value -> (Ptr Iter -> IO a) -> IO a\n  , bContains :: Ptr BTree -> Ptr Value -> IO Bool\n  , bIterCurrent :: Ptr Iter -> IO (Ptr Value)\n  , bIterNext :: Ptr Iter -> IO ()\n  , bIterIsEqual :: Ptr Iter -> Ptr Iter -> IO Bool\n  }\n\n\nspec :: Spec\nspec = describe \"BTree\" $ aroundAll (setupAndTeardown testDir) $ parallel $ do\n  it \"can be initialized and destroyed\" $ \\bindings -> do\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n      bDestroy bindings tree\n\n  it \"is possible to remove all elements from the tree\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      bPurge bindings tree -- empty trees\n      empty1 <- bEmpty bindings tree\n      bPurge bindings tree -- calling it again\n      empty2 <- bEmpty bindings tree\n\n      withValue bindings 1 $ R.void . bInsert bindings tree\n\n      empty3 <- bEmpty bindings tree\n      bPurge bindings tree -- non-empty tree\n      empty4 <- bEmpty bindings tree\n      bPurge bindings tree -- calling it again\n      empty5 <- bEmpty bindings tree\n\n      for_ [1..100] $ \\i ->\n        withValue bindings i $ R.void . bInsert bindings tree\n      bPurge bindings tree -- calling it again\n      empty6 <- bEmpty bindings tree\n\n      bDestroy bindings tree\n\n      empty1 `shouldBe` True\n      empty2 `shouldBe` True\n      empty3 `shouldBe` False\n      empty4 `shouldBe` True\n      empty5 `shouldBe` True\n      empty6 `shouldBe` True\n\n  it \"should be possible to merge one tree into another\" $ \\bindings ->\n    withTree bindings $ \\tree1 -> do\n      withTree bindings $ \\tree2 -> do\n        bInit bindings tree1\n        bInit bindings tree2\n\n        for_ [1..4] $ \\i -> do\n          withValue bindings i $ bInsert bindings tree1\n        for_ [2, 4, 6] $ \\i -> do\n          withValue bindings i $ bInsert bindings tree2\n\n        -- tree1 = \"destination\", tree2 = \"source\"\n        bMerge bindings tree1 tree2\n        list <- treeToList bindings tree1\n\n        bDestroy bindings tree1\n        bDestroy bindings tree2\n\n        list `shouldBe` [1, 2, 3, 4, 6]\n\n  it \"is possible to swap two trees\" $ \\bindings -> do\n    withTree bindings $ \\tree1 -> do\n      withTree bindings $ \\tree2 -> do\n        bInit bindings tree1\n        bInit bindings tree2\n\n        for_ [1..100] $ \\i -> do\n          withValue bindings i $ \\value -> do\n            _ <- bInsert bindings tree1 value\n            pass\n          withValue bindings (i + 100) $ \\value -> do\n            _ <- bInsert bindings tree2 value\n            pass\n\n        c1 <- withValue bindings 42 $ bContains bindings tree1\n        c2 <- withValue bindings 78 $ bContains bindings tree1\n        c3 <- withValue bindings 142 $ bContains bindings tree2\n        c4 <- withValue bindings 178 $ bContains bindings tree2\n\n        bSwap bindings tree1 tree2\n\n        c5 <- withValue bindings 42 $ bContains bindings tree2\n        c6 <- withValue bindings 78 $ bContains bindings tree2\n        c7 <- withValue bindings 142 $ bContains bindings tree1\n        c8 <- withValue bindings 178 $ bContains bindings tree1\n\n        bDestroy bindings tree1\n        bDestroy bindings tree2\n\n        let result = R.and [c1, c2, c3, c4, c5, c6, c7, c8]\n        result `shouldBe` True\n\n  it \"is possible to get begin and end iterators\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      withIters bindings $ \\beginIter endIter -> do\n        bInit bindings tree\n        bBegin bindings tree beginIter\n        bEnd bindings tree endIter\n        beginIter `shouldNotBe` nullPtr\n        endIter `shouldNotBe` nullPtr\n        bDestroy bindings tree\n\n  it \"is possible to iterate over the tree\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      withValue bindings 4 $ R.void . bInsert bindings tree\n      withValue bindings 2 $ R.void . bInsert bindings tree\n      withValue bindings 5 $ R.void . bInsert bindings tree\n      withValue bindings 1 $ R.void . bInsert bindings tree\n      withValue bindings 3 $ R.void . bInsert bindings tree\n\n      withIters bindings $ \\beginIter endIter -> do\n        bBegin bindings tree beginIter\n        bEnd bindings tree endIter\n        isEqual <- bIterIsEqual bindings beginIter endIter\n        isEqual `shouldBe` False\n\n      values <- treeToList bindings tree\n\n      bDestroy bindings tree\n      values `shouldBe` [1, 2, 3, 4, 5]\n\n  it \"should have equal begin and end iterators if tree is empty\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      withIters bindings $ \\beginIter endIter -> do\n        bInit bindings tree\n        bBegin bindings tree beginIter\n        bEnd bindings tree endIter\n        isEqual <- bIterIsEqual bindings beginIter endIter\n        isEqual `shouldBe` True\n        bDestroy bindings tree\n\n  it \"is possible to insert a value\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      withValue bindings 1 $ \\value -> do\n        bInit bindings tree\n        didInsert <- bInsert bindings tree value\n        didInsert' <- bInsert bindings tree value\n        didInsert `shouldBe` True\n        didInsert' `shouldBe` False\n        bDestroy bindings tree\n\n  it \"is possible to check if the tree is empty\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      empty1 <- bEmpty bindings tree\n      withValue bindings 1 $ R.void . bInsert bindings tree\n      empty2 <- bEmpty bindings tree\n      withValue bindings 2 $ R.void . bInsert bindings tree\n      empty3 <- bEmpty bindings tree\n\n      bDestroy bindings tree\n\n      empty1 `shouldBe` True\n      empty2 `shouldBe` False\n      empty3 `shouldBe` False\n\n  it \"is possible to lookup the size of the tree\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n      size1 <- bSize bindings tree\n      R.void $ withValue bindings 1 $ bInsert bindings tree\n      size2 <- bSize bindings tree\n      for_ [2..100] $ \\i -> do\n        withValue bindings i $ bInsert bindings tree\n      size3 <- bSize bindings tree\n      bDestroy bindings tree\n      size1 `shouldBe` 0\n      size2 `shouldBe` 1\n      size3 `shouldBe` 100\n\n  it \"is possible to check if the tree contains a certain value\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      c1 <- withValue bindings 1000 $ bContains bindings tree\n      R.void $ withValue bindings 1000 $ bInsert bindings tree\n      c2 <- withValue bindings 1000 $ bContains bindings tree\n\n      for_ [1..100] $ \\i ->\n        withValue bindings i $ \\value -> do\n          _ <- bInsert bindings tree value\n          pass\n\n      c3 <- withValue bindings 42 $ bContains bindings tree\n      c4 <- withValue bindings 78 $ bContains bindings tree\n      c5 <- withValue bindings 132 $ bContains bindings tree\n\n      c1 `shouldBe` False\n      c2 `shouldBe` True\n      c3 `shouldBe` True\n      c4 `shouldBe` True\n      c5 `shouldBe` False\n\n      bDestroy bindings tree\n\n  -- Tests below are taken from Souffle's test suite\n\n  it \"should support basic operations on the btree\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      -- check initial conditions\n      bSize bindings tree >>= (`shouldBe` 0)\n      bNodeCount bindings tree >>= (`shouldBe` 0)\n      bDepth bindings tree >>= (`shouldBe` 0)\n      withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` False)\n      withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` False)\n      withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` False)\n\n      -- add an element\n\n      R.void $ withValue bindings 12 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 1)\n      bNodeCount bindings tree >>= (`shouldBe` 1)\n      bDepth bindings tree >>= (`shouldBe` 1)\n      withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` False)\n      withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True) -- TODO failing\n      withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` False)\n\n      -- add a larger element\n      R.void $ withValue bindings 14 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 2)\n      bNodeCount bindings tree >>= (`shouldBe` 1)\n      bDepth bindings tree >>= (`shouldBe` 1)\n      withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` False)\n      withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True)\n      withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` True)\n\n      -- add a smaller element\n      R.void $ withValue bindings 10 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 3)\n      bNodeCount bindings tree >>= (`shouldBe` 1)\n      bDepth bindings tree >>= (`shouldBe` 1)\n      withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` True)\n      withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True)\n      withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` True)\n\n      -- cause a split\n      R.void $ withValue bindings 11 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 4)\n      withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` True)\n      withValue bindings 11 (bContains bindings tree) >>= (`shouldBe` True)\n      withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True)\n      withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` True)\n\n      -- adding duplicates\n      R.void $ withValue bindings 12 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 4)\n      R.void $ withValue bindings 12 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 4)\n      R.void $ withValue bindings 10 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 4)\n\n      R.void $ withValue bindings 15 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 5)\n      bNodeCount bindings tree >>= (`shouldBe` 3)\n      bDepth bindings tree >>= (`shouldBe` 2)\n\n      R.void $ withValue bindings 16 (bInsert bindings tree)\n      bSize bindings tree >>= (`shouldBe` 6)\n\n      bDestroy bindings tree\n\n  it \"should automatically remove duplicates\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      replicateM_ 10 $ withValue bindings 0 $ bInsert bindings tree\n\n      size <- bSize bindings tree\n      value <- withIter bindings $ \\iter -> do\n        bBegin bindings tree iter\n        valuePtr <- bIterCurrent bindings iter\n        peek valuePtr\n\n      bDestroy bindings tree\n\n      size `shouldBe` 1\n      value `shouldBe` 0\n\n  it \"should contain the value after it is inserted\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      let n = 100\n      for_ [0..n] $ \\i -> do\n        R.void $ withValue bindings i $ bInsert bindings tree\n\n        for_ [0..n] $ \\j -> do\n          contains <- withValue bindings j $ bContains bindings tree\n          contains `shouldBe` (j <= i)\n\n      bDestroy bindings tree\n\n  it \"should contain the value after it is inserted (reverse)\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      let n = 100\n      for_ [n, (n - 1) .. 0] $ \\i -> do\n        R.void $ withValue bindings i (bInsert bindings tree)\n\n        for_ [0..n] $ \\j -> do\n          contains <- withValue bindings j (bContains bindings tree)\n          contains `shouldBe` (j >= i)\n\n      bDestroy bindings tree\n\n  it \"should contain the value after is inserted (shuffled)\" $ \\bindings -> do\n    let list = [1..10000]\n    shuffled <- shuffle list\n\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      for_ shuffled $ \\i -> do\n        R.void $ withValue bindings i (bInsert bindings tree)\n\n      for_ list $ \\j -> do\n        contains <- withValue bindings j (bContains bindings tree)\n        contains `shouldBe` True\n\n      bDestroy bindings tree\n\n  it \"should withstand iterator stress test\" $ \\bindings -> do\n    let isSorted xs = sort xs == xs\n        list = [1..300]  -- for faster unit tests\n        -- list = [1..1000]  -- for real stress test\n    shuffled <- shuffle list\n\n    withTree bindings $ \\tree -> do\n      bInit bindings tree\n\n      for_ shuffled $ \\i -> do\n        values <- treeToList bindings tree\n        -- this is the main check if iterators are working correctly:\n        isSorted values `shouldBe` True\n        R.void $ withValue bindings i (bInsert bindings tree)\n\n      bDestroy bindings tree\n\n  it \"should calculate correct lower and upper bounds of a value\" $ \\bindings ->\n    withTree bindings $ \\tree -> do\n      let getBound f = flip (f bindings tree) (peek <=< bIterCurrent bindings)\n          getLB = getBound bLowerBound\n          getUB = getBound bUpperBound\n\n      bInit bindings tree\n\n      for_ [0..10] $ \\i -> do\n        R.void $ withValue bindings i (bInsert bindings tree)\n\n      lb1 <- withValue bindings 5 getLB\n      ub1 <- withValue bindings 5 getUB\n      lb1 `shouldBe` 5\n      ub1 `shouldBe` 6\n\n      -- add duplicates and check again\n      replicateM_ 3 $ R.void $ withValue bindings 5 $ bInsert bindings tree\n\n      lb2 <- withValue bindings 5 getLB\n      ub2 <- withValue bindings 5 getUB\n      lb2 `shouldBe` 5\n      ub2 `shouldBe` 6\n\n      bDestroy bindings tree\n\n  it \"should calculate correct lower and upper bound for empty trees\" $ \\bindings ->\n    withTree bindings $ \\tree ->\n      withIter bindings $ \\endIter -> do\n        bInit bindings tree\n        bEnd bindings tree endIter\n\n        -- empty\n        lbIsEnd1 <- withValue bindings 5 $ flip (bLowerBound bindings tree) $\n          bIterIsEqual bindings endIter\n        ubIsEnd1 <- withValue bindings 5 $ flip (bUpperBound bindings tree) $\n          bIterIsEqual bindings endIter\n        lbIsEnd1 `shouldBe` True\n        ubIsEnd1 `shouldBe` True\n\n        let checkBounds expected3 expected5 = do\n              withValue bindings 3 $ flip (bLowerBound bindings tree) $ \\lbIter ->\n                withValue bindings 3 $ flip (bUpperBound bindings tree) $ \\ubIter -> do\n                  isEqual <- bIterIsEqual bindings lbIter ubIter\n                  isEqual `shouldBe` expected3\n              withValue bindings 5 $ flip (bLowerBound bindings tree) $ \\lbIter ->\n                withValue bindings 5 $ flip (bUpperBound bindings tree) $ \\ubIter -> do\n                  isEqual <- bIterIsEqual bindings lbIter ubIter\n                  isEqual `shouldBe` expected5\n\n        -- insert 4\n        R.void $ withValue bindings 4 (bInsert bindings tree)\n        checkBounds True True\n        -- insert 6\n        R.void $ withValue bindings 6 (bInsert bindings tree)\n        checkBounds True True\n        -- insert 5\n        R.void $ withValue bindings 5 (bInsert bindings tree)\n        checkBounds True False\n\n        bDestroy bindings tree\n\nsetupAndTeardown :: FilePath -> ActionWith Bindings -> IO ()\nsetupAndTeardown dir =\n  bracket (setup dir) teardown\n\nsetup :: FilePath -> IO Bindings\nsetup dir = do\n  createDirectoryIfMissing False dir\n  let meta = Meta\n        { numColumns = 1\n        , index = [0]\n        , blockSize = 16\n        , searchType = Linear\n        }\n  cgBTree dir meta\n  loadNativeCode dir\n\nteardown :: Bindings -> IO ()\nteardown =\n  dlclose . dynamicLib\n\ncgBTree :: FilePath -> Meta -> IO ()\ncgBTree dir meta = do\n  ctx <- LibLLVM.mkContext\n  llvmMod <- LibLLVM.mkModule ctx \"eclair\"\n  td <- LibLLVM.getTargetData llvmMod\n  let cfg = Config Nothing ctx td\n  llvmIR <- runModuleBuilderT $ do\n    exts <- cgExternals\n    table <- instantiate \"test\" meta $ runConfigT cfg $ codegen exts\n    let iterParams = IteratorParams\n          { ipIterCurrent = fnIterCurrent table\n          , ipIterNext = fnIterNext table\n          , ipIterIsEqual = fnIterIsEqual table\n          , ipTypeIter = typeIter table\n          }\n    R.void $ hoist intoIO $ instantiate \"test\" iterParams $\n      fnInsertRangeTemplate table\n    cgHelperCode table (extMalloc exts) (extFree exts)\n  let llvmIRText = ppllvm llvmIR\n  writeFileText (llFile dir) llvmIRText\n  -- Next line is a hack, because we can't access node types from the test:\n  appendFileText (llFile dir) helperCodeAppendix\n  callProcess \"clang\" [\"-fPIC\", \"-shared\", \"-O0\", \"-o\", soFile dir, llFile dir]\n\ncgExternals :: ModuleBuilderT IO Externals\ncgExternals = do\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  freeFn <- extern \"free\" [ptr i8] void\n  memsetFn <- extern \"llvm.memset.p0i8.i64\" [ptr i8, i8, i64, i1] void\n  pure $ Externals mallocFn freeFn memsetFn notUsed notUsed notUsed notUsed\n\n-- Helper test code for initializing and freeing a struct from native code:\ncgHelperCode :: Monad m => Table -> Operand -> Operand -> ModuleBuilderT m ()\ncgHelperCode table mallocFn freeFn = do\n  let treeTy = typeObj table\n      iterTy = typeIter table\n      valueTy = typeValue table\n  _ <- function \"eclair_btree_new\" [] (ptr treeTy) $ \\[] ->\n    ret =<< call mallocFn [int32 16]\n  _ <- function \"eclair_btree_delete\" [(ptr treeTy, \"btree\")] void $ \\[btree] ->\n    call freeFn [btree]\n  _ <- function \"eclair_iter_new\" [] (ptr iterTy) $ \\[] ->\n    ret =<< call mallocFn [int32 16]\n  _ <- function \"eclair_iter_delete\" [(ptr iterTy, \"iter\")] void $ \\[iter] ->\n    call freeFn [iter]\n  _ <- function \"eclair_value_new\" [] (ptr valueTy) $ \\[] ->\n    ret =<< call mallocFn [int32 4] -- Hardcoded for 1x i32\n  _ <- function \"eclair_value_delete\" [(ptr valueTy, \"value\")] void $ \\[value] ->\n    call freeFn [value]\n  -- Next function is needed because returning i1 is not C ABI compatible\n  _ <- function \"eclair_btree_contains_helper_test\" [(ptr treeTy, \"tree\"), (ptr valueTy, \"val\")] i8 $ \\[tree, val] -> do\n    result <- call (fnContains table) [tree, val] >>= (`zext` i8)\n    ret result\n  pass\n\nhelperCodeAppendix :: Text\nhelperCodeAppendix = unlines\n  [ \"\"\n  , \"define external ccc i64 @node_count(ptr %node_0) {\"\n  , \"start:\"\n  , \"  %stack.ptr_0 = alloca i64\"  -- count\n  , \"  store i64 1, ptr %stack.ptr_0\"\n  , \"  %0 = getelementptr %node_t_test, ptr %node_0, i32 0, i32 0, i32 3\"\n  , \"  %1 = load i1, ptr %0\" -- node type\n  , \"  %2 = icmp eq i1 %1, 0\" -- is leaf?\n  , \"  br i1 %2, label %if_0, label %end_if_0\"\n  , \"if_0:\"\n  , \"  ret i64 1\"\n  , \"end_if_0:\"\n  , \"  %3 = getelementptr %node_t_test, ptr %node_0, i32 0, i32 0, i32 2\"\n  , \"  %4 = load i16, ptr %3\"\n  , \"  br label %for_begin_0\"\n  , \"for_begin_0:\"\n  , \"  %5 = phi i16 [0, %end_if_0], [%12, %for_body_0]\"\n  , \"  %6 = icmp ule i16 %5, %4\"\n  , \"  br i1 %6, label %for_body_0, label %for_end_0\"\n  , \"for_body_0:\"\n  , \"  %7 = load i64, ptr %stack.ptr_0\" -- count\n  , \"  %8 = getelementptr %inner_node_t_test, ptr %node_0, i32 0, i32 1, i16 %5\" -- child ptr\n  , \"  %9 = load ptr, ptr %8\" -- child\n  , \"  %10 = call ccc i64 @node_count(ptr %9)\"\n  , \"  %11 = add i64 %7, %10\"\n  , \"  store i64 %11, ptr %stack.ptr_0\"\n  , \"  %12 = add i16 1, %5\"\n  , \"  br label %for_begin_0\"\n  , \"for_end_0:\"\n  , \"  %13 = load i64, ptr %stack.ptr_0\"\n  , \"  ret i64 %13\"\n  , \"}\"\n  , \"\"\n  , \"define external ccc i64 @eclair_btree_node_count_test(ptr %tree_0) {\"\n  , \"start:\"\n  , \"  %0 = getelementptr %btree_t_test, ptr %tree_0, i32 0, i32 0\"\n  , \"  %1 = load ptr, ptr %0\"\n  , \"  %2 = icmp eq ptr %1, zeroinitializer\"\n  , \"  br i1 %2, label %null_0, label %not_null_0\"\n  , \"null_0:\"\n  , \"  ret i64 0\"\n  , \"not_null_0:\"\n  , \"  %3 = call ccc i64 @node_count(ptr %1)\"\n  , \"  ret i64 %3\"\n  , \"}\"\n  , \"\"\n  , \"define external ccc i32 @node_depth(ptr %node_0) {\"\n  , \"start:\"\n  , \"  %0 = getelementptr %node_t_test, ptr %node_0, i32 0, i32 0, i32 3\"\n  , \"  %1 = load i1, ptr %0\" -- node type\n  , \"  %2 = icmp eq i1 %1, 0\" -- is leaf?\n  , \"  br i1 %2, label %if_0, label %end_if_0\"\n  , \"if_0:\"\n  , \"  ret i32 1\"\n  , \"end_if_0:\"\n  , \"  %3 = getelementptr %inner_node_t_test, ptr %node_0, i32 0, i32 1, i16 0\" -- child ptr\n  , \"  %4 = load ptr, ptr %3\" -- child\n  , \"  %5 = call ccc i32 @node_depth(ptr %4)\"\n  , \"  %6 = add i32 %5, 1\"\n  , \"  ret i32 %6\"\n  , \"}\"\n  , \"\"\n  , \"define external ccc i32 @eclair_btree_depth_test(ptr %tree_0) {\"\n  , \"start:\"\n  , \"  %0 = getelementptr %btree_t_test, ptr %tree_0, i32 0, i32 0\"\n  , \"  %1 = load ptr, ptr %0\"\n  , \"  %2 = icmp eq ptr %1, zeroinitializer\"\n  , \"  br i1 %2, label %null_0, label %not_null_0\"\n  , \"null_0:\"\n  , \"  ret i32 0\"\n  , \"not_null_0:\"\n  , \"  %3 = call ccc i32 @node_depth(ptr %1)\"\n  , \"  ret i32 %3\"\n  , \"}\"\n  ]\n\nloadNativeCode :: FilePath -> IO Bindings\nloadNativeCode dir = do\n  lib <- dlopen (soFile dir) [RTLD_LAZY]\n  funcNewTree <- dlsym lib \"eclair_btree_new\"\n  funcDeleteTree <- dlsym lib \"eclair_btree_delete\"\n  funcNewIter <- dlsym lib \"eclair_iter_new\"\n  funcDeleteIter <- dlsym lib \"eclair_iter_delete\"\n  funcNewValue <- dlsym lib \"eclair_value_new\"\n  funcDeleteValue <- dlsym lib \"eclair_value_delete\"\n  funcInit <- dlsym lib \"eclair_btree_init_empty_test\"\n  funcDestroy <- dlsym lib \"eclair_btree_destroy_test\"\n  funcPurge <- dlsym lib \"eclair_btree_clear_test\"\n  funcSwap <- dlsym lib \"eclair_btree_swap_test\"\n  funcBegin <- dlsym lib \"eclair_btree_begin_test\"\n  funcEnd <- dlsym lib \"eclair_btree_end_test\"\n  funcInsert <- dlsym lib \"eclair_btree_insert_value_test\"\n  funcMerge <- dlsym lib \"eclair_btree_insert_range_test\"\n  funcEmpty <- dlsym lib \"eclair_btree_is_empty_test\"\n  funcSize <- dlsym lib \"eclair_btree_size_test\"\n  funcNodeCount <- dlsym lib \"eclair_btree_node_count_test\"\n  funcDepth <- dlsym lib \"eclair_btree_depth_test\"\n  funcContains <- dlsym lib \"eclair_btree_contains_helper_test\"\n  funcLB <- dlsym lib \"eclair_btree_lower_bound_test\"\n  funcUB <- dlsym lib \"eclair_btree_upper_bound_test\"\n  funcIterCurrent <- dlsym lib \"eclair_btree_iterator_current_test\"\n  funcIterNext <- dlsym lib \"eclair_btree_iterator_next_test\"\n  funcIterIsEqual <- dlsym lib \"eclair_btree_iterator_is_equal_test\"\n  let withIter' :: forall a. (Ptr Iter -> IO a) -> IO a\n      withIter' = mkWithX funcNewIter funcDeleteIter\n      iterCurrent = mkIterCurrent funcIterCurrent\n      begin' = mkBegin funcBegin\n      end' = mkEnd funcEnd\n  pure $ Bindings\n    { dynamicLib = lib\n    , withTree = mkWithX funcNewTree funcDeleteTree\n    , withIter = withIter'\n    , withValue = \\value f -> do\n        mkWithX funcNewValue funcDeleteValue $ \\valuePtr -> do\n          poke valuePtr value\n          f valuePtr\n    , bInit = mkInit funcInit\n    , bDestroy = mkDestroy funcDestroy\n    , bPurge = mkPurge funcPurge\n    , bSwap = mkSwap funcSwap\n    , bBegin = begin'\n    , bEnd = end'\n    , bInsert = mkInsert funcInsert\n    , bMerge = mkMerge funcMerge withIter' begin' end'\n    , bEmpty = mkIsEmpty funcEmpty\n    , bSize = mkSize funcSize\n    , bNodeCount = mkNodeCount funcNodeCount\n    , bDepth = mkDepth funcDepth\n    , bContains = mkContains funcContains\n    , bIterCurrent = iterCurrent\n    , bIterNext = mkIterNext funcIterNext\n    , bIterIsEqual = mkIterIsEqual funcIterIsEqual\n    , bLowerBound = mkBound funcLB withIter'\n    , bUpperBound = mkBound funcUB withIter'\n    }\n  where\n    mkInit fn tree = callFFI fn retVoid [argPtr tree]\n    mkDestroy fn tree = callFFI fn retVoid [argPtr tree]\n    mkPurge fn tree = callFFI fn retVoid [argPtr tree]\n    mkSwap fn tree1 tree2 = callFFI fn retVoid [argPtr tree1, argPtr tree2]\n    mkBegin fn tree resultIter = callFFI fn retVoid [argPtr tree, argPtr resultIter]\n    mkEnd fn tree resultIter = callFFI fn retVoid [argPtr tree, argPtr resultIter]\n    mkInsert fn tree value = do\n      result <- callFFI fn retCUChar [argPtr tree, argPtr value]\n      pure $ result == 1\n    mkMerge fn withIter' begin' end' tree1 tree2 = do\n      withIter' $ \\beginIter ->\n        withIter' $ \\endIter -> do\n          R.void $ begin' tree2 beginIter\n          R.void $ end' tree2 endIter\n          callFFI fn retVoid [argPtr tree1, argPtr beginIter, argPtr endIter]\n    mkIsEmpty fn tree = do\n      result <- callFFI fn retCUChar [argPtr tree]\n      pure $ result == 1\n    mkSize fn tree = fromIntegral <$> callFFI fn retCULong [argPtr tree]\n    mkNodeCount fn tree = fromIntegral <$> callFFI fn retCULong [argPtr tree]\n    mkDepth fn tree = fromIntegral <$> callFFI fn retCUInt [argPtr tree]\n    mkContains fn tree value = do\n      result <- callFFI fn retCUChar [argPtr tree, argPtr value]\n      pure $ result == 1\n    mkNew fn = callFFI fn (retPtr retVoid) []\n    mkDelete fn obj = callFFI fn retVoid [argPtr obj]\n    mkWithX newFn deleteFn = bracket (castPtr <$> mkNew newFn) (mkDelete deleteFn)\n    mkIterCurrent fn iter = castPtr <$> callFFI fn (retPtr retVoid) [argPtr iter]\n    mkIterNext fn iter = callFFI fn retVoid [argPtr iter]\n    mkIterIsEqual fn beginIter endIter = do\n      result <- callFFI fn retCUChar [argPtr beginIter, argPtr endIter]\n      pure $ result == 1\n    mkBound fn withIter' tree value f = do\n      withIter' $ \\iter -> do\n        callFFI fn retVoid [argPtr tree, argPtr value, argPtr iter]\n        f iter\n\nwithIters :: Bindings -> (Ptr Iter -> Ptr Iter -> IO a) -> IO a\nwithIters bindings f =\n  withIter bindings $ \\beginIter ->\n    withIter bindings $ \\endIter ->\n      f beginIter endIter\n\ntreeToList :: Bindings -> Ptr BTree -> IO [Value]\ntreeToList bindings tree =\n  withIters bindings $ \\beginIter endIter -> do\n    bBegin bindings tree beginIter\n    bEnd bindings tree endIter\n\n    whileM (isNotEqualIter beginIter endIter) $ do\n      value <- bIterCurrent bindings beginIter\n      bIterNext bindings beginIter\n      peek value\n  where\n    isNotEqualIter beginIter endIter = do\n      not <$> bIterIsEqual bindings beginIter endIter\n\nllFile, soFile :: FilePath -> FilePath\nllFile dir = dir </> \"btree.ll\"\nsoFile dir = dir </> \"btree.so\"\n\ntestDir :: FilePath\ntestDir = \"/tmp/eclair-btree\"\n\nnotUsed :: a\nnotUsed = panic \"Not used\"\n\nwhileM :: Monad m => m Bool -> m a -> m [a]\nwhileM cond action = go\n  where\n    go = cond >>= \\case\n      True -> do\n        x <- action\n        xs <- go\n        pure $ x:xs\n      False ->\n        pure []\n\nshuffle :: [a] -> IO [a]\nshuffle xs = do\n  array <- mkArray n xs\n  forM [1..n] $ \\i -> do\n    j <- randomRIO (i,n)\n    vi <- readArray array i\n    vj <- readArray array j\n    writeArray array j vi\n    pure vj\n  where\n    n = length xs\n    mkArray :: Int -> [a] -> IO (IOArray Int a)\n    mkArray m = newListArray (1,m)\n\nintoIO :: Identity a -> IO a\nintoIO = pure . runIdentity\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/HashMapSpec.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.HashMapSpec\n  ( module Test.Eclair.LLVM.HashMapSpec\n  ) where\n\nimport Prelude hiding (void, HashMap, Symbol)\nimport Control.Exception\nimport Control.Monad.Morph\nimport qualified Test.Eclair.LLVM.SymbolUtils as S\nimport qualified LLVM.C.API as LibLLVM\nimport Eclair.LLVM.HashMap\nimport qualified Eclair.LLVM.Symbol as S\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport Eclair.LLVM.Externals\nimport Foreign.LibFFI\nimport Foreign hiding (void)\nimport System.Posix.DynamicLinker\nimport System.Directory.Extra\nimport System.Process.Extra\nimport System.FilePath\nimport Test.Hspec\n\ntype Value = Word32\n\ndata Bindings\n  = Bindings\n  { dynamicLib :: DL\n  , symBindings :: S.Bindings\n  , withHashMap :: (Ptr HashMap -> IO ()) -> IO ()\n  , bInit :: Ptr HashMap -> IO ()\n  , bDestroy :: Ptr HashMap -> IO ()\n  , bGetOrPut :: Ptr HashMap -> Ptr S.Symbol -> Value -> IO Value\n  , bLookup :: Ptr HashMap -> Ptr S.Symbol -> IO Value\n  , bContains :: Ptr HashMap -> Ptr S.Symbol -> IO Bool\n  }\n\nspec :: Spec\nspec = describe \"HashMap\" $ aroundAll (setupAndTeardown testDir) $ parallel $ do\n  it \"can be initialized and destroyed\" $ \\bindings ->\n    withHashMap bindings $ \\hm -> do\n      bInit bindings hm\n      bDestroy bindings hm\n\n  it \"stores a new value if the requested key was not found\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withHashMap bindings $ \\hm -> do\n      bInit bindings hm\n\n      withSym sBindings \"abcd\" $ \\sym -> do\n        value1 <- bGetOrPut bindings hm sym 42\n        value1 `shouldBe` 42\n\n        -- different symbol -> separate entry in the hashmap\n        withSym sBindings \"abcdef\" $ \\sym' -> do\n          value3 <- bGetOrPut bindings hm sym' 34\n          value3 `shouldBe` 34\n          pass\n\n      bDestroy bindings hm\n\n  it \"retrieves the old value if the requested key was found\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withHashMap bindings $ \\hm -> do\n      bInit bindings hm\n\n      withSym sBindings \"abcd\" $ \\sym -> do\n        value1 <- bGetOrPut bindings hm sym 42\n        value1 `shouldBe` 42\n        value2 <- bGetOrPut bindings hm sym 100\n        value2 `shouldBe` 42\n\n        -- same symbol -> same entry in the hashmap\n        withSym sBindings \"abcd\" $ \\sym' -> do\n          value4 <- bGetOrPut bindings hm sym' 34\n          value4 `shouldBe` 42\n\n      bDestroy bindings hm\n\n  it \"is possible to lookup keys in the hashmap\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withHashMap bindings $ \\hm -> do\n      bInit bindings hm\n\n      -- key found\n      withSym sBindings \"abcd\" $ \\sym -> do\n        _ <- bGetOrPut bindings hm sym 42\n        value <- bLookup bindings hm sym\n        value `shouldBe` 42\n\n      -- key not found\n      withSym sBindings \"123\" $ \\sym -> do\n        value <- bLookup bindings hm sym\n        value `shouldBe` 0xffffffff\n\n      bDestroy bindings hm\n\n  it \"is possible to check if a hashmap contains a key\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withHashMap bindings $ \\hm -> do\n      bInit bindings hm\n\n      -- key found\n      withSym sBindings \"abcd\" $ \\sym -> do\n        _ <- bGetOrPut bindings hm sym 42\n        value <- bContains bindings hm sym\n        value `shouldBe` True\n\n      -- key not found\n      withSym sBindings \"123\" $ \\sym -> do\n        value <- bContains bindings hm sym\n        value `shouldBe` False\n\n      bDestroy bindings hm\n\n-- TODO big hashmap test + test for colissions\n\nsetupAndTeardown :: FilePath -> ActionWith Bindings -> IO ()\nsetupAndTeardown dir =\n  bracket (setup dir) teardown\n\nsetup :: FilePath -> IO Bindings\nsetup dir = do\n  createDirectoryIfMissing False dir\n  compileCode cgExternals cgTestCode dir\n  loadNativeCode dir\n\nteardown :: Bindings -> IO ()\nteardown =\n  dlclose . dynamicLib\n\ncompileCode\n  :: ModuleBuilderT IO Externals\n  -> (S.Symbol -> HashMap -> Externals -> ModuleBuilderT IO ())\n  -> FilePath -> IO ()\ncompileCode cgExts cgHelperCode dir = do\n  ctx <- LibLLVM.mkContext\n  llvmMod <- LibLLVM.mkModule ctx \"eclair\"\n  td <- LibLLVM.getTargetData llvmMod\n  llvmIR <- runModuleBuilderT $ do\n    exts <- cgExts\n    let cfg = Config Nothing ctx td\n    sym <- hoist intoIO $ S.codegen exts\n    hm <- runConfigT cfg $ codegen sym exts\n    cgHelperCode sym hm exts\n  let llvmIRText = ppllvm llvmIR\n  writeFileText (llFile dir) llvmIRText\n  callProcess \"clang\" [\"-fPIC\", \"-shared\", \"-O0\", \"-o\", soFile dir, llFile dir]\n\nintoIO :: Identity a -> IO a\nintoIO = pure . runIdentity\n\ncgExternals :: ModuleBuilderT IO Externals\ncgExternals = do\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  freeFn <- extern \"free\" [ptr i8] void\n  memcpyFn <- extern \"memcpy\" [ptr i8, ptr i8, i64] (ptr i8)\n  memcmpFn <- extern \"memcmp\" [ptr i8, ptr i8, i64] i32\n  pure $ Externals mallocFn freeFn notUsed memcpyFn memcmpFn notUsed notUsed\n\ncgTestCode :: S.Symbol -> HashMap -> Externals -> ModuleBuilderT IO ()\ncgTestCode sym hm exts = do\n  let hmTypes = hashMapTypes hm\n      hmTy = tyHashMap hmTypes\n      tySym = tyKey hmTypes\n      mallocFn = extMalloc exts\n      freeFn = extFree exts\n\n  _ <- function \"eclair_hashmap_new\" [] (ptr hmTy) $ \\[] ->\n    ret =<< call mallocFn [int32 $ 64 * 32] -- 64 vectors long\n  _ <- function \"eclair_hashmap_delete\" [(ptr hmTy, \"hm\")] void $ \\[h] ->\n    call freeFn [h]\n  let args = [(ptr hmTy, \"hashmap\"), (ptr tySym, \"symbol\")]\n  _ <- function \"eclair_hashmap_contains_helper\" args i8 $ \\[h, s] -> do\n    result <- call (hashMapContains hm) [h, s]\n    ret =<< result `zext` i8\n\n  S.cgTestCode sym exts\n\nloadNativeCode :: FilePath -> IO Bindings\nloadNativeCode dir = do\n  lib <- dlopen (soFile dir) [RTLD_LAZY]\n  sBindings <- S.loadNativeCode' lib\n  fnNew <- dlsym lib \"eclair_hashmap_new\"\n  fnDelete <- dlsym lib \"eclair_hashmap_delete\"\n  fnInit <- dlsym lib \"eclair_hashmap_init\"\n  fnDestroy <- dlsym lib \"eclair_hashmap_destroy\"\n  fnGetOrPut <- dlsym lib \"eclair_hashmap_get_or_put_value\"\n  fnContains <- dlsym lib \"eclair_hashmap_contains_helper\"\n  fnLookup <- dlsym lib \"eclair_hashmap_lookup\"\n  pure $ Bindings\n    { dynamicLib = lib\n    , symBindings = sBindings\n    , withHashMap = mkWithHashMap fnNew fnDelete\n    , bInit = mkInit fnInit\n    , bDestroy = mkDestroy fnDestroy\n    , bGetOrPut = mkGetOrPut fnGetOrPut\n    , bContains = mkContains fnContains\n    , bLookup = mkLookup fnLookup\n    }\n  where\n    mkNew fn = callFFI fn (retPtr retVoid) []\n    mkDelete fn hm = callFFI fn retVoid [argPtr hm]\n    mkWithHashMap fnNew fnDelete =\n      bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete)\n    mkInit fn hm = callFFI fn retVoid [argPtr hm]\n    mkDestroy fn hm = callFFI fn retVoid [argPtr hm]\n    mkGetOrPut fn hm sym value =\n      fromIntegral <$> callFFI fn retCUInt [argPtr hm, argPtr sym, argCUInt $ fromIntegral value]\n    mkContains fn hm sym = do\n      result <- callFFI fn retCUChar [argPtr hm, argPtr sym]\n      pure $ result == 1\n    mkLookup fn hm sym =\n      fromIntegral <$> callFFI fn retCUInt [argPtr hm, argPtr sym]\n\ntestDir :: FilePath\ntestDir = \"/tmp/eclair-hashmap\"\n\nllFile, soFile :: FilePath -> FilePath\nllFile dir = dir </> \"hashmap.ll\"\nsoFile dir = dir </> \"hashmap.so\"\n\nnotUsed :: a\nnotUsed = panic \"Not used\"\n\nwithSym :: S.Bindings -> String -> (Ptr S.Symbol -> IO a) -> IO a\nwithSym bindings str f = do\n  S.withSymbol bindings $ \\sym -> do\n    S.bInit bindings sym str\n    result <- f sym\n    S.bDestroy bindings sym\n    pure result\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/HashSpec.hs",
    "content": "module Test.Eclair.LLVM.HashSpec\n  ( module Test.Eclair.LLVM.HashSpec\n  ) where\n\nimport qualified Data.Set as Set\nimport Eclair.LLVM.Hash\nimport Test.Hspec\n\n\ndata MyOption = Option0 | Option1\n  deriving stock Enum\n  deriving ToHash via HashEnum MyOption\n\ndata Prefixed = Prefixed\n  deriving stock Generic\n  deriving ToHash via HashWithPrefix \"prefix\" Prefixed\n\ndata Config = Config Int Int Int MyOption\n  deriving stock Generic\n  deriving ToHash via HashWithPrefix \"config\" Config\n\ndata OnlyInt\n  = OnlyInt\n  { getInt :: Int\n  , getText :: Text\n  , getMyOption :: MyOption\n  }\n  deriving ToHash via HashOnly \"getInt\" OnlyInt\n\nspec :: Spec\nspec = describe \"Hashing data\" $ parallel $ do\n  it \"can hash ints\" $ do\n    unHash (getHash (1234 :: Int)) `shouldBe` \"1234\"\n\n  it \"can hash texts\" $ do\n    unHash (getHash (\"12345\" :: Text)) `shouldBe` \"12345\"\n\n  it \"can hash lists\" $ do\n    unHash (getHash ([\"123\", \"45\", \"6\"] :: [Text])) `shouldBe` \"123_45_6\"\n\n  it \"can hash non empty lists\" $ do\n    let nonEmptyList = \"123\" :| [\"45\", \"6\"] :: NonEmpty Text\n    unHash (getHash nonEmptyList) `shouldBe` \"123_45_6\"\n\n  it \"can hash sets\" $ do\n    let set = Set.fromList [123, 45, 6] :: Set Int\n    -- NOTE: set is sorted => different ordering!\n    unHash (getHash set) `shouldBe` \"6_45_123\"\n\n  it \"can hash sum types as enums\" $ do\n    unHash (getHash Option0) `shouldBe` \"0\"\n    unHash (getHash Option1) `shouldBe` \"1\"\n\n  it \"can hash with a prefix\" $ do\n    unHash (getHash Prefixed) `shouldBe` \"prefix__0\"\n\n  it \"can hash generic product types\" $ do\n    let cfg = Config 1 2 3 Option1\n    unHash (getHash cfg) `shouldBe` \"config__1__2__3__1\"\n\n  it \"can hash only a specific field of a record\" $ do\n    let onlyInt = OnlyInt 123 \"abc\" Option1\n    unHash (getHash onlyInt) `shouldBe` \"123\"\n\n  it \"can combine hashes\" $ do\n    let x, y :: Int\n        x = 42\n        y = 1000\n    unHash (getHash x <> getHash y) `shouldBe` \"42__1000\"\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/SymbolSpec.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.SymbolSpec\n  ( module Test.Eclair.LLVM.SymbolSpec\n  ) where\n\nimport Prelude hiding (void, Symbol)\nimport Test.Eclair.LLVM.SymbolUtils\nimport Control.Monad.Morph\nimport Control.Exception\nimport Eclair.LLVM.Symbol\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport Eclair.LLVM.Externals\nimport System.Posix.DynamicLinker\nimport System.Directory.Extra\nimport System.Process.Extra\nimport Test.Hspec\n\n\nspec :: Spec\nspec = describe \"Symbol\" $ aroundAll (setupAndTeardown testDir) $ parallel $ do\n  it \"can be initialized and destroyed\" $ \\bindings ->\n    withSymbol bindings $ \\s -> do\n      let str = \"my example string\"\n          len = fromIntegral $ length str\n      bInit bindings s str\n      bLength bindings s >>= (`shouldBe` len)\n      bData bindings s >>= (`shouldBe` str)\n      bDestroy bindings s\n\n  it \"is possible to compare 2 symbols\" $ \\bindings ->\n    withSymbol bindings $ \\s1 -> do\n      withSymbol bindings $ \\s2 -> do\n        bInit bindings s1 \"abc\"\n        bInit bindings s2 \"1234\"\n\n        isEq1 <- bIsEqual bindings s1 s2\n        isEq2 <- bIsEqual bindings s1 s1\n        isEq3 <- bIsEqual bindings s2 s2\n\n        bDestroy bindings s1\n        bDestroy bindings s2\n\n        isEq1 `shouldBe` False\n        isEq2 `shouldBe` True\n        isEq3 `shouldBe` True\n\n\nsetupAndTeardown :: FilePath -> ActionWith Bindings -> IO ()\nsetupAndTeardown dir =\n  bracket (setup dir) teardown\n\nsetup :: FilePath -> IO Bindings\nsetup dir = do\n  createDirectoryIfMissing False dir\n  compileCode cgExternals cgTestCode dir\n  loadNativeCode dir\n\nteardown :: Bindings -> IO ()\nteardown =\n  dlclose . dynamicLib\n\ncompileCode\n  :: ModuleBuilderT IO Externals\n  -> (Symbol -> Externals -> ModuleBuilderT IO ())\n  -> FilePath -> IO ()\ncompileCode cgExts cgHelperCode dir = do\n  llvmIR <- runModuleBuilderT $ do\n    exts <- cgExts\n    symbol <- hoist intoIO $ codegen exts\n    cgHelperCode symbol exts\n  let llvmIRText = ppllvm llvmIR\n  writeFileText (llFile dir) llvmIRText\n  callProcess \"clang\" [\"-fPIC\", \"-shared\", \"-O0\", \"-o\", soFile dir, llFile dir]\n\ncgExternals :: ModuleBuilderT IO Externals\ncgExternals = do\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  freeFn <- extern \"free\" [ptr i8] void\n  memcpyFn <- extern \"memcpy\" [ptr i8, ptr i8, i64] (ptr i8)\n  memcmpFn <- extern \"memcmp\" [ptr i8, ptr i8, i64] i32\n  pure $ Externals mallocFn freeFn notUsed memcpyFn memcmpFn notUsed notUsed\n\ntestDir :: FilePath\ntestDir = \"/tmp/eclair-symbol\"\n\nnotUsed :: a\nnotUsed = panic \"Not used\"\n\nintoIO :: Identity a -> IO a\nintoIO = pure . runIdentity\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/SymbolTableSpec.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.SymbolTableSpec\n  ( module Test.Eclair.LLVM.SymbolTableSpec\n  ) where\n\nimport Prelude hiding (void, Symbol)\nimport qualified LLVM.C.API as LibLLVM\nimport qualified Test.Eclair.LLVM.SymbolUtils as S\nimport Control.Monad.Morph\nimport Control.Exception\nimport Eclair.LLVM.SymbolTable\nimport qualified Eclair.LLVM.Symbol as S\nimport qualified Eclair.LLVM.Vector as V\nimport qualified Eclair.LLVM.HashMap as HM\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport Eclair.LLVM.Externals\nimport System.Posix.DynamicLinker\nimport System.Directory.Extra\nimport System.Process.Extra\nimport System.FilePath\nimport Foreign hiding (void)\nimport Foreign.LibFFI\nimport Test.Hspec\n\n\ntype Symbol = S.Symbol\ntype Value = Word32\n\ndata Bindings\n  = Bindings\n  { dynamicLib :: DL\n  , symBindings :: S.Bindings\n  , withSymTab :: (Ptr SymbolTable -> IO ()) -> IO ()\n  , bInit :: Ptr SymbolTable -> IO ()\n  , bDestroy :: Ptr SymbolTable -> IO ()\n  , bFindOrInsert :: Ptr SymbolTable -> Ptr Symbol -> IO Value\n  -- NOTE: no need to free returned symbol after lookup\n  , bLookupSymbol :: Ptr SymbolTable -> Value -> IO (Ptr Symbol)\n  , bContainsSymbol :: Ptr SymbolTable -> Ptr Symbol -> IO Bool\n  , bLookupIndex :: Ptr SymbolTable -> Ptr Symbol -> IO Value\n  , bContainsIndex :: Ptr SymbolTable -> Value -> IO Bool\n  }\n\nspec :: Spec\nspec = describe \"Symbol table\" $ aroundAll (setupAndTeardown testDir) $ parallel $ do\n  it \"can be initialized and destroyed\" $ \\bindings ->\n    withSymTab bindings $ \\st -> do\n      bInit bindings st\n      bDestroy bindings st\n\n  it \"is possible to add symbols to the table\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withSymTab bindings $ \\st -> do\n      bInit bindings st\n      _ <- S.withSymbol sBindings $ \\sym -> do\n        S.bInit sBindings sym \"abcd\"\n        idx <- bFindOrInsert bindings st sym\n        idx `shouldBe` 0\n        idx' <- bFindOrInsert bindings st sym\n        idx' `shouldBe` 0\n        -- Owned by symbol table now:\n        -- S.bDestroy sBindings sym\n\n      _ <- S.withSymbol sBindings $ \\sym -> do\n        S.bInit sBindings sym \"123\"\n        idx <- bFindOrInsert bindings st sym\n        idx `shouldBe` 1\n        -- Owned by symbol table now:\n        -- S.bDestroy sBindings sym\n\n      bDestroy bindings st\n\n  it \"is possible to check if the table contains a key\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withSymTab bindings $ \\st -> do\n      bInit bindings st\n      _ <- S.withSymbol sBindings $ \\sym -> do\n        S.bInit sBindings sym \"abcd\"\n        result1 <- bContainsSymbol bindings st sym\n        _ <- bFindOrInsert bindings st sym\n        result2 <- bContainsSymbol bindings st sym\n        S.bDestroy sBindings sym\n        result1 `shouldBe` False\n        result2 `shouldBe` True\n      pass\n\n  it \"is possible to check if the table contains a value\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withSymTab bindings $ \\st -> do\n      bInit bindings st\n      _ <- S.withSymbol sBindings $ \\sym -> do\n        S.bInit sBindings sym \"abcd\"\n        result1 <- bContainsIndex bindings st 0\n        _ <- bFindOrInsert bindings st sym\n        result2 <- bContainsIndex bindings st 0\n        S.bDestroy sBindings sym\n        result1 `shouldBe` False\n        result2 `shouldBe` True\n      pass\n\n  it \"is possible to lookup a key corresponding to a value\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withSymTab bindings $ \\st -> do\n      bInit bindings st\n      _ <- S.withSymbol sBindings $ \\sym -> do\n        S.bInit sBindings sym \"abcd\"\n        -- NOTE: unsafe lookup, don't use it before symbol is inserted\n        -- sym1 <- bLookupSymbol bindings st 0\n        idx <- bFindOrInsert bindings st sym\n        idx `shouldBe` 0\n        print =<< bContainsSymbol bindings st sym\n        sym' <- bLookupSymbol bindings st idx\n        -- Owned by symbol table now:\n        -- S.bDestroy sBindings sym\n        result <- S.bIsEqual sBindings sym sym'\n        result `shouldBe` True\n        pass\n      pass\n\n  it \"is possible to lookup a value corresponding to a key\" $ \\bindings -> do\n    let sBindings = symBindings bindings\n    withSymTab bindings $ \\st -> do\n      bInit bindings st\n      _ <- S.withSymbol sBindings $ \\sym -> do\n        S.bInit sBindings sym \"abcd\"\n        -- NOTE: unsafe lookup, don't use it before symbol is inserted\n        idx1 <- bLookupIndex bindings st sym\n        _ <- bFindOrInsert bindings st sym\n        idx2 <- bLookupIndex bindings st sym\n        S.bDestroy sBindings sym\n        idx1 `shouldBe` 0xffffffff\n        idx2 `shouldBe` 0\n      pass\n\nsetupAndTeardown :: FilePath -> ActionWith Bindings -> IO ()\nsetupAndTeardown dir =\n  bracket (setup dir) teardown\n\nsetup :: FilePath -> IO Bindings\nsetup dir = do\n  createDirectoryIfMissing False dir\n  compileCode cgExternals cgTestCode dir\n  loadNativeCode dir\n\nteardown :: Bindings -> IO ()\nteardown =\n  dlclose . dynamicLib\n\ncompileCode\n  :: ModuleBuilderT IO Externals\n  -> (Symbol -> SymbolTable -> Externals -> ModuleBuilderT IO ())\n  -> FilePath -> IO ()\ncompileCode cgExts cgHelperCode dir = do\n  ctx <- LibLLVM.mkContext\n  llvmMod <- LibLLVM.mkModule ctx \"eclair\"\n  td <- LibLLVM.getTargetData llvmMod\n  llvmIR <- runModuleBuilderT $ do\n    exts <- cgExts\n    symbol <- hoist intoIO $ S.codegen exts\n    let cfg = Config Nothing ctx td\n        symbolDestructor iterPtr = do\n          _ <- call (S.symbolDestroy symbol) [iterPtr]\n          pass\n    vec <- instantiate \"test\" (S.tySymbol symbol) $ runConfigT cfg $ V.codegen exts (Just symbolDestructor)\n    hm <- runConfigT cfg $ HM.codegen symbol exts\n    symTab <- hoist intoIO $ codegen (S.tySymbol symbol) vec hm\n    cgHelperCode symbol symTab exts\n  let llvmIRText = ppllvm llvmIR\n  writeFileText (llFile dir) llvmIRText\n  callProcess \"clang\" [\"-fPIC\", \"-shared\", \"-O0\", \"-o\", soFile dir, llFile dir]\n\ncgExternals :: ModuleBuilderT IO Externals\ncgExternals = do\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  freeFn <- extern \"free\" [ptr i8] void\n  memcpyFn <- extern \"memcpy\" [ptr i8, ptr i8, i64] (ptr i8)\n  memcmpFn <- extern \"memcmp\" [ptr i8, ptr i8, i64] i32\n  pure $ Externals mallocFn freeFn notUsed memcpyFn memcmpFn notUsed notUsed\n\ncgTestCode :: S.Symbol -> SymbolTable -> Externals -> ModuleBuilderT IO ()\ncgTestCode sym symTab exts = do\n  let mallocFn = extMalloc exts\n      freeFn = extFree exts\n      tySym = S.tySymbol sym\n      symTabTy = tySymbolTable symTab\n  _ <- function \"eclair_symbol_table_new\" [] (ptr symTabTy) $ \\[] ->\n    ret =<< call mallocFn [int32 4096]\n  _ <- function \"eclair_symbol_table_delete\" [(ptr symTabTy, \"hm\")] void $ \\[h] ->\n    call freeFn [h]\n  let args = [(ptr symTabTy, \"symbol_table\"), (ptr tySym, \"symbol\")]\n  _ <- function \"eclair_symbol_table_contains_symbol_helper\" args i8 $ \\[st, s] -> do\n    result <- call (symbolTableContainsSymbol symTab) [st, s]\n    ret =<< result `zext` i8\n  let args' = [(ptr symTabTy, \"symbol_table\"), (i32, \"value\")]\n  _ <- function \"eclair_symbol_table_contains_index_helper\" args' i8 $ \\[st, v] -> do\n    result <- call (symbolTableContainsIndex symTab) [st, v]\n    ret =<< result `zext` i8\n\n  S.cgTestCode sym exts\n\nloadNativeCode :: FilePath -> IO Bindings\nloadNativeCode dir = do\n  lib <- dlopen (soFile dir) [RTLD_LAZY]\n  sBindings <- S.loadNativeCode' lib\n  fnNew <- dlsym lib \"eclair_symbol_table_new\"\n  fnDelete <- dlsym lib \"eclair_symbol_table_delete\"\n  fnInit <- dlsym lib \"eclair_symbol_table_init\"\n  fnDestroy <- dlsym lib \"eclair_symbol_table_destroy\"\n  fnFindOrInsert <- dlsym lib \"eclair_symbol_table_find_or_insert\"\n  fnLookupSymbol <- dlsym lib \"eclair_symbol_table_lookup_symbol\"\n  fnContainsSymbol <- dlsym lib \"eclair_symbol_table_contains_symbol_helper\"\n  fnLookupIndex <- dlsym lib \"eclair_symbol_table_lookup_index\"\n  fnContainsIndex <- dlsym lib \"eclair_symbol_table_contains_index_helper\"\n  pure $ Bindings\n    { dynamicLib = lib\n    , symBindings = sBindings\n    , withSymTab = mkWithSymTab fnNew fnDelete\n    , bInit = mkInit fnInit\n    , bDestroy = mkDestroy fnDestroy\n    , bFindOrInsert = mkFindOrInsert fnFindOrInsert\n    , bLookupSymbol = mkLookupSymbol fnLookupSymbol\n    , bLookupIndex = mkLookupIndex fnLookupIndex\n    , bContainsSymbol = mkContainsSymbol fnContainsSymbol\n    , bContainsIndex = mkContainsIndex fnContainsIndex\n    }\n  where\n    mkNew fn = callFFI fn (retPtr retVoid) []\n    mkDelete fn st = callFFI fn retVoid [argPtr st]\n    mkWithSymTab fnNew fnDelete =\n      bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete)\n    mkInit fn st = callFFI fn retVoid [argPtr st]\n    mkDestroy fn st = callFFI fn retVoid [argPtr st]\n    mkFindOrInsert fn st sym =\n      fromIntegral <$> callFFI fn retCUInt [argPtr st, argPtr sym]\n    mkLookupSymbol fn st value =\n      castPtr <$> callFFI fn (retPtr retVoid) [argPtr st, argCUInt $ fromIntegral value]\n    mkLookupIndex fn st sym =\n      fromIntegral <$> callFFI fn retCUInt [argPtr st, argPtr sym]\n    mkContainsSymbol fn st sym = do\n      result <- callFFI fn retCUChar [argPtr st, argPtr sym]\n      pure $ result == 1\n    mkContainsIndex fn st value = do\n      result <- callFFI fn retCUChar [argPtr st, argCUInt $ fromIntegral value]\n      pure $ result == 1\n\ntestDir :: FilePath\ntestDir = \"/tmp/eclair-symbol-table\"\n\nnotUsed :: a\nnotUsed = panic \"Not used\"\n\nintoIO :: Identity a -> IO a\nintoIO = pure . runIdentity\n\nllFile, soFile :: FilePath -> FilePath\nllFile dir = dir </> \"symbol-table.ll\"\nsoFile dir = dir </> \"symbol-table.so\"\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/SymbolUtils.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.SymbolUtils\n  ( Bindings(..)\n  , Symbol(..)\n  , cgTestCode\n  , loadNativeCode\n  , loadNativeCode'\n  , soFile\n  , llFile\n  ) where\n\nimport Prelude hiding (void, Symbol)\nimport Control.Exception\nimport Eclair.LLVM.Symbol\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport Eclair.LLVM.Externals\nimport Foreign.LibFFI\nimport Foreign hiding (void, bit)\nimport System.Posix.DynamicLinker\nimport System.FilePath\nimport Foreign.C\n\ndata Bindings\n  = Bindings\n  { dynamicLib :: DL\n  , withSymbol :: forall a. (Ptr Symbol -> IO a) -> IO a\n  , bInit :: Ptr Symbol -> String -> IO ()\n  , bDestroy :: Ptr Symbol -> IO ()\n  , bIsEqual :: Ptr Symbol -> Ptr Symbol -> IO Bool\n  , bLength :: Ptr Symbol -> IO Word32\n  , bData :: Ptr Symbol -> IO String\n  }\n\ncgTestCode :: Symbol -> Externals -> ModuleBuilderT IO ()\ncgTestCode sym exts = do\n  let mallocFn = extMalloc exts\n      freeFn = extFree exts\n      memcpyFn = extMemcpy exts\n      symTy = tySymbol sym\n  _ <- function \"eclair_symbol_new\" [] (ptr symTy) $ \\[] ->\n    ret =<< call mallocFn [int32 16]\n  _ <- function \"eclair_symbol_delete\" [(ptr symTy, \"sym\")] void $ \\[s] ->\n    call freeFn [s]\n  let initArgs = [(ptr symTy, \"sym\"), (i32, \"length\"), (ptr i8, \"data\")]\n  _ <- function \"eclair_symbol_init_helper\" initArgs void $ \\[s, len, str] -> do\n    -- Needed because \"str\" is freed afterwards\n    memory <- call mallocFn [len]\n    _ <- call memcpyFn [memory, str, len, bit 0]\n    _ <- call (symbolInit sym) [s, len, memory]\n    pass\n  let isEqArgs = [(ptr symTy, \"sym1\"), (ptr symTy, \"sym2\")]\n  _ <- function \"eclair_symbol_is_equal_helper\" isEqArgs i8 $ \\[sym1, sym2] -> do\n    isEq <- call (symbolIsEqual sym) [sym1, sym2]\n    ret =<< isEq `zext` i8\n  _ <- function \"eclair_symbol_length\" [(ptr symTy, \"sym\")] i32 $ \\[s] -> do\n    lenPtr <- gep s [int32 0, int32 0]\n    ret =<< load lenPtr 0\n  _ <- function \"eclair_symbol_data\" [(ptr symTy, \"sym\")] (ptr i8) $ \\[s] -> do\n    lenPtr <- gep s [int32 0, int32 1]\n    ret =<< load lenPtr 0\n  pass\n\nloadNativeCode :: FilePath -> IO Bindings\nloadNativeCode dir = do\n  lib <- dlopen (soFile dir) [RTLD_LAZY]\n  loadNativeCode' lib\n\nloadNativeCode' :: DL -> IO Bindings\nloadNativeCode' lib = do\n  fnNew <- dlsym lib \"eclair_symbol_new\"\n  fnDelete <- dlsym lib \"eclair_symbol_delete\"\n  fnInit <- dlsym lib \"eclair_symbol_init_helper\"\n  fnDestroy <- dlsym lib \"eclair_symbol_destroy\"\n  fnIsEqual <- dlsym lib \"eclair_symbol_is_equal_helper\"\n  fnLength <- dlsym lib \"eclair_symbol_length\"\n  fnData <- dlsym lib \"eclair_symbol_data\"\n  let getLength = mkLength fnLength\n  pure $ Bindings\n    { dynamicLib = lib\n    , withSymbol = mkWithSymbol fnNew fnDelete\n    , bInit = mkInit fnInit\n    , bDestroy = mkDestroy fnDestroy\n    , bIsEqual = mkIsEqual fnIsEqual\n    , bLength = getLength\n    , bData = mkData fnData getLength\n    }\n  where\n    mkNew fn = callFFI fn (retPtr retVoid) []\n    mkDelete fn sym = callFFI fn retVoid [argPtr sym]\n    mkWithSymbol fnNew fnDelete =\n      bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete)\n    mkInit fn sym str = do\n      let len = fromIntegral $ length str\n      callFFI fn retVoid [argPtr sym, argCUInt len, argString str]\n    mkDestroy fn sym = callFFI fn retVoid [argPtr sym]\n    mkIsEqual fn sym1 sym2 = do\n      result <- callFFI fn retCUChar [argPtr sym1, argPtr sym2]\n      pure $ result == 1\n    mkLength fn sym = do\n      fromIntegral <$> callFFI fn retCUInt [argPtr sym]\n    mkData fn getLength sym = do\n      len <- fromIntegral <$> getLength sym\n      strPtr <- callFFI fn (retPtr retCChar) [argPtr sym]\n      peekCAStringLen (strPtr, len)\n\nsoFile :: FilePath -> FilePath\nsoFile dir = dir </> \"symbol.so\"\n\nllFile :: FilePath -> FilePath\nllFile dir = dir </> \"symbol.ll\"\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LLVM/VectorSpec.hs",
    "content": "{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}\nmodule Test.Eclair.LLVM.VectorSpec\n  ( module Test.Eclair.LLVM.VectorSpec\n  ) where\n\nimport Prelude hiding (void)\nimport qualified LLVM.C.API as LibLLVM\nimport Eclair.LLVM.Vector\nimport Eclair.LLVM.Codegen hiding (retVoid, nullPtr)\nimport Eclair.LLVM.Externals\nimport Foreign.LibFFI\nimport Foreign hiding (void)\nimport System.Posix.DynamicLinker\nimport Control.Exception\nimport System.Directory.Extra\nimport System.Process.Extra\nimport System.FilePath\nimport Test.Hspec\n\ntype Value = Int\n\ndata Bindings\n  = Bindings\n  { dynamicLib :: DL\n  , withVec :: (Ptr Vector -> IO ()) -> IO ()\n  , bInit :: Ptr Vector -> IO ()\n  , bDestroy :: Ptr Vector -> IO ()\n  , bPush :: Ptr Vector -> Value -> IO Word32\n  , bSize :: Ptr Vector -> IO Word64\n  , bCapacity :: Ptr Vector -> IO Word64\n  , bGetValue :: Ptr Vector -> Int -> IO Value\n  }\n\nspec :: Spec\nspec = describe \"Vector\" $ aroundAll (setupAndTeardown testDir) $ parallel $ do\n  it \"can be initialized and destroyed\" $ \\bindings ->\n    withVec bindings $ \\v -> do\n      bInit bindings v\n      bDestroy bindings v\n\n  it \"can store multiple elements\" $ \\bindings -> do\n    withVec bindings $ \\v -> do\n      bInit bindings v\n      idx1 <- bPush bindings v 42\n      idx2 <- bPush bindings v 123\n      value2 <- bGetValue bindings v 1\n      value1 <- bGetValue bindings v 0\n      bDestroy bindings v\n      idx1 `shouldBe` 0\n      idx2 `shouldBe` 1\n      value1 `shouldBe` 42\n      value2 `shouldBe` 123\n\n  it \"can store duplicate values\" $ \\bindings -> do\n    withVec bindings $ \\v -> do\n      bInit bindings v\n      idx1 <- bPush bindings v 42\n      idx2 <- bPush bindings v 42\n      value1 <- bGetValue bindings v 0\n      value2 <- bGetValue bindings v 1\n      bDestroy bindings v\n      idx1 `shouldBe` 0\n      idx2 `shouldBe` 1\n      value1 `shouldBe` 42\n      value2 `shouldBe` 42\n\n  it \"keeps track of the number of elements inside\" $ \\bindings ->\n    withVec bindings $ \\v -> do\n      bInit bindings v\n\n      bSize bindings v >>= (`shouldBe` 0)\n      -- This vector allocates on initialization\n      bCapacity bindings v >>= (`shouldBe` 16)\n\n      _ <- bPush bindings v 1\n      bSize bindings v >>= (`shouldBe` 1)\n      bCapacity bindings v >>= (`shouldBe` 16)\n      _ <- bPush bindings v 2\n      bSize bindings v >>= (`shouldBe` 2)\n      bCapacity bindings v >>= (`shouldBe` 16)\n\n      for_ [0..13] $ bPush bindings v\n      bSize bindings v >>= (`shouldBe` 16)\n      bCapacity bindings v >>= (`shouldBe` 16)\n\n      _ <- bPush bindings v 42\n      bSize bindings v >>= (`shouldBe` 17)\n      bCapacity bindings v >>= (`shouldBe` 32)\n\n      for_ [0..15] $ bPush bindings v\n      bSize bindings v >>= (`shouldBe` 33)\n      bCapacity bindings v >>= (`shouldBe` 64)\n\n      bDestroy bindings v\n\n  it \"always keeps order of elements, even after resizing\" $ \\bindings ->\n    withVec bindings $ \\v -> do\n      bInit bindings v\n\n      -- This does several reallocations\n      for_ [0..99] $ bPush bindings v\n      bSize bindings v >>= (`shouldBe` 100)\n      bCapacity bindings v >>= (`shouldBe` 128)\n\n      for_ [0..99] $ \\i -> do\n        bGetValue bindings v i >>= (`shouldBe` i)\n\n      bDestroy bindings v\n\nsetupAndTeardown :: FilePath -> ActionWith Bindings -> IO ()\nsetupAndTeardown dir =\n  bracket (setup dir) teardown\n\nsetup :: FilePath -> IO Bindings\nsetup dir = do\n  createDirectoryIfMissing False dir\n  compileCode cgExternals cgTestCode dir\n  loadNativeCode dir\n\nteardown :: Bindings -> IO ()\nteardown =\n  dlclose . dynamicLib\n\ncompileCode\n  :: ModuleBuilderT IO Externals\n  -> (Vector -> Operand -> Operand -> ModuleBuilderT IO ())\n  -> FilePath -> IO ()\ncompileCode cgExts cgHelperCode dir = do\n  ctx <- LibLLVM.mkContext\n  llvmMod <- LibLLVM.mkModule ctx \"eclair\"\n  td <- LibLLVM.getTargetData llvmMod\n  llvmIR <- runModuleBuilderT $ do\n    exts <- cgExts\n    let cfg = Config Nothing ctx td\n    vec <- instantiate \"test\" i32 $ runConfigT cfg $\n      codegen exts Nothing -- TODO destructor\n    cgHelperCode vec (extMalloc exts) (extFree exts)\n  let llvmIRText = ppllvm llvmIR\n  writeFileText (llFile dir) llvmIRText\n  callProcess \"clang\" [\"-fPIC\", \"-shared\", \"-O0\", \"-o\", soFile dir, llFile dir]\n\ncgExternals :: ModuleBuilderT IO Externals\ncgExternals = do\n  mallocFn <- extern \"malloc\" [i32] (ptr i8)\n  freeFn <- extern \"free\" [ptr i8] void\n  memcpyFn <- extern \"llvm.memcpy.p0i8.p0i8.i64\" [ptr i8, ptr i8, i64, i1] void\n  pure $ Externals mallocFn freeFn notUsed memcpyFn notUsed notUsed notUsed\n\ncgTestCode :: Vector -> Operand -> Operand -> ModuleBuilderT IO ()\ncgTestCode vec mallocFn freeFn = do\n  let vecTypes = vectorTypes vec\n      vecTy = tyVector vecTypes\n      valueTy = tyElement vecTypes\n  _ <- function \"eclair_vector_new_test\" [] (ptr vecTy) $ \\[] ->\n    ret =<< call mallocFn [int32 24]\n  _ <- function \"eclair_vector_delete_test\" [(ptr vecTy, \"vec\")] void $ \\[v] ->\n    call freeFn [v]\n  _ <- function \"eclair_vector_capacity_test\" [(ptr vecTy, \"vec\")] i32 $ \\[v] -> do\n    capPtr <- gep v [int32 0, int32 2]\n    ret =<< load capPtr 0\n  _ <- function \"eclair_value_new_test\" [(i32, \"value\")] (ptr valueTy) $ \\[v] -> do\n    vPtr <- call mallocFn [int32 4]\n    store vPtr 0 v\n    ret vPtr\n  _ <- function \"eclair_value_delete_test\" [(ptr valueTy, \"value\")] void $ \\[v] ->\n    call freeFn [v]\n  pass\n\nloadNativeCode :: FilePath -> IO Bindings\nloadNativeCode dir = do\n  lib <- dlopen (soFile dir) [RTLD_LAZY]\n  fnNew <- dlsym lib \"eclair_vector_new_test\"\n  fnDelete <- dlsym lib \"eclair_vector_delete_test\"\n  fnValueNew <- dlsym lib \"eclair_value_new_test\"\n  fnValueDelete <- dlsym lib \"eclair_value_delete_test\"\n  fnInit <- dlsym lib \"eclair_vector_init_test\"\n  fnDestroy <- dlsym lib \"eclair_vector_destroy_test\"\n  fnPush <- dlsym lib \"eclair_vector_push_test\"\n  fnSize <- dlsym lib \"eclair_vector_size_test\"\n  fnCapacity <- dlsym lib \"eclair_vector_capacity_test\"\n  fnGetValue <- dlsym lib \"eclair_vector_get_value_test\"\n  pure $ Bindings\n    { dynamicLib = lib\n    , withVec = mkWithVec fnNew fnDelete\n    , bInit = mkInit fnInit\n    , bDestroy = mkDestroy fnDestroy\n    , bPush = mkPush fnValueNew fnValueDelete fnPush\n    , bSize = mkSize fnSize\n    , bCapacity = mkCapacity fnCapacity\n    , bGetValue = mkGetValue fnGetValue\n    }\n  where\n    mkNew fn = callFFI fn (retPtr retVoid) []\n    mkDelete fn vec = callFFI fn retVoid [argPtr vec]\n    mkWithVec fnNew fnDelete =\n      bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete)\n    mkInit fn vec = callFFI fn retVoid [argPtr vec]\n    mkDestroy fn vec = callFFI fn retVoid [argPtr vec]\n    mkPush fnValueNew fnValueDelete fn vec value =\n      withValue fnValueNew fnValueDelete value $ \\valuePtr ->\n        fromIntegral <$> callFFI fn retCUInt [argPtr vec, argPtr valuePtr]\n    mkSize fn vec =\n      fromIntegral <$> callFFI fn retCULong [argPtr vec]\n    mkCapacity fn vec =\n      fromIntegral <$> callFFI fn retCULong [argPtr vec]\n    mkGetValue fn vec idx = do\n      resultPtr <- callFFI fn (retPtr retCUInt) [argPtr vec, argCUInt $ fromIntegral idx]\n      fromIntegral <$> peek resultPtr\n    withValue fnNew fnDelete value =\n      bracket\n        (castPtr <$> callFFI fnNew (retPtr retCUChar) [argCUInt $ fromIntegral value])\n        (\\valuePtr -> callFFI fnDelete retVoid [argPtr valuePtr])\n\ntestDir :: FilePath\ntestDir = \"/tmp/eclair-vector\"\n\nllFile, soFile :: FilePath -> FilePath\nllFile dir = dir </> \"vector.ll\"\nsoFile dir = dir </> \"vector.so\"\n\nnotUsed :: a\nnotUsed = panic \"Not used\"\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LSP/HandlersSpec.hs",
    "content": "module Test.Eclair.LSP.HandlersSpec\n  ( module Test.Eclair.LSP.HandlersSpec\n  ) where\n\nimport Eclair\nimport Eclair.TypeSystem\nimport Eclair.Common.Location\nimport Eclair.LSP.Monad\nimport Eclair.LSP.Handlers\nimport qualified Data.Map as M\nimport Test.Hspec\n\nspec :: Spec\nspec = describe \"LSP handlers\" $ parallel $ do\n  hoverSpec\n  documentHighlightSpec\n  diagnosticsSpec\n\nhoverSpec :: Spec\nhoverSpec = describe \"Hover action\" $ do\n  it \"reports types on hover\" $ do\n    let file = fixture \"hover.eclair\"\n        srcPos1 = SourcePos 5 7  -- 0-indexed!\n        srcPos2 = SourcePos 11 9\n    (result1, result2) <- withLSP (Just file) $ do\n      (,) <$> hoverHandler file srcPos1\n          <*> hoverHandler file srcPos2\n    result1 `shouldBe`\n      HoverOk (SourceSpan file (SourcePos 5 7) (SourcePos 5 8)) U32\n    result2 `shouldBe`\n      HoverOk (SourceSpan file (SourcePos 11 8) (SourcePos 11 13)) Str\n\n  it \"returns an error if file not found in vfs\" $ do\n    let file = \"not_found.eclair\"\n        srcPos = SourcePos 11 10\n    result <- withLSP Nothing $ hoverHandler file srcPos\n    result `shouldBe` HoverError file (SourcePos 11 10) \"File not found in VFS!\"\n\n  it \"returns an error if no hover information available for position\" $ do\n    let file = fixture \"hover.eclair\"\n        srcPos1 = SourcePos 4 1  -- whitespace\n        srcPos2 = SourcePos 14 10  -- outside of file bounds\n    (result1, result2) <- withLSP (Just file) $ do\n      (,) <$> hoverHandler file srcPos1\n          <*> hoverHandler file srcPos2\n    result1 `shouldBe`\n      HoverError file srcPos1 \"No type information for this position!\"\n    result2 `shouldBe`\n      HoverError file srcPos2 \"Error computing location offset in file!\"\n\n  it \"returns an error if file failed to parse\" $ do\n    let file = fixture \"unparsable.eclair\"\n        srcPos = SourcePos 4 1\n    result <- withLSP (Just file) $ hoverHandler file srcPos\n    result `shouldBe` HoverError file srcPos \"File contains errors!\"\n\n  it \"returns an error if file failed to typecheck\" $ do\n    let file = fixture \"type_errors.eclair\"\n        srcPos = SourcePos 4 1\n    result <- withLSP (Just file) $ hoverHandler file srcPos\n    result `shouldBe` HoverError file srcPos \"File contains errors!\"\n\ndocumentHighlightSpec :: Spec\ndocumentHighlightSpec = describe \"Document highlight action\" $ do\n  it \"highlights the same identifiers in scope\" $ do\n    let file = fixture \"document_highlight.eclair\"\n        srcPos1 = SourcePos 6 10 -- x\n        srcPos2 = SourcePos 6 13 -- y\n        srcPos3 = SourcePos 7 10 -- z\n    (result1, result2, result3) <- withLSP (Just file) $ do\n      (,,) <$> documentHighlightHandler file srcPos1\n           <*> documentHighlightHandler file srcPos2\n           <*> documentHighlightHandler file srcPos3\n    result1 `shouldBe` DocHLOk\n      [ SourceSpan file (SourcePos 6 10) (SourcePos 6 11)\n      , SourceSpan file (SourcePos 7 7) (SourcePos 7 8)\n      ]\n    result2 `shouldBe` DocHLOk\n      [ SourceSpan file (SourcePos 6 13) (SourcePos 6 14)\n      , SourceSpan file (SourcePos 8 15) (SourcePos 8 16)\n      ]\n    result3 `shouldBe` DocHLOk\n      [ SourceSpan file (SourcePos 7 10) (SourcePos 7 11)\n      , SourceSpan file (SourcePos 8 12) (SourcePos 8 13)\n      ]\n\n  it \"returns an error if file not found in vfs\" $ do\n    let file = \"not_found.eclair\"\n        srcPos = SourcePos 11 10\n    result <- withLSP Nothing $ documentHighlightHandler file srcPos\n    result `shouldBe` DocHLError file (SourcePos 11 10) \"File not found in VFS!\"\n\n  it \"returns an error if file failed to parse\" $ do\n    let file = fixture \"unparsable.eclair\"\n        srcPos = SourcePos 4 1\n    result <- withLSP (Just file) $ documentHighlightHandler file srcPos\n    result `shouldBe` DocHLError file srcPos \"Failed to get highlight information!\"\n\ndiagnosticsSpec :: Spec\ndiagnosticsSpec = describe \"Diagnostics action\" $ parallel $ do\n  it \"reports nothing if file is OK\" $ do\n    let file = fixture \"hover.eclair\"\n    DiagnosticsOk diags <- withLSP (Just file) $ diagnosticsHandler file\n    length diags `shouldBe` 0\n\n  it \"reports invalid syntax\" $ do\n    let file = fixture \"invalid_syntax.eclair\"\n    DiagnosticsOk diags <- withLSP (Just file) $ diagnosticsHandler file\n    length diags `shouldBe` 2\n\n  it \"returns an error if file not found in vfs\" $ do\n    let file = \"not_found.eclair\"\n    result <- withLSP Nothing $ diagnosticsHandler file\n    result `shouldBe` DiagnosticsError file Nothing \"File not found in VFS!\"\n\n  it \"reports semantic errors\" $ do\n    let file = fixture \"semantic_errors.eclair\"\n    DiagnosticsOk [diag] <- withLSP (Just file) $ diagnosticsHandler file\n    let (Diagnostic _ _ _ msg) = diag\n    toString msg `shouldContain` \"Wildcard in top level fact\"\n\n  it \"reports type errors\" $ do\n    let file = fixture \"type_errors.eclair\"\n    DiagnosticsOk (_:_:diag:_) <- withLSP (Just file) $ diagnosticsHandler file\n    let (Diagnostic _ _ _ msg) = diag\n    toString msg `shouldContain` \"Type mismatch\"\n\nfixture :: FilePath -> FilePath\nfixture file =\n  \"./tests/eclair/fixtures/lsp/\" <> file\n\nwithLSP :: Maybe FilePath -> LspM a -> IO a\nwithLSP mFile m = runLSP $ do\n  case mFile of\n    Nothing -> pass\n    Just file -> do\n      fileContents <- decodeUtf8 <$> readFileBS file\n      lift $ vfsSetFile file fileContents\n\n  vfsVar <- lift getVfsVar\n  let readVFS path = do\n        vfs <- readMVar vfsVar\n        pure $! M.lookup path vfs\n  let params = Parameters 1 Nothing readVFS\n  local (const params) m\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/LSP/JSONSpec.hs",
    "content": "{-# LANGUAGE QuasiQuotes #-}\n\nmodule Test.Eclair.LSP.JSONSpec\n  ( module Test.Eclair.LSP.JSONSpec\n  ) where\n\nimport qualified Data.Hermes as H\nimport Eclair.Common.Location\nimport Eclair.LSP.Types\nimport Eclair.LSP.JSON\nimport Eclair.LSP.Handlers\nimport Eclair.JSON\nimport Eclair.TypeSystem\nimport Test.Hspec\nimport NeatInterpolation\n\ndecodesAs :: (Eq a, Show a) => H.Decoder a -> Text -> a -> IO ()\ndecodesAs decoder txt expected =\n  H.decodeEither decoder (encodeUtf8 txt)\n    `shouldBe` Right expected\n\nspec :: Spec\nspec = describe \"LSP JSON processing\" $ parallel $ do\n  describe \"JSON encoding\" $ parallel $ do\n    it \"can encode response to JSON\" $ do\n      encodeJSON (responseToJSON (HoverResponse (HoverOk (SourceSpan \"/etc/passwd\" (SourcePos 11 13) (SourcePos 17 19)) U32)))\n        `shouldBe` [text|\n          {\"type\":\"success\",\"hover\":{\"location\":{\"file\":\"/etc/passwd\",\"start\":{\"line\":11,\"column\":13},\"end\":{\"line\":17,\"column\":19}},\"type\":\"u32\"}}\n        |]\n      encodeJSON (responseToJSON (HoverResponse (HoverError \"/etc/passwd\" (SourcePos 11 13) \"sample hover error message\")))\n        `shouldBe` [text|\n          {\"type\":\"error\",\"error\":{\"file\":\"/etc/passwd\",\"position\":{\"line\":11,\"column\":13},\"message\":\"sample hover error message\"}}\n        |]\n      encodeJSON (responseToJSON (DocumentHighlightResponse (DocHLOk [SourceSpan \"/etc/passwd\" (SourcePos 11 13) (SourcePos 17 19)])))\n        `shouldBe` [text|\n          {\"type\":\"success\",\"highlights\":[{\"file\":\"/etc/passwd\",\"start\":{\"line\":11,\"column\":13},\"end\":{\"line\":17,\"column\":19}}]}\n        |]\n      encodeJSON (responseToJSON (DocumentHighlightResponse (DocHLError \"/etc/passwd\" (SourcePos 11 13) \"sample highlight error message\")))\n        `shouldBe` [text|\n          {\"type\":\"error\",\"error\":{\"file\":\"/etc/passwd\",\"position\":{\"line\":11,\"column\":13},\"message\":\"sample highlight error message\"}}\n        |]\n      encodeJSON (responseToJSON (DiagnosticsResponse (DiagnosticsOk [Diagnostic Parser (SourceSpan \"/etc/passwd\" (SourcePos 11 13) (SourcePos 17 19)) Error \"sample diagnostic message\"])))\n        `shouldBe` [text|\n          {\"type\":\"success\",\"diagnostics\":[{\"location\":{\"file\":\"/etc/passwd\",\"start\":{\"line\":11,\"column\":13},\"end\":{\"line\":17,\"column\":19}},\"source\":\"Parser\",\"severity\":\"error\",\"message\":\"sample diagnostic message\"}]}\n        |]\n      encodeJSON (responseToJSON (DiagnosticsResponse (DiagnosticsError \"/etc/passwd\" (Just (SourcePos 11 13)) \"sample diagnostic error message\")))\n        `shouldBe` [text|\n          {\"type\":\"error\",\"error\":{\"file\":\"/etc/passwd\",\"position\":{\"line\":11,\"column\":13},\"message\":\"sample diagnostic error message\"}}\n        |]\n      encodeJSON (responseToJSON (DiagnosticsResponse (DiagnosticsError \"/etc/passwd\" Nothing \"sample diagnostic error message\")))\n        `shouldBe` [text|\n          {\"type\":\"error\",\"error\":{\"file\":\"/etc/passwd\",\"position\":{\"line\":0,\"column\":0},\"message\":\"sample diagnostic error message\"}}\n        |]\n      encodeJSON (responseToJSON SuccessResponse)\n        `shouldBe` [text|\n          {\"success\":true}\n        |]\n      encodeJSON (responseToJSON ShuttingDown)\n        `shouldBe` [text|\n          {\"shutdown\":true}\n        |]\n\n    it \"can encode diagnostic to JSON\" $ do\n      encodeJSON (diagnosticToJSON (Diagnostic Parser (SourceSpan \"/etc/passwd\" (SourcePos 11 13) (SourcePos 17 19)) Error \"sample diagnostic message\"))\n        `shouldBe` [text|\n          {\"location\":{\"file\":\"/etc/passwd\",\"start\":{\"line\":11,\"column\":13},\"end\":{\"line\":17,\"column\":19}},\"source\":\"Parser\",\"severity\":\"error\",\"message\":\"sample diagnostic message\"}\n        |]\n\n    it \"can encode diagnostic source to JSON\" $ do\n      encodeJSON (diagnosticSourceToJSON Parser)\n        `shouldBe` [text|\n          \"Parser\"\n        |]\n      encodeJSON (diagnosticSourceToJSON Typesystem)\n        `shouldBe` [text|\n          \"Typesystem\"\n        |]\n      encodeJSON (diagnosticSourceToJSON SemanticAnalysis)\n        `shouldBe` [text|\n          \"SemanticAnalysis\"\n        |]\n      encodeJSON (diagnosticSourceToJSON Transpiler)\n        `shouldBe` [text|\n          \"Transpiler\"\n        |]\n\n    it \"can encode severity to JSON\" $ do\n      encodeJSON (severityToJSON Error)\n        `shouldBe` [text|\n          \"error\"\n        |]\n\n    it \"can encode source span to JSON\" $ do\n      encodeJSON (srcSpanToJSON (SourceSpan \"/etc/passwd\" (SourcePos 11 13) (SourcePos 17 19)))\n        `shouldBe` [text|\n          {\"file\":\"/etc/passwd\",\"start\":{\"line\":11,\"column\":13},\"end\":{\"line\":17,\"column\":19}}\n        |]\n\n    it \"can encode source position to JSON\" $ do\n      encodeJSON (srcPosToJSON (SourcePos 32 58))\n        `shouldBe` [text|\n          {\"line\":32,\"column\":58}\n        |]\n\n    it \"can encode type to JSON\" $ do\n      encodeJSON (typeToJSON U32) `shouldBe` [text|\n        \"u32\"\n      |]\n      encodeJSON (typeToJSON Str) `shouldBe` [text|\n        \"string\"\n      |]\n\n  describe \"JSON decoding\" $ parallel $ do\n    it \"can decode hover command from JSON\" $ do\n      decodesAs\n        commandDecoder\n        [text|\n          {\n            \"type\": \"hover\",\n            \"command\": {\n              \"position\": {\"line\": 100, \"column\": 22},\n              \"file\": \"/tmp/file.eclair\"\n            }\n          }\n        |]\n        (Hover \"/tmp/file.eclair\" (SourcePos 100 22))\n\n    it \"can decode document-highlight command from JSON\" $ do\n      decodesAs\n        commandDecoder\n        [text|\n          {\n            \"type\": \"document-highlight\",\n            \"command\": {\n              \"position\": {\"line\": 100, \"column\": 22},\n              \"file\": \"/tmp/file.eclair\"\n            }\n          }\n        |]\n        (DocumentHighlight \"/tmp/file.eclair\" (SourcePos 100 22))\n\n    it \"can decode diagnostics command from JSON\" $ do\n      decodesAs\n        commandDecoder\n        [text|\n          {\n            \"type\": \"diagnostics\",\n            \"command\": {\n              \"file\": \"/tmp/file.eclair\"\n            }\n          }\n        |]\n        (Diagnostics \"/tmp/file.eclair\")\n\n    it \"can decode update-vfs command from JSON\" $ do\n      decodesAs\n        commandDecoder\n        [text|\n          {\n            \"type\": \"update-vfs\",\n            \"command\": {\n              \"file\": \"/etc/passwd\",\n              \"contents\": \"root:*:0:0:System Administrator:/var/root:/bin/sh\"\n            }\n          }\n        |]\n        (UpdateVFS \"/etc/passwd\" \"root:*:0:0:System Administrator:/var/root:/bin/sh\")\n\n    it \"can decode a hover command from JSON\" $ do\n      decodesAs\n        hoverDecoder\n        [text|\n          {\n            \"position\": {\"line\": 100, \"column\": 22},\n            \"file\": \"/tmp/file.eclair\"\n          }\n        |]\n        (Hover \"/tmp/file.eclair\" (SourcePos 100 22))\n\n    it \"can decode a document highlight command from JSON\" $ do\n      decodesAs\n        referencesDecoder\n        [text|\n          {\n            \"position\": {\"line\": 100, \"column\": 22},\n            \"file\": \"/tmp/file.eclair\"\n          }\n        |]\n        (DocumentHighlight \"/tmp/file.eclair\" (SourcePos 100 22))\n\n    it \"can decode a diagnostics command from JSON\" $ do\n      decodesAs\n        diagnosticsDecoder\n        [text|\n          {\n            \"file\": \"/etc/passwd\"\n          }\n        |]\n        (Diagnostics \"/etc/passwd\")\n\n    it \"can decode a update-vfs command from JSON\" $ do\n      decodesAs\n        updateVfsDecoder\n        [text|\n          {\n            \"file\": \"/etc/passwd\",\n            \"contents\": \"root:*:0:0:System Administrator:/var/root:/bin/sh\"\n          }\n        |]\n        (UpdateVFS \"/etc/passwd\" \"root:*:0:0:System Administrator:/var/root:/bin/sh\")\n\n    it \"can decode a source position from JSON\" $ do\n      decodesAs\n        srcPosDecoder\n        [text|{\"line\": 42, \"column\": 10}|]\n        (SourcePos 42 10)\n"
  },
  {
    "path": "tests/eclair/Test/Eclair/RA/IndexSelectionSpec.hs",
    "content": "{-# LANGUAGE QuasiQuotes #-}\n\nmodule Test.Eclair.RA.IndexSelectionSpec\n  ( module Test.Eclair.RA.IndexSelectionSpec\n  ) where\n\nimport qualified Data.Map as Map\nimport qualified Data.Set as Set\nimport Test.Hspec\nimport System.FilePath\nimport Eclair.Common.Id\nimport Eclair.Parser\nimport Eclair.AST.Lower\nimport Eclair.RA.IndexSelection\nimport Eclair.RA.Transforms\nimport qualified Eclair.TypeSystem as TS\nimport qualified Data.Text as T\nimport NeatInterpolation\n\n\nidxSel :: FilePath -> Text -> IndexMap\nidxSel path text' = do\n  let file = \"tests/fixtures\" </> path <.> \"eclair\"\n      ast = (\\(parsed, _, _, _) -> parsed) $ parseText file text'\n   in case TS.typeCheck ast of\n        Left _ -> panic $ \"Failed to typecheck \" <> toText file <> \"!\"\n        Right typeInfo -> do\n          let ra = simplify $ compileToRA [] ast\n              (indexMap, _) = runIndexSelection (TS.infoTypedefs typeInfo) ra\n           in indexMap\n\ntoSelection :: [(T.Text, [[Column]])] -> IndexMap\ntoSelection info = idxMap\n  where\n    (texts, colss) = unzip info\n    f text' cols = (Id text', Set.fromList $ map Index cols)\n    idxMap = Map.fromList $ zipWith f texts colss\n\nspec :: Spec\nspec = describe \"Index selection\" $ parallel $ do\n  it \"creates indexes for a single fact\" $ do\n    idxSel \"single_fact\" [text|\n      @def edge(u32, u32).\n      @def another(u32, u32, u32).\n\n      edge(1, 2).\n      edge(2, 3).\n\n      another(1,2,3).\n      |] `shouldBe`\n      toSelection [(\"another\", [[0,1,2]]), (\"edge\", [[0,1]])]\n\n  it \"creates indexes for a single non-recursive rule\" $ do\n    idxSel \"single_nonrecursive_rule\" [text|\n      @def edge(u32, u32).\n      @def path(u32, u32).\n\n      edge(1,2).\n\n      path(x,y) :- edge(x,y).\n      |] `shouldBe`\n      toSelection [(\"edge\", [[0,1]]), (\"path\", [[0,1]])]\n\n  it \"creates indexes for nested searches correctly\" $ do\n    idxSel \"multiple_rule_clauses\" [text|\n      @def first(u32).\n      @def second(u32, u32).\n      @def third(u32, u32).\n\n      first(1).\n      second(2, 3).\n\n      third(x, y) :-\n        first(y),\n        second(x, y).\n      |] `shouldBe`\n      toSelection [ (\"first\",  [[0]])\n                  , (\"second\", [[1,0]])\n                  , (\"third\",  [[0,1]])\n                  ]\n\n  it \"creates indexes for rules with equal columns correctly\" $ do\n    idxSel \"rule_equal_columns\" [text|\n      @def a(u32).\n      @def b(u32, u32).\n      @def c(u32, u32, u32).\n      @def d(u32, u32, u32, u32).\n      @def other(u32).\n\n\n      a(1).\n      b(2, 3).\n      c(4, 5, 6).\n      d(7, 8, 9, 10).\n      other(11).\n\n      a(x) :-\n        b(x, x),\n        other(x).\n\n      a(y) :-\n        c(y, y, y),\n        other(y).\n\n      a(z) :-\n        d(z, z, 12, z),\n        other(z).\n      |] `shouldBe`\n      toSelection [ (\"a\", [[0]])\n                  , (\"b\", [[0,1]])\n                  , (\"c\", [[0,1,2]])\n                  , (\"d\", [[2,0,1,3]])\n                  , (\"other\", [[0]])\n                  ]\n\n  it \"handles multiple indexes on 1 rule correctly\" $ do\n    idxSel \"index_selection\" [text|\n      @def a(u32).\n      @def b(u32).\n      @def c(u32, u32, u32).\n      @def d(u32).\n      @def triple(u32, u32, u32).\n\n\n      a(1).\n      b(1).\n      c(1, 2, 3).\n      d(1).\n      triple(4, 5, 6).\n\n      a(y) :-\n        // [2]\n        triple(x, y, 123).\n\n      b(x) :-\n        // [0,1] => [0,1,2]\n        triple(123, 456, x).\n\n      c(x, y, z) :-\n        // [0,1,2]\n        triple(x, y, z).\n\n      d(x) :-\n        // [0, 2]\n        triple(123, x, 456).\n      |] `shouldBe`\n      toSelection [ (\"a\", [[0]])\n                  , (\"b\", [[0]])\n                  , (\"c\", [[0,1,2]])\n                  , (\"d\", [[0]])\n                  , (\"triple\", [[0,1,2], [2,0]])\n                  ]\n\n  it \"selects a minimal set of indexes for a rule\" $ do\n    idxSel \"minimal_index_selection\" [text|\n      @def first(u32, u32, u32).\n      @def second(u32).\n      @def third(u32).\n      @def fourth(u32).\n      @def fifth(u32).\n\n      // [1,0,2] ([0,1,2] re-ordered)\n      first(1, 2, 3).\n      second(1).\n      third(1).\n      fourth(1).\n      fifth(1).\n\n      second(x) :-\n        // [0,1] => [1,0,2]\n        first(123, 456, x).\n\n      third(x) :-\n        // [2,1]\n        first(x, 123, 456).\n\n      fourth(x) :-\n        // [2] => [2,1]\n        first(x, a, 123).\n\n      fifth(x) :-\n        // [1] => [1,0,2]\n        first(x, 123, a).\n      |] `shouldBe`\n      toSelection [ (\"first\", [[1,0,2], [2,1]])\n                  , (\"second\", [[0]])\n                  , (\"third\", [[0]])\n                  , (\"fourth\", [[0]])\n                  , (\"fifth\", [[0]])\n                  ]\n\n  it \"creates indexes for a rule with 2 clauses of same name\" $ do\n    idxSel \"multiple_clauses_same_name\" [text|\n      @def link(u32, u32).\n      @def chain(u32, u32, u32).\n\n      link(1,2).\n\n      chain(x, y, z) :-\n        link(x, y),\n        link(y, z).\n      |] `shouldBe`\n      toSelection [ (\"link\", [[0,1]])\n                  , (\"chain\", [[0,1,2]])\n                  ]\n\n  it \"creates indexes for a single recursive rule\" $ do\n    idxSel \"single_recursive_rule\" [text|\n      @def edge(u32, u32).\n      @def path(u32, u32).\n\n      edge(1,2).\n\n      path(x, y) :-\n        edge(x, z),\n        path(z, y).\n      |] `shouldBe`\n      toSelection [ (\"delta_path\", [[0,1]])\n                  , (\"new_path\", [[0,1]])\n                  , (\"path\", [[0,1]])\n                  , (\"edge\", [[0,1]])\n                  ]\n\n  -- TODO variant where one is recursive\n\n  it \"creates indexes for mutually recursive rules\" $ do\n    idxSel \"mutually_recursive_rules\" [text|\n      @def a(u32).\n      @def b(u32).\n      @def c(u32).\n      @def d(u32).\n\n      a(x) :- b(x), c(x).\n      b(1).\n      b(x) :- c(x), d(x).\n      c(2).\n      c(x) :- b(x), d(x).\n      d(3).\n      |] `shouldBe`\n      toSelection [ (\"a\", [[0]])\n                  , (\"b\", [[0]])\n                  , (\"new_b\", [[0]])\n                  , (\"delta_b\", [[0]])\n                  , (\"c\", [[0]])\n                  , (\"new_c\", [[0]])\n                  , (\"delta_c\", [[0]])\n                  , (\"d\", [[0]])\n                  ]\n\n  -- TODO tests for rules with >2 clauses, ...\n\n  it \"calculates index correctly for multiple columns at once\" $ do\n    idxSel \"index_for_chain\" [text|\n      @def a(u32, u32, u32, u32, u32).\n      @def b(u32).\n\n\n      a(1,2,3,4,5).\n      b(1).\n\n      b(x) :-\n        // [0,1]\n        a(123, 123, x, y, z).\n\n      b(x) :-\n        // [2,3,4]\n        a(x, y, 123, 123, 123).\n\n      b(x) :-\n        // [4] => [4,2,3]\n        a(x, y, z, a, 123).\n      |] `shouldBe`\n      toSelection [ (\"a\", [[0,1,2,3,4], [4,2,3]])\n                  , (\"b\", [[0]])\n                  ]\n\n  it \"calculates indexes correctly for programs with no top level facts\" $ do\n    idxSel \"no_top_level_facts\" [text|\n      @def edge(u32, u32).\n      @def path(u32, u32).\n\n      path(x, y) :-\n        edge(x, y).\n\n      path(x, z) :-\n        edge(x, y),\n        path(y, z).\n      |] `shouldBe`\n      toSelection [ (\"delta_path\", [[0,1]])\n                  , (\"new_path\", [[0,1]])\n                  , (\"path\", [[0,1]])\n                  , (\"edge\", [[0,1]])\n                  ]\n\n  it \"does not use 'NoElem' constraints to compute indexes\" $ do\n    idxSel \"index_selection_should_only_check_for_equalities\" [text|\n      @def edge(u32, u32).\n      @def reachable(u32, u32).\n\n      reachable(x, y) :-\n        edge(x, y).\n\n      reachable(x, z) :-\n        edge(x, _),\n        reachable(_, z).\n      |] `shouldBe`\n      toSelection [ (\"delta_reachable\", [[0,1]])\n                  , (\"new_reachable\", [[0,1]])\n                  , (\"reachable\", [[0,1]])\n                  , (\"edge\", [[0,1]])\n                  ]\n\n  it \"creates indexes for a rule with arithmetic\" $ do\n    idxSel \"multiple_clauses_same_name\" [text|\n      @def first(u32).\n      @def second(u32, u32).\n      @def third(u32, u32).\n\n      first(1).\n      second(2, 3).\n\n      third(x + 1, y) :-\n        first(y),\n        second(x, y + 1).\n      |] `shouldBe`\n      toSelection [ (\"first\",  [[0]])\n                  , (\"second\", [[1,0]])\n                  , (\"third\",  [[0,1]])\n                  ]\n\n  it \"creates indexes for negations with wildcards correctly\" $ do\n    idxSel \"multiple_rule_clauses\" [text|\n      @def first(u32).\n      @def second(u32, u32).\n      @def third(u32, u32).\n\n      first(1).\n      second(2, 3).\n\n      third(x, x) :-\n        first(x),\n        !second(_, x).\n      |] `shouldBe`\n      toSelection [ (\"first\",  [[0]])\n                  , (\"second\", [[1,0]])\n                  , (\"third\",  [[0,1]])\n                  ]\n    idxSel \"multiple_rule_clauses\" [text|\n      @def first(u32).\n      @def second(u32, u32).\n      @def third(u32, u32).\n      @def fourth(u32).\n\n      first(1).\n      second(2, 3).\n\n      third(x, x) :-\n        first(x),\n        !second(_, x).\n\n      fourth(x) :-\n        first(x),\n        !second(x, _).\n      |] `shouldBe`\n      toSelection [ (\"first\",  [[0]])\n                  , (\"second\", [[0,1], [1]])\n                  , (\"third\",  [[0,1]])\n                  , (\"fourth\", [[0]])\n                  ]\n"
  },
  {
    "path": "tests/eclair/fixtures/lsp/document_highlight.eclair",
    "content": "@def edge(u32, u32).\n@def reachable(u32, u32).\n\nreachable(x, y) :-\n  edge(x, y).\n\nreachable(x, y) :-\n  edge(x, z),\n  reachable(z, y).\n"
  },
  {
    "path": "tests/eclair/fixtures/lsp/hover.eclair",
    "content": "@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n@def literal(string) output.\n\nreachable(x, y) :-\n  edge(x, y).\n\nreachable(x, z) :-\n  edge(x, y),\n  reachable(y, z).\n\nliteral(\"abc\").\n"
  },
  {
    "path": "tests/eclair/fixtures/lsp/invalid_syntax.eclair",
    "content": "@def number(u32) output.\n\nnumber(123, ).\nnumber(456).\nnumber(789, ).\n"
  },
  {
    "path": "tests/eclair/fixtures/lsp/semantic_errors.eclair",
    "content": "@def wildcard_in_fact(u32) output.\n\nwildcard_in_fact(_).\n"
  },
  {
    "path": "tests/eclair/fixtures/lsp/type_errors.eclair",
    "content": "@def edge(u32, u32).\n@def reachable(string, u32).\n@def literal(u32).\n\nreachable(x, y) :-\n  edge(x, y).\n\nreachable(x, z) :-\n  edge(x, y),\n  reachable(y, z).\n\nliteral(\"abc\").\n"
  },
  {
    "path": "tests/eclair/fixtures/lsp/unparsable.eclair",
    "content": "@def edge(u32, u32).\n@def reachable(u32, u32).\n@def literal(string).\n\nreachable(x, y) :-\n\nreachable(x, z) :-\n  1.\n\nliteral(\"abc\")\n"
  },
  {
    "path": "tests/eclair/test.hs",
    "content": "{-# OPTIONS_GHC -Wno-missing-export-lists #-}\n{-# OPTIONS_GHC -F -pgmF hspec-discover #-}\n"
  },
  {
    "path": "tests/end_to_end/compile_and_run_native.eclair",
    "content": "// This checks if eclair can be correctly compiled and linked with C code.\n\n// RUN: split-file %s %t\n// RUN: %eclair compile %t/program.eclair > %t/program.ll\n// RUN: %clang -o %t/program -O0 %t/main.c %t/program.ll\n// RUN: %t/program | FileCheck %s\n\n// CHECK: (1, 2)\n// CHECK-NEXT: (1, 3)\n// CHECK-NEXT: (2, 3)\n\n//--- program.eclair\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n\nreachable(x, y) :-\n  edge(x, y).\n\nreachable(x, y) :-\n  edge(x, z),\n  reachable(z, y).\n\n//--- main.c\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdint.h>\n\n\nstruct program;\n\nextern struct program* eclair_program_init();\nextern void eclair_program_destroy(struct program*);\nextern void eclair_program_run(struct program*);\nextern void eclair_add_facts(struct program*, uint32_t fact_type, uint32_t* data, size_t fact_count);\nextern void eclair_add_fact(struct program*, uint32_t fact_type, uint32_t* data);\nextern uint32_t* eclair_get_facts(struct program*, uint32_t fact_type);\nextern void eclair_free_buffer(uint32_t* data);\n\nint main(int argc, char** argv)\n{\n    struct program* prog = eclair_program_init();\n\n    // edge(1,2), edge(2,3)\n    uint32_t data[] = {\n        1, 2,\n        2, 3\n    };\n    eclair_add_facts(prog, 0, data, 2);\n\n    eclair_program_run(prog);\n\n    // NOTE: normally you call btree_size here to figure out the size\n    uint32_t* data_out = eclair_get_facts(prog, 1);\n    printf(\"REACHABLE: (%d, %d)\\n\", data_out[0], data_out[1]);  // (1,2)\n    printf(\"REACHABLE: (%d, %d)\\n\", data_out[2], data_out[3]);  // (2,3)\n    printf(\"REACHABLE: (%d, %d)\\n\", data_out[4], data_out[5]);  // (1,3)\n\n    eclair_free_buffer(data_out);\n\n    eclair_program_destroy(prog);\n    return 0;\n}\n"
  },
  {
    "path": "tests/end_to_end/compile_and_run_wasm.eclair",
    "content": "// This checks if eclair can be correctly compiled to WASM and used from JS.\n\n// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// TODO: we really need to have our own WASM allocator..\n// RUN: test -e %t/walloc.c || wget https://raw.githubusercontent.com/wingo/walloc/master/walloc.c -O %t/walloc.c\n// RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/walloc.o %t/walloc.c\n\n// RUN: %eclair compile --target wasm32 %t/program.eclair > %t/program.ll\n// RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/program.o %t/program.ll\n// RUN: %wasm-ld --no-entry --import-memory -o %t/program.wasm %t/program.o %t/walloc.o\n\n// RUN: node %t/program.js | FileCheck %s\n\n// CHECK: [ 1, 2 ]\n// CHECK-NEXT: [ 1, 3 ]\n// CHECK-NEXT: [ 1, 4 ]\n// CHECK-NEXT: [ 2, 3 ]\n// CHECK-NEXT: [ 2, 4 ]\n// CHECK-NEXT: [ 3, 4 ]\n\n//--- program.eclair\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n\nreachable(x, y) :-\n  edge(x, y).\n\nreachable(x, y) :-\n  edge(x, z),\n  reachable(z, y).\n\n//--- program.js\n\nconst fs = require(\"fs\");\n\nconst addFact = (\n  instance,\n  memory,\n  program,\n  factType,\n  factArray,\n  columnCount\n) => {\n  const byteCount = columnCount * Uint32Array.BYTES_PER_ELEMENT;\n  const address = instance.exports.eclair_malloc(byteCount);\n  const array = new Uint32Array(memory.buffer, address, byteCount);\n  array.set(factArray);\n  instance.exports.eclair_add_fact(program, factType, address);\n  instance.exports.eclair_free(address);\n};\n\nconst addFacts = (\n  instance,\n  memory,\n  program,\n  factType,\n  factsArray,\n  columnCount\n) => {\n  const byteCount =\n    factsArray.length * columnCount * Uint32Array.BYTES_PER_ELEMENT;\n  const address = instance.exports.eclair_malloc(byteCount);\n  const array = new Uint32Array(memory.buffer, address, byteCount);\n  array.set(factsArray.flat());\n  instance.exports.eclair_add_facts(\n    program,\n    factType,\n    address,\n    factsArray.length\n  );\n  instance.exports.eclair_free(address);\n};\n\nconst EDGE = 0;\nconst REACHABLE = 1;\nconst NUM_COLUMNS = 2;\n\nconst main = () => {\n  const bytes = fs.readFileSync(\"TEST_DIR/program.wasm\");\n\n  const mod = new WebAssembly.Module(bytes);\n  const memory = new WebAssembly.Memory({ initial: 3, maximum: 3 });\n  const imports = { env: { memory } };\n  const instance = new WebAssembly.Instance(mod, imports);\n\n  const program = instance.exports.eclair_program_init();\n\n  addFact(instance, memory, program, EDGE, [1, 2], NUM_COLUMNS);\n  addFacts(instance, memory, program, EDGE, [[2, 3], [3, 4]], NUM_COLUMNS);\n\n  instance.exports.eclair_program_run(program);\n\n  const resultAddress = instance.exports.eclair_get_facts(program, REACHABLE);\n  const factCount = instance.exports.eclair_fact_count(program, REACHABLE);\n  const resultArray = new Uint32Array(\n    memory.buffer,\n    resultAddress,\n    factCount * NUM_COLUMNS\n  );\n  const results = Array.from(resultArray);\n  for (let i = 0; i < results.length; i += NUM_COLUMNS) {\n    const fact = results.slice(i, i + NUM_COLUMNS);\n    console.log(fact);\n  }\n  instance.exports.eclair_free_buffer(resultAddress);\n\n  instance.exports.eclair_program_destroy(program);\n};\n\nmain();\n"
  },
  {
    "path": "tests/end_to_end/compile_and_run_with_extern.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile %t/program.eclair > %t/program.ll\n// RUN: %clang -o %t/program -O0 %t/main.c %t/program.ll\n// RUN: %t/program | FileCheck %s\n\n// No output, since it never matches\n// CHECK:      func triggered with: 456\n// CHECK:      func triggered with: 123\n// CHECK-NEXT: COUNT: 1\n// CHECK-NEXT: RESULT: 123\n\n//--- program.eclair\n@def edge(u32, u32) input.\n@def test_externs(u32) output.\n\n@extern func(u32) u32.\n\ntest_externs(func(123)).\n\ntest_externs(x) :-\n  edge(x, _),\n  x = func(456).\n\n//--- main.c\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdint.h>\n\nstruct program;\nstruct symbol_table;\n\nextern struct program* eclair_program_init();\nextern void eclair_program_destroy(struct program*);\nextern void eclair_program_run(struct program*);\nextern void eclair_add_facts(struct program*, uint32_t fact_type, uint32_t* data, size_t fact_count);\nextern void eclair_add_fact(struct program*, uint32_t fact_type, uint32_t* data);\nextern uint32_t* eclair_get_facts(struct program*, uint32_t fact_type);\nextern uint32_t eclair_fact_count(struct program*, uint32_t fact_type);\nextern void eclair_free_buffer(uint32_t* data);\n\nuint32_t func(struct symbol_table*, uint32_t value) {\n    printf(\"func triggered with: %d\\n\", value);\n    return value;\n}\n\nint main(int argc, char** argv)\n{\n    struct program* prog = eclair_program_init();\n\n    // edge(1,2), edge(2,3)\n    uint32_t data[] = {\n        1, 2,\n        2, 3\n    };\n    eclair_add_facts(prog, 0, data, 2);\n\n    eclair_program_run(prog);\n\n    uint32_t fact_count = eclair_fact_count(prog, 1);\n    uint32_t* data_out = eclair_get_facts(prog, 1);\n    printf(\"COUNT: %d\\n\", fact_count);\n    printf(\"RESULT: %d\\n\", data_out[0]);\n\n    eclair_free_buffer(data_out);\n\n    eclair_program_destroy(prog);\n    return 0;\n}\n"
  },
  {
    "path": "tests/hello.eclair",
    "content": "// This is mostly a sanity check that the test-suite works\n// (and that LLVM can compile to LLVM correctly).\n\n// RUN: %eclair compile %s | FileCheck %s\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n\nreachable(x, y) :-\n  edge(x, y).\n\nreachable(x, y) :-\n  edge(x, z),\n  reachable(z, y).\n\n// CHECK: eclair_program_run\n"
  },
  {
    "path": "tests/lit.cfg",
    "content": "import subprocess\nimport lit.formats\nfrom lit.llvm.subst import ToolSubst\n\nconfig.name = \"Eclair integration tests\"\nconfig.test_format = lit.formats.ShTest(False)  # 1 test per file\nconfig.suffixes = ['.eclair']\nconfig.excludes = ['Test', 'lsp']  # Don't look in the directory for Haskell tests\n\ntest_dir = os.path.dirname(__file__)\nconfig.test_source_root = test_dir\nconfig.test_exec_root = test_dir\n\nanalysis_dir = test_dir + '/../cbits/'\nconfig.environment['DATALOG_DIR'] = analysis_dir\nconfig.environment['ECLAIR_USE_COLOR'] = '0'\n\neclair = subprocess.check_output('which eclair 2> /dev/null || cabal list-bin eclair', shell=True).decode().rstrip('\\n')\nconfig.substitutions.append(('%eclair', eclair))\n\nutil_dir = test_dir + '/utils/'\nconfig.substitutions.append(('%extract_snippet', util_dir + 'extract_snippet'))\n\nclang = subprocess.check_output('which clang-17 2> /dev/null || which clang', shell=True).decode().rstrip('\\n')\nwasm_ld = subprocess.check_output('which wasm-ld-17 2> /dev/null || which wasm-ld', shell=True).decode().rstrip('\\n')\nconfig.substitutions.append(('%clang', clang))\nconfig.substitutions.append(('%wasm-ld', wasm_ld))\n"
  },
  {
    "path": "tests/lowering/arithmetic.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: %extract_snippet %t/actual_eir.out \"fn.*eclair_program_run\" > %t/actual_eir_snippet.out\n// RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_run\" > %t/actual_llvm_snippet.out\n// RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out\n\n//--- program.eclair\n@def fact1(u32) input.\n@def fact2(u32) output.\n\nfact2(y) :-\n  fact1(x),\n  y = x + 3.\n\nfact2(x + 3) :-\n  fact1(x).\n\nfact2((x + 1) + 2 * x) :-\n  fact1(x).\n\nfact2(x) :-\n  fact1(x),\n  fact1(x + 4),\n  fact1(x + 4).\n\nfact2((8 - x) / x) :-\n  fact1(x).\n\n//--- expected_ra.out\nsearch fact1 as fact10 do\n  project (((8 - fact10[0]) / fact10[0])) into fact2\nsearch fact1 as fact10 do\n  search fact1 as fact11 where (fact11[0] = (fact10[0] + 4)) do\n    search fact1 as fact12 where (fact12[0] = (fact10[0] + 4)) do\n      project (fact10[0]) into fact2\nsearch fact1 as fact10 do\n  project (((fact10[0] + 1) + (2 * fact10[0]))) into fact2\nsearch fact1 as fact10 do\n  project ((fact10[0] + 3)) into fact2\nsearch fact1 as fact10 do\n  project ((fact10[0] + 3)) into fact2\n//--- expected_eir.out\nexport fn eclair_program_run(*Program) -> Void\n{\n  lower_bound_value = fact1.stack_allocate Value\n  upper_bound_value = fact1.stack_allocate Value\n  lower_bound_value.0 = 0\n  upper_bound_value.0 = 4294967295\n  begin_iter = fact1.stack_allocate Iter\n  end_iter = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter)\n  loop\n  {\n    condition = fact1.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = fact1.iter_current(begin_iter)\n    value = fact2.stack_allocate Value\n    value.0 = ((8 - current.0) / current.0)\n    fact2.insert(FN_ARG[0].2, value)\n    fact1.iter_next(begin_iter)\n  }\n  range_query.end:\n  lower_bound_value_1 = fact1.stack_allocate Value\n  upper_bound_value_1 = fact1.stack_allocate Value\n  lower_bound_value_1.0 = 0\n  upper_bound_value_1.0 = 4294967295\n  begin_iter_1 = fact1.stack_allocate Iter\n  end_iter_1 = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_1)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_1)\n  loop\n  {\n    condition_1 = fact1.iter_is_equal(begin_iter_1, end_iter_1)\n    if (condition_1)\n    {\n      goto range_query.end_1\n    }\n    current_1 = fact1.iter_current(begin_iter_1)\n    lower_bound_value_2 = fact1.stack_allocate Value\n    upper_bound_value_2 = fact1.stack_allocate Value\n    lower_bound_value_2.0 = (current_1.0 + 4)\n    upper_bound_value_2.0 = (current_1.0 + 4)\n    begin_iter_2 = fact1.stack_allocate Iter\n    end_iter_2 = fact1.stack_allocate Iter\n    fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_2)\n    fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_2)\n    loop\n    {\n      condition_2 = fact1.iter_is_equal(begin_iter_2, end_iter_2)\n      if (condition_2)\n      {\n        goto range_query.end_2\n      }\n      current_2 = fact1.iter_current(begin_iter_2)\n      lower_bound_value_3 = fact1.stack_allocate Value\n      upper_bound_value_3 = fact1.stack_allocate Value\n      lower_bound_value_3.0 = (current_1.0 + 4)\n      upper_bound_value_3.0 = (current_1.0 + 4)\n      begin_iter_3 = fact1.stack_allocate Iter\n      end_iter_3 = fact1.stack_allocate Iter\n      fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_3, begin_iter_3)\n      fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_3, end_iter_3)\n      loop\n      {\n        condition_3 = fact1.iter_is_equal(begin_iter_3, end_iter_3)\n        if (condition_3)\n        {\n          goto range_query.end_3\n        }\n        current_3 = fact1.iter_current(begin_iter_3)\n        value_1 = fact2.stack_allocate Value\n        value_1.0 = current_1.0\n        fact2.insert(FN_ARG[0].2, value_1)\n        fact1.iter_next(begin_iter_3)\n      }\n      range_query.end_3:\n      fact1.iter_next(begin_iter_2)\n    }\n    range_query.end_2:\n    fact1.iter_next(begin_iter_1)\n  }\n  range_query.end_1:\n  lower_bound_value_4 = fact1.stack_allocate Value\n  upper_bound_value_4 = fact1.stack_allocate Value\n  lower_bound_value_4.0 = 0\n  upper_bound_value_4.0 = 4294967295\n  begin_iter_4 = fact1.stack_allocate Iter\n  end_iter_4 = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_4, begin_iter_4)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_4, end_iter_4)\n  loop\n  {\n    condition_4 = fact1.iter_is_equal(begin_iter_4, end_iter_4)\n    if (condition_4)\n    {\n      goto range_query.end_4\n    }\n    current_4 = fact1.iter_current(begin_iter_4)\n    value_2 = fact2.stack_allocate Value\n    value_2.0 = ((current_4.0 + 1) + (2 * current_4.0))\n    fact2.insert(FN_ARG[0].2, value_2)\n    fact1.iter_next(begin_iter_4)\n  }\n  range_query.end_4:\n  lower_bound_value_5 = fact1.stack_allocate Value\n  upper_bound_value_5 = fact1.stack_allocate Value\n  lower_bound_value_5.0 = 0\n  upper_bound_value_5.0 = 4294967295\n  begin_iter_5 = fact1.stack_allocate Iter\n  end_iter_5 = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_5, begin_iter_5)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_5, end_iter_5)\n  loop\n  {\n    condition_5 = fact1.iter_is_equal(begin_iter_5, end_iter_5)\n    if (condition_5)\n    {\n      goto range_query.end_5\n    }\n    current_5 = fact1.iter_current(begin_iter_5)\n    value_3 = fact2.stack_allocate Value\n    value_3.0 = (current_5.0 + 3)\n    fact2.insert(FN_ARG[0].2, value_3)\n    fact1.iter_next(begin_iter_5)\n  }\n  range_query.end_5:\n  lower_bound_value_6 = fact1.stack_allocate Value\n  upper_bound_value_6 = fact1.stack_allocate Value\n  lower_bound_value_6.0 = 0\n  upper_bound_value_6.0 = 4294967295\n  begin_iter_6 = fact1.stack_allocate Iter\n  end_iter_6 = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_6, begin_iter_6)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_6, end_iter_6)\n  loop\n  {\n    condition_6 = fact1.iter_is_equal(begin_iter_6, end_iter_6)\n    if (condition_6)\n    {\n      goto range_query.end_6\n    }\n    current_6 = fact1.iter_current(begin_iter_6)\n    value_4 = fact2.stack_allocate Value\n    value_4.0 = (current_6.0 + 3)\n    fact2.insert(FN_ARG[0].2, value_4)\n    fact1.iter_next(begin_iter_6)\n  }\n  range_query.end_6:\n}\n//--- expected_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [1 x i32], i32 1\n  %stack.ptr_1 = alloca [1 x i32], i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_4 = alloca [1 x i32], i32 1\n  %stack.ptr_5 = alloca [1 x i32], i32 1\n  %stack.ptr_6 = alloca [1 x i32], i32 1\n  %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_8 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_9 = alloca [1 x i32], i32 1\n  %stack.ptr_10 = alloca [1 x i32], i32 1\n  %stack.ptr_11 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_12 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_13 = alloca [1 x i32], i32 1\n  %stack.ptr_14 = alloca [1 x i32], i32 1\n  %stack.ptr_15 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_16 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_17 = alloca [1 x i32], i32 1\n  %stack.ptr_18 = alloca [1 x i32], i32 1\n  %stack.ptr_19 = alloca [1 x i32], i32 1\n  %stack.ptr_20 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_21 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_22 = alloca [1 x i32], i32 1\n  %stack.ptr_23 = alloca [1 x i32], i32 1\n  %stack.ptr_24 = alloca [1 x i32], i32 1\n  %stack.ptr_25 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_26 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_27 = alloca [1 x i32], i32 1\n  %stack.ptr_28 = alloca [1 x i32], i32 1\n  %stack.ptr_29 = alloca [1 x i32], i32 1\n  %stack.ptr_30 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_31 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_32 = alloca [1 x i32], i32 1\n  %0 = getelementptr [1 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 0, ptr %0\n  %1 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 4294967295, ptr %1\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %2, ptr %stack.ptr_0, ptr %stack.ptr_2)\n  %3 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %3, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  br label %loop_0\nloop_0:\n  %4 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3)\n  br i1 %4, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %5 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2)\n  %6 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0\n  %7 = getelementptr [1 x i32], ptr %5, i32 0, i32 0\n  %8 = load i32, ptr %7\n  %9 = sub i32 8, %8\n  %10 = getelementptr [1 x i32], ptr %5, i32 0, i32 0\n  %11 = load i32, ptr %10\n  %12 = udiv i32 %9, %11\n  store i32 %12, ptr %6\n  %13 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %14 = call ccc i1 @eclair_btree_insert_value_0(ptr %13, ptr %stack.ptr_4)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2)\n  br label %loop_0\nrange_query.end:\n  %15 = getelementptr [1 x i32], ptr %stack.ptr_5, i32 0, i32 0\n  store i32 0, ptr %15\n  %16 = getelementptr [1 x i32], ptr %stack.ptr_6, i32 0, i32 0\n  store i32 4294967295, ptr %16\n  %17 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %17, ptr %stack.ptr_5, ptr %stack.ptr_7)\n  %18 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %18, ptr %stack.ptr_6, ptr %stack.ptr_8)\n  br label %loop_1\nloop_1:\n  %19 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_7, ptr %stack.ptr_8)\n  br i1 %19, label %if_1, label %end_if_1\nif_1:\n  br label %range_query.end_1\nend_if_1:\n  %20 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_7)\n  %21 = getelementptr [1 x i32], ptr %stack.ptr_9, i32 0, i32 0\n  %22 = getelementptr [1 x i32], ptr %20, i32 0, i32 0\n  %23 = load i32, ptr %22\n  %24 = add i32 %23, 4\n  store i32 %24, ptr %21\n  %25 = getelementptr [1 x i32], ptr %stack.ptr_10, i32 0, i32 0\n  %26 = getelementptr [1 x i32], ptr %20, i32 0, i32 0\n  %27 = load i32, ptr %26\n  %28 = add i32 %27, 4\n  store i32 %28, ptr %25\n  %29 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %29, ptr %stack.ptr_9, ptr %stack.ptr_11)\n  %30 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %30, ptr %stack.ptr_10, ptr %stack.ptr_12)\n  br label %loop_2\nloop_2:\n  %31 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_11, ptr %stack.ptr_12)\n  br i1 %31, label %if_2, label %end_if_2\nif_2:\n  br label %range_query.end_2\nend_if_2:\n  %32 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_11)\n  %33 = getelementptr [1 x i32], ptr %stack.ptr_13, i32 0, i32 0\n  %34 = getelementptr [1 x i32], ptr %20, i32 0, i32 0\n  %35 = load i32, ptr %34\n  %36 = add i32 %35, 4\n  store i32 %36, ptr %33\n  %37 = getelementptr [1 x i32], ptr %stack.ptr_14, i32 0, i32 0\n  %38 = getelementptr [1 x i32], ptr %20, i32 0, i32 0\n  %39 = load i32, ptr %38\n  %40 = add i32 %39, 4\n  store i32 %40, ptr %37\n  %41 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %41, ptr %stack.ptr_13, ptr %stack.ptr_15)\n  %42 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %42, ptr %stack.ptr_14, ptr %stack.ptr_16)\n  br label %loop_3\nloop_3:\n  %43 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_15, ptr %stack.ptr_16)\n  br i1 %43, label %if_3, label %end_if_3\nif_3:\n  br label %range_query.end_3\nend_if_3:\n  %44 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_15)\n  %45 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0\n  %46 = getelementptr [1 x i32], ptr %20, i32 0, i32 0\n  %47 = load i32, ptr %46\n  store i32 %47, ptr %45\n  %48 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %49 = call ccc i1 @eclair_btree_insert_value_0(ptr %48, ptr %stack.ptr_17)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_15)\n  br label %loop_3\nrange_query.end_3:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_11)\n  br label %loop_2\nrange_query.end_2:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_7)\n  br label %loop_1\nrange_query.end_1:\n  %50 = getelementptr [1 x i32], ptr %stack.ptr_18, i32 0, i32 0\n  store i32 0, ptr %50\n  %51 = getelementptr [1 x i32], ptr %stack.ptr_19, i32 0, i32 0\n  store i32 4294967295, ptr %51\n  %52 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %52, ptr %stack.ptr_18, ptr %stack.ptr_20)\n  %53 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %53, ptr %stack.ptr_19, ptr %stack.ptr_21)\n  br label %loop_4\nloop_4:\n  %54 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_20, ptr %stack.ptr_21)\n  br i1 %54, label %if_4, label %end_if_4\nif_4:\n  br label %range_query.end_4\nend_if_4:\n  %55 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_20)\n  %56 = getelementptr [1 x i32], ptr %stack.ptr_22, i32 0, i32 0\n  %57 = getelementptr [1 x i32], ptr %55, i32 0, i32 0\n  %58 = load i32, ptr %57\n  %59 = add i32 %58, 1\n  %60 = getelementptr [1 x i32], ptr %55, i32 0, i32 0\n  %61 = load i32, ptr %60\n  %62 = mul i32 2, %61\n  %63 = add i32 %59, %62\n  store i32 %63, ptr %56\n  %64 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %65 = call ccc i1 @eclair_btree_insert_value_0(ptr %64, ptr %stack.ptr_22)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_20)\n  br label %loop_4\nrange_query.end_4:\n  %66 = getelementptr [1 x i32], ptr %stack.ptr_23, i32 0, i32 0\n  store i32 0, ptr %66\n  %67 = getelementptr [1 x i32], ptr %stack.ptr_24, i32 0, i32 0\n  store i32 4294967295, ptr %67\n  %68 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %68, ptr %stack.ptr_23, ptr %stack.ptr_25)\n  %69 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %69, ptr %stack.ptr_24, ptr %stack.ptr_26)\n  br label %loop_5\nloop_5:\n  %70 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_25, ptr %stack.ptr_26)\n  br i1 %70, label %if_5, label %end_if_5\nif_5:\n  br label %range_query.end_5\nend_if_5:\n  %71 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_25)\n  %72 = getelementptr [1 x i32], ptr %stack.ptr_27, i32 0, i32 0\n  %73 = getelementptr [1 x i32], ptr %71, i32 0, i32 0\n  %74 = load i32, ptr %73\n  %75 = add i32 %74, 3\n  store i32 %75, ptr %72\n  %76 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %77 = call ccc i1 @eclair_btree_insert_value_0(ptr %76, ptr %stack.ptr_27)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_25)\n  br label %loop_5\nrange_query.end_5:\n  %78 = getelementptr [1 x i32], ptr %stack.ptr_28, i32 0, i32 0\n  store i32 0, ptr %78\n  %79 = getelementptr [1 x i32], ptr %stack.ptr_29, i32 0, i32 0\n  store i32 4294967295, ptr %79\n  %80 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %80, ptr %stack.ptr_28, ptr %stack.ptr_30)\n  %81 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %81, ptr %stack.ptr_29, ptr %stack.ptr_31)\n  br label %loop_6\nloop_6:\n  %82 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_30, ptr %stack.ptr_31)\n  br i1 %82, label %if_6, label %end_if_6\nif_6:\n  br label %range_query.end_6\nend_if_6:\n  %83 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_30)\n  %84 = getelementptr [1 x i32], ptr %stack.ptr_32, i32 0, i32 0\n  %85 = getelementptr [1 x i32], ptr %83, i32 0, i32 0\n  %86 = load i32, ptr %85\n  %87 = add i32 %86, 3\n  store i32 %87, ptr %84\n  %88 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %89 = call ccc i1 @eclair_btree_insert_value_0(ptr %88, ptr %stack.ptr_32)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_30)\n  br label %loop_6\nrange_query.end_6:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/clause_with_same_vars.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: %extract_snippet %t/actual_eir.out \"fn eclair_program_run\" > %t/actual_eclair_program_run.out\n// RUN: diff %t/expected_eclair_program_run.out %t/actual_eclair_program_run.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n//--- program.eclair\n@def a(u32) output.\n@def b(u32, u32) input.\n@def c(u32, u32, u32, u32, u32) input.\n@def other(u32) input.\n\na(x) :-\n  b(x, x),\n  other(x).\n\na(y) :-\n  c(y, y, 42, _, y),\n  other(y).\n\n//--- expected_ra.out\nsearch c as c0 where (c0[2] = 42) do\n  if c0[0] = c0[1] do\n    if c0[0] = c0[4] do\n      search other as other1 where (c0[0] = other1[0]) do\n        project (c0[0]) into a\nsearch b as b0 do\n  if b0[0] = b0[1] do\n    search other as other1 where (b0[0] = other1[0]) do\n      project (b0[0]) into a\n//--- expected_eclair_program_run.out\nexport fn eclair_program_run(*Program) -> Void\n{\n  lower_bound_value = c.stack_allocate Value\n  upper_bound_value = c.stack_allocate Value\n  lower_bound_value.0 = 0\n  lower_bound_value.1 = 0\n  lower_bound_value.2 = 42\n  lower_bound_value.3 = 0\n  lower_bound_value.4 = 0\n  upper_bound_value.0 = 4294967295\n  upper_bound_value.1 = 4294967295\n  upper_bound_value.2 = 42\n  upper_bound_value.3 = 4294967295\n  upper_bound_value.4 = 4294967295\n  begin_iter = c.stack_allocate Iter\n  end_iter = c.stack_allocate Iter\n  c.iter_lower_bound(FN_ARG[0].3, lower_bound_value, begin_iter)\n  c.iter_upper_bound(FN_ARG[0].3, upper_bound_value, end_iter)\n  loop\n  {\n    condition = c.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = c.iter_current(begin_iter)\n    condition_1 = (current.0 == current.1)\n    if (condition_1)\n    {\n      condition_2 = (current.0 == current.4)\n      if (condition_2)\n      {\n        lower_bound_value_1 = other.stack_allocate Value\n        upper_bound_value_1 = other.stack_allocate Value\n        lower_bound_value_1.0 = current.0\n        upper_bound_value_1.0 = current.0\n        begin_iter_1 = other.stack_allocate Iter\n        end_iter_1 = other.stack_allocate Iter\n        other.iter_lower_bound(FN_ARG[0].4, lower_bound_value_1, begin_iter_1)\n        other.iter_upper_bound(FN_ARG[0].4, upper_bound_value_1, end_iter_1)\n        loop\n        {\n          condition_3 = other.iter_is_equal(begin_iter_1, end_iter_1)\n          if (condition_3)\n          {\n            goto range_query.end_1\n          }\n          current_1 = other.iter_current(begin_iter_1)\n          value = a.stack_allocate Value\n          value.0 = current.0\n          a.insert(FN_ARG[0].1, value)\n          other.iter_next(begin_iter_1)\n        }\n        range_query.end_1:\n      }\n    }\n    c.iter_next(begin_iter)\n  }\n  range_query.end:\n  lower_bound_value_2 = b.stack_allocate Value\n  upper_bound_value_2 = b.stack_allocate Value\n  lower_bound_value_2.0 = 0\n  lower_bound_value_2.1 = 0\n  upper_bound_value_2.0 = 4294967295\n  upper_bound_value_2.1 = 4294967295\n  begin_iter_2 = b.stack_allocate Iter\n  end_iter_2 = b.stack_allocate Iter\n  b.iter_lower_bound(FN_ARG[0].2, lower_bound_value_2, begin_iter_2)\n  b.iter_upper_bound(FN_ARG[0].2, upper_bound_value_2, end_iter_2)\n  loop\n  {\n    condition_4 = b.iter_is_equal(begin_iter_2, end_iter_2)\n    if (condition_4)\n    {\n      goto range_query.end_2\n    }\n    current_2 = b.iter_current(begin_iter_2)\n    condition_5 = (current_2.0 == current_2.1)\n    if (condition_5)\n    {\n      lower_bound_value_3 = other.stack_allocate Value\n      upper_bound_value_3 = other.stack_allocate Value\n      lower_bound_value_3.0 = current_2.0\n      upper_bound_value_3.0 = current_2.0\n      begin_iter_3 = other.stack_allocate Iter\n      end_iter_3 = other.stack_allocate Iter\n      other.iter_lower_bound(FN_ARG[0].4, lower_bound_value_3, begin_iter_3)\n      other.iter_upper_bound(FN_ARG[0].4, upper_bound_value_3, end_iter_3)\n      loop\n      {\n        condition_6 = other.iter_is_equal(begin_iter_3, end_iter_3)\n        if (condition_6)\n        {\n          goto range_query.end_3\n        }\n        current_3 = other.iter_current(begin_iter_3)\n        value_1 = a.stack_allocate Value\n        value_1.0 = current_2.0\n        a.insert(FN_ARG[0].1, value_1)\n        other.iter_next(begin_iter_3)\n      }\n      range_query.end_3:\n    }\n    b.iter_next(begin_iter_2)\n  }\n  range_query.end_2:\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [5 x i32], i32 1\n  %stack.ptr_1 = alloca [5 x i32], i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_2, i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_2, i32 1\n  %stack.ptr_4 = alloca [1 x i32], i32 1\n  %stack.ptr_5 = alloca [1 x i32], i32 1\n  %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_8 = alloca [1 x i32], i32 1\n  %stack.ptr_9 = alloca [2 x i32], i32 1\n  %stack.ptr_10 = alloca [2 x i32], i32 1\n  %stack.ptr_11 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_12 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_13 = alloca [1 x i32], i32 1\n  %stack.ptr_14 = alloca [1 x i32], i32 1\n  %stack.ptr_15 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_16 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_17 = alloca [1 x i32], i32 1\n  %0 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 0, ptr %0\n  %1 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 0, ptr %1\n  %2 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 2\n  store i32 42, ptr %2\n  %3 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 3\n  store i32 0, ptr %3\n  %4 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 4\n  store i32 0, ptr %4\n  %5 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 4294967295, ptr %5\n  %6 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 1\n  store i32 4294967295, ptr %6\n  %7 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 2\n  store i32 42, ptr %7\n  %8 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 3\n  store i32 4294967295, ptr %8\n  %9 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 4\n  store i32 4294967295, ptr %9\n  %10 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_lower_bound_2(ptr %10, ptr %stack.ptr_0, ptr %stack.ptr_2)\n  %11 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_upper_bound_2(ptr %11, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  br label %loop_0\nloop_0:\n  %12 = call ccc i1 @eclair_btree_iterator_is_equal_2(ptr %stack.ptr_2, ptr %stack.ptr_3)\n  br i1 %12, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %13 = call ccc ptr @eclair_btree_iterator_current_2(ptr %stack.ptr_2)\n  %14 = getelementptr [5 x i32], ptr %13, i32 0, i32 0\n  %15 = load i32, ptr %14\n  %16 = getelementptr [5 x i32], ptr %13, i32 0, i32 1\n  %17 = load i32, ptr %16\n  %18 = icmp eq i32 %15, %17\n  br i1 %18, label %if_1, label %end_if_3\nif_1:\n  %19 = getelementptr [5 x i32], ptr %13, i32 0, i32 0\n  %20 = load i32, ptr %19\n  %21 = getelementptr [5 x i32], ptr %13, i32 0, i32 4\n  %22 = load i32, ptr %21\n  %23 = icmp eq i32 %20, %22\n  br i1 %23, label %if_2, label %end_if_2\nif_2:\n  %24 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0\n  %25 = getelementptr [5 x i32], ptr %13, i32 0, i32 0\n  %26 = load i32, ptr %25\n  store i32 %26, ptr %24\n  %27 = getelementptr [1 x i32], ptr %stack.ptr_5, i32 0, i32 0\n  %28 = getelementptr [5 x i32], ptr %13, i32 0, i32 0\n  %29 = load i32, ptr %28\n  store i32 %29, ptr %27\n  %30 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_lower_bound_0(ptr %30, ptr %stack.ptr_4, ptr %stack.ptr_6)\n  %31 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_upper_bound_0(ptr %31, ptr %stack.ptr_5, ptr %stack.ptr_7)\n  br label %loop_1\nloop_1:\n  %32 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_6, ptr %stack.ptr_7)\n  br i1 %32, label %if_3, label %end_if_1\nif_3:\n  br label %range_query.end_1\nend_if_1:\n  %33 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_6)\n  %34 = getelementptr [1 x i32], ptr %stack.ptr_8, i32 0, i32 0\n  %35 = getelementptr [5 x i32], ptr %13, i32 0, i32 0\n  %36 = load i32, ptr %35\n  store i32 %36, ptr %34\n  %37 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %38 = call ccc i1 @eclair_btree_insert_value_0(ptr %37, ptr %stack.ptr_8)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_6)\n  br label %loop_1\nrange_query.end_1:\n  br label %end_if_2\nend_if_2:\n  br label %end_if_3\nend_if_3:\n  call ccc void @eclair_btree_iterator_next_2(ptr %stack.ptr_2)\n  br label %loop_0\nrange_query.end:\n  %39 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 0\n  store i32 0, ptr %39\n  %40 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 1\n  store i32 0, ptr %40\n  %41 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0\n  store i32 4294967295, ptr %41\n  %42 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1\n  store i32 4294967295, ptr %42\n  %43 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_1(ptr %43, ptr %stack.ptr_9, ptr %stack.ptr_11)\n  %44 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_1(ptr %44, ptr %stack.ptr_10, ptr %stack.ptr_12)\n  br label %loop_2\nloop_2:\n  %45 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_11, ptr %stack.ptr_12)\n  br i1 %45, label %if_4, label %end_if_4\nif_4:\n  br label %range_query.end_2\nend_if_4:\n  %46 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_11)\n  %47 = getelementptr [2 x i32], ptr %46, i32 0, i32 0\n  %48 = load i32, ptr %47\n  %49 = getelementptr [2 x i32], ptr %46, i32 0, i32 1\n  %50 = load i32, ptr %49\n  %51 = icmp eq i32 %48, %50\n  br i1 %51, label %if_5, label %end_if_6\nif_5:\n  %52 = getelementptr [1 x i32], ptr %stack.ptr_13, i32 0, i32 0\n  %53 = getelementptr [2 x i32], ptr %46, i32 0, i32 0\n  %54 = load i32, ptr %53\n  store i32 %54, ptr %52\n  %55 = getelementptr [1 x i32], ptr %stack.ptr_14, i32 0, i32 0\n  %56 = getelementptr [2 x i32], ptr %46, i32 0, i32 0\n  %57 = load i32, ptr %56\n  store i32 %57, ptr %55\n  %58 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_lower_bound_0(ptr %58, ptr %stack.ptr_13, ptr %stack.ptr_15)\n  %59 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_upper_bound_0(ptr %59, ptr %stack.ptr_14, ptr %stack.ptr_16)\n  br label %loop_3\nloop_3:\n  %60 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_15, ptr %stack.ptr_16)\n  br i1 %60, label %if_6, label %end_if_5\nif_6:\n  br label %range_query.end_3\nend_if_5:\n  %61 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_15)\n  %62 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0\n  %63 = getelementptr [2 x i32], ptr %46, i32 0, i32 0\n  %64 = load i32, ptr %63\n  store i32 %64, ptr %62\n  %65 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %66 = call ccc i1 @eclair_btree_insert_value_0(ptr %65, ptr %stack.ptr_17)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_15)\n  br label %loop_3\nrange_query.end_3:\n  br label %end_if_6\nend_if_6:\n  call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_11)\n  br label %loop_2\nrange_query.end_2:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/comparisons.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_run\" > %t/actual_llvm_snippet.out\n// RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out\n\n//--- program.eclair\n@def fact1(u32, u32) input.\n@def fact2(u32, u32) output.\n\nfact2(x, 1) :-\n  z = x,\n  fact1(x, z),\n  y = 123,\n  fact1(y, x).\n\nfact2(x, y) :-\n  123 = x,\n  fact1(y, x).\n\nfact2(x, y) :-\n  123 < x,\n  123 <= x,\n  123 > x,\n  123 >= x,\n  123 != x,\n  fact1(y, x).\n\n//--- expected_ra.out\nsearch fact1 as fact10 do\n  if 123 < fact10[1] do\n    if 123 <= fact10[1] do\n      if 123 > fact10[1] do\n        if 123 >= fact10[1] do\n          if 123 != fact10[1] do\n            project (fact10[1], fact10[0]) into fact2\nsearch fact1 as fact10 where (123 = fact10[1]) do\n  project (fact10[1], fact10[0]) into fact2\nsearch fact1 as fact10 do\n  if fact10[1] = fact10[0] do\n    search fact1 as fact11 where (fact11[0] = 123 and fact10[0] = fact11[1]) do\n      project (fact10[0], 1) into fact2\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  fact1 btree(num_columns=2, index=[1,0], block_size=256, search_type=linear)\n  fact2 btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  fact1.init_empty(program.1)\n  fact2.init_empty(program.2)\n  symbol_table.insert(program.0, \"fact1\")\n  symbol_table.insert(program.0, \"fact2\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  fact1.destroy(FN_ARG[0].1)\n  fact2.destroy(FN_ARG[0].2)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  lower_bound_value = fact1.stack_allocate Value\n  upper_bound_value = fact1.stack_allocate Value\n  lower_bound_value.0 = 0\n  lower_bound_value.1 = 0\n  upper_bound_value.0 = 4294967295\n  upper_bound_value.1 = 4294967295\n  begin_iter = fact1.stack_allocate Iter\n  end_iter = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter)\n  loop\n  {\n    condition = fact1.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = fact1.iter_current(begin_iter)\n    condition_1 = (123 < current.1)\n    if (condition_1)\n    {\n      condition_2 = (123 <= current.1)\n      if (condition_2)\n      {\n        condition_3 = (123 > current.1)\n        if (condition_3)\n        {\n          condition_4 = (123 >= current.1)\n          if (condition_4)\n          {\n            condition_5 = (123 != current.1)\n            if (condition_5)\n            {\n              value = fact2.stack_allocate Value\n              value.0 = current.1\n              value.1 = current.0\n              fact2.insert(FN_ARG[0].2, value)\n            }\n          }\n        }\n      }\n    }\n    fact1.iter_next(begin_iter)\n  }\n  range_query.end:\n  lower_bound_value_1 = fact1.stack_allocate Value\n  upper_bound_value_1 = fact1.stack_allocate Value\n  lower_bound_value_1.0 = 0\n  lower_bound_value_1.1 = 123\n  upper_bound_value_1.0 = 4294967295\n  upper_bound_value_1.1 = 123\n  begin_iter_1 = fact1.stack_allocate Iter\n  end_iter_1 = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_1)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_1)\n  loop\n  {\n    condition_6 = fact1.iter_is_equal(begin_iter_1, end_iter_1)\n    if (condition_6)\n    {\n      goto range_query.end_1\n    }\n    current_1 = fact1.iter_current(begin_iter_1)\n    value_1 = fact2.stack_allocate Value\n    value_1.0 = current_1.1\n    value_1.1 = current_1.0\n    fact2.insert(FN_ARG[0].2, value_1)\n    fact1.iter_next(begin_iter_1)\n  }\n  range_query.end_1:\n  lower_bound_value_2 = fact1.stack_allocate Value\n  upper_bound_value_2 = fact1.stack_allocate Value\n  lower_bound_value_2.0 = 0\n  lower_bound_value_2.1 = 0\n  upper_bound_value_2.0 = 4294967295\n  upper_bound_value_2.1 = 4294967295\n  begin_iter_2 = fact1.stack_allocate Iter\n  end_iter_2 = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_2)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_2)\n  loop\n  {\n    condition_7 = fact1.iter_is_equal(begin_iter_2, end_iter_2)\n    if (condition_7)\n    {\n      goto range_query.end_2\n    }\n    current_2 = fact1.iter_current(begin_iter_2)\n    condition_8 = (current_2.1 == current_2.0)\n    if (condition_8)\n    {\n      lower_bound_value_3 = fact1.stack_allocate Value\n      upper_bound_value_3 = fact1.stack_allocate Value\n      lower_bound_value_3.0 = 123\n      lower_bound_value_3.1 = current_2.0\n      upper_bound_value_3.0 = 123\n      upper_bound_value_3.1 = current_2.0\n      begin_iter_3 = fact1.stack_allocate Iter\n      end_iter_3 = fact1.stack_allocate Iter\n      fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_3, begin_iter_3)\n      fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_3, end_iter_3)\n      loop\n      {\n        condition_9 = fact1.iter_is_equal(begin_iter_3, end_iter_3)\n        if (condition_9)\n        {\n          goto range_query.end_3\n        }\n        current_3 = fact1.iter_current(begin_iter_3)\n        value_2 = fact2.stack_allocate Value\n        value_2.0 = current_2.0\n        value_2.1 = 1\n        fact2.insert(FN_ARG[0].2, value_2)\n        fact1.iter_next(begin_iter_3)\n      }\n      range_query.end_3:\n    }\n    fact1.iter_next(begin_iter_2)\n  }\n  range_query.end_2:\n}\n//--- expected_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca [2 x i32], i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_4 = alloca [2 x i32], i32 1\n  %stack.ptr_5 = alloca [2 x i32], i32 1\n  %stack.ptr_6 = alloca [2 x i32], i32 1\n  %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_8 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_9 = alloca [2 x i32], i32 1\n  %stack.ptr_10 = alloca [2 x i32], i32 1\n  %stack.ptr_11 = alloca [2 x i32], i32 1\n  %stack.ptr_12 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_14 = alloca [2 x i32], i32 1\n  %stack.ptr_15 = alloca [2 x i32], i32 1\n  %stack.ptr_16 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_17 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_18 = alloca [2 x i32], i32 1\n  %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 0, ptr %0\n  %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 0, ptr %1\n  %2 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 4294967295, ptr %2\n  %3 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1\n  store i32 4294967295, ptr %3\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %4, ptr %stack.ptr_0, ptr %stack.ptr_2)\n  %5 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %5, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  br label %loop_0\nloop_0:\n  %6 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3)\n  br i1 %6, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %7 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2)\n  %8 = getelementptr [2 x i32], ptr %7, i32 0, i32 1\n  %9 = load i32, ptr %8\n  %10 = icmp ult i32 123, %9\n  br i1 %10, label %if_1, label %end_if_5\nif_1:\n  %11 = getelementptr [2 x i32], ptr %7, i32 0, i32 1\n  %12 = load i32, ptr %11\n  %13 = icmp ule i32 123, %12\n  br i1 %13, label %if_2, label %end_if_4\nif_2:\n  %14 = getelementptr [2 x i32], ptr %7, i32 0, i32 1\n  %15 = load i32, ptr %14\n  %16 = icmp ugt i32 123, %15\n  br i1 %16, label %if_3, label %end_if_3\nif_3:\n  %17 = getelementptr [2 x i32], ptr %7, i32 0, i32 1\n  %18 = load i32, ptr %17\n  %19 = icmp uge i32 123, %18\n  br i1 %19, label %if_4, label %end_if_2\nif_4:\n  %20 = getelementptr [2 x i32], ptr %7, i32 0, i32 1\n  %21 = load i32, ptr %20\n  %22 = icmp ne i32 123, %21\n  br i1 %22, label %if_5, label %end_if_1\nif_5:\n  %23 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 0\n  %24 = getelementptr [2 x i32], ptr %7, i32 0, i32 1\n  %25 = load i32, ptr %24\n  store i32 %25, ptr %23\n  %26 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 1\n  %27 = getelementptr [2 x i32], ptr %7, i32 0, i32 0\n  %28 = load i32, ptr %27\n  store i32 %28, ptr %26\n  %29 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %30 = call ccc i1 @eclair_btree_insert_value_1(ptr %29, ptr %stack.ptr_4)\n  br label %end_if_1\nend_if_1:\n  br label %end_if_2\nend_if_2:\n  br label %end_if_3\nend_if_3:\n  br label %end_if_4\nend_if_4:\n  br label %end_if_5\nend_if_5:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2)\n  br label %loop_0\nrange_query.end:\n  %31 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0\n  store i32 0, ptr %31\n  %32 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1\n  store i32 123, ptr %32\n  %33 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0\n  store i32 4294967295, ptr %33\n  %34 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1\n  store i32 123, ptr %34\n  %35 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %35, ptr %stack.ptr_5, ptr %stack.ptr_7)\n  %36 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %36, ptr %stack.ptr_6, ptr %stack.ptr_8)\n  br label %loop_1\nloop_1:\n  %37 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_7, ptr %stack.ptr_8)\n  br i1 %37, label %if_6, label %end_if_6\nif_6:\n  br label %range_query.end_1\nend_if_6:\n  %38 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_7)\n  %39 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 0\n  %40 = getelementptr [2 x i32], ptr %38, i32 0, i32 1\n  %41 = load i32, ptr %40\n  store i32 %41, ptr %39\n  %42 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 1\n  %43 = getelementptr [2 x i32], ptr %38, i32 0, i32 0\n  %44 = load i32, ptr %43\n  store i32 %44, ptr %42\n  %45 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %46 = call ccc i1 @eclair_btree_insert_value_1(ptr %45, ptr %stack.ptr_9)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_7)\n  br label %loop_1\nrange_query.end_1:\n  %47 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0\n  store i32 0, ptr %47\n  %48 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1\n  store i32 0, ptr %48\n  %49 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 0\n  store i32 4294967295, ptr %49\n  %50 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 1\n  store i32 4294967295, ptr %50\n  %51 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %51, ptr %stack.ptr_10, ptr %stack.ptr_12)\n  %52 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %52, ptr %stack.ptr_11, ptr %stack.ptr_13)\n  br label %loop_2\nloop_2:\n  %53 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_12, ptr %stack.ptr_13)\n  br i1 %53, label %if_7, label %end_if_7\nif_7:\n  br label %range_query.end_2\nend_if_7:\n  %54 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_12)\n  %55 = getelementptr [2 x i32], ptr %54, i32 0, i32 1\n  %56 = load i32, ptr %55\n  %57 = getelementptr [2 x i32], ptr %54, i32 0, i32 0\n  %58 = load i32, ptr %57\n  %59 = icmp eq i32 %56, %58\n  br i1 %59, label %if_8, label %end_if_9\nif_8:\n  %60 = getelementptr [2 x i32], ptr %stack.ptr_14, i32 0, i32 0\n  store i32 123, ptr %60\n  %61 = getelementptr [2 x i32], ptr %stack.ptr_14, i32 0, i32 1\n  %62 = getelementptr [2 x i32], ptr %54, i32 0, i32 0\n  %63 = load i32, ptr %62\n  store i32 %63, ptr %61\n  %64 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 0\n  store i32 123, ptr %64\n  %65 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 1\n  %66 = getelementptr [2 x i32], ptr %54, i32 0, i32 0\n  %67 = load i32, ptr %66\n  store i32 %67, ptr %65\n  %68 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %68, ptr %stack.ptr_14, ptr %stack.ptr_16)\n  %69 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %69, ptr %stack.ptr_15, ptr %stack.ptr_17)\n  br label %loop_3\nloop_3:\n  %70 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_16, ptr %stack.ptr_17)\n  br i1 %70, label %if_9, label %end_if_8\nif_9:\n  br label %range_query.end_3\nend_if_8:\n  %71 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_16)\n  %72 = getelementptr [2 x i32], ptr %stack.ptr_18, i32 0, i32 0\n  %73 = getelementptr [2 x i32], ptr %54, i32 0, i32 0\n  %74 = load i32, ptr %73\n  store i32 %74, ptr %72\n  %75 = getelementptr [2 x i32], ptr %stack.ptr_18, i32 0, i32 1\n  store i32 1, ptr %75\n  %76 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %77 = call ccc i1 @eclair_btree_insert_value_1(ptr %76, ptr %stack.ptr_18)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_16)\n  br label %loop_3\nrange_query.end_3:\n  br label %end_if_9\nend_if_9:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_12)\n  br label %loop_2\nrange_query.end_2:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/different_types.eclair",
    "content": "// TODO add tests for caching mechanism (e.g. single_nonrecursive_rule test)\n\n// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_add_facts\" > %t/actual_eclair_add_facts_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_get_facts\" > %t/actual_eclair_get_facts_llvm.out\n// RUN: diff %t/expected_eclair_add_facts_llvm.out %t/actual_eclair_add_facts_llvm.out\n// RUN: diff %t/expected_eclair_get_facts_llvm.out %t/actual_eclair_get_facts_llvm.out\n\n//--- program.eclair\n@def a(u32) input output.\n@def b(u32, u32, u32) input output.\n\n//--- expected_eclair_add_facts_llvm.out\ndefine external ccc void @eclair_add_facts(ptr %eclair_program_0, i32 %fact_type_0, ptr %memory_0, i32 %fact_count_0) \"wasm-export-name\"=\"eclair_add_facts\" {\nstart:\n  switch i32 %fact_type_0, label %switch.default_0 [i32 0, label %a_0 i32 1, label %b_0]\na_0:\n  %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 1\n  br label %for_begin_0\nfor_begin_0:\n  %1 = phi i32 [0, %a_0], [%5, %for_body_0]\n  %2 = icmp ult i32 %1, %fact_count_0\n  br i1 %2, label %for_body_0, label %for_end_0\nfor_body_0:\n  %3 = getelementptr [1 x i32], ptr %memory_0, i32 %1\n  %4 = call ccc i1 @eclair_btree_insert_value_0(ptr %0, ptr %3)\n  %5 = add i32 1, %1\n  br label %for_begin_0\nfor_end_0:\n  br label %end_0\nb_0:\n  %6 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 2\n  br label %for_begin_1\nfor_begin_1:\n  %7 = phi i32 [0, %b_0], [%11, %for_body_1]\n  %8 = icmp ult i32 %7, %fact_count_0\n  br i1 %8, label %for_body_1, label %for_end_1\nfor_body_1:\n  %9 = getelementptr [3 x i32], ptr %memory_0, i32 %7\n  %10 = call ccc i1 @eclair_btree_insert_value_1(ptr %6, ptr %9)\n  %11 = add i32 1, %7\n  br label %for_begin_1\nfor_end_1:\n  br label %end_0\nswitch.default_0:\n  ret void\nend_0:\n  ret void\n}\n//--- expected_eclair_get_facts_llvm.out\ndefine external ccc ptr @eclair_get_facts(ptr %eclair_program_0, i32 %fact_type_0) \"wasm-export-name\"=\"eclair_get_facts\" {\nstart:\n  %stack.ptr_0 = alloca i32, i32 1\n  %stack.ptr_1 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_3 = alloca i32, i32 1\n  %stack.ptr_4 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_5 = alloca %btree_iterator_t_1, i32 1\n  switch i32 %fact_type_0, label %switch.default_0 [i32 0, label %a_0 i32 1, label %b_0]\na_0:\n  %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 1\n  %1 = call ccc i64 @eclair_btree_size_0(ptr %0)\n  %2 = trunc i64 %1 to i32\n  %3 = mul i32 %2, 4\n  %4 = call ccc ptr @malloc(i32 %3)\n  store i32 0, ptr %stack.ptr_0\n  call ccc void @eclair_btree_begin_0(ptr %0, ptr %stack.ptr_1)\n  call ccc void @eclair_btree_end_0(ptr %0, ptr %stack.ptr_2)\n  br label %while_begin_0\nwhile_begin_0:\n  %5 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_1, ptr %stack.ptr_2)\n  %6 = select i1 %5, i1 0, i1 1\n  br i1 %6, label %while_body_0, label %while_end_0\nwhile_body_0:\n  %7 = load i32, ptr %stack.ptr_0\n  %8 = getelementptr [1 x i32], ptr %4, i32 %7\n  %9 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_1)\n  %10 = getelementptr [1 x i32], ptr %9, i32 0\n  %11 = load [1 x i32], ptr %10\n  %12 = getelementptr [1 x i32], ptr %8, i32 0\n  store [1 x i32] %11, ptr %12\n  %13 = add i32 %7, 1\n  store i32 %13, ptr %stack.ptr_0\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_1)\n  br label %while_begin_0\nwhile_end_0:\n  ret ptr %4\nb_0:\n  %14 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 2\n  %15 = call ccc i64 @eclair_btree_size_1(ptr %14)\n  %16 = trunc i64 %15 to i32\n  %17 = mul i32 %16, 12\n  %18 = call ccc ptr @malloc(i32 %17)\n  store i32 0, ptr %stack.ptr_3\n  call ccc void @eclair_btree_begin_1(ptr %14, ptr %stack.ptr_4)\n  call ccc void @eclair_btree_end_1(ptr %14, ptr %stack.ptr_5)\n  br label %while_begin_1\nwhile_begin_1:\n  %19 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_4, ptr %stack.ptr_5)\n  %20 = select i1 %19, i1 0, i1 1\n  br i1 %20, label %while_body_1, label %while_end_1\nwhile_body_1:\n  %21 = load i32, ptr %stack.ptr_3\n  %22 = getelementptr [3 x i32], ptr %18, i32 %21\n  %23 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_4)\n  %24 = getelementptr [3 x i32], ptr %23, i32 0\n  %25 = load [3 x i32], ptr %24\n  %26 = getelementptr [3 x i32], ptr %22, i32 0\n  store [3 x i32] %25, ptr %26\n  %27 = add i32 %21, 1\n  store i32 %27, ptr %stack.ptr_3\n  call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_4)\n  br label %while_begin_1\nwhile_end_1:\n  ret ptr %18\nswitch.default_0:\n  ret ptr zeroinitializer\n}\n"
  },
  {
    "path": "tests/lowering/extern_definitions.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: %extract_snippet %t/actual_eir.out \"fn.*eclair_program_run\" > %t/actual_eir_snippet.out\n// RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_run\" > %t/actual_llvm_snippet.out\n// RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out\n\n//--- program.eclair\n@def edge(u32, u32) input.\n@def test_externs(u32) output.\n\n@extern constraint(string).\n@extern func(u32) u32.\n@extern func2(u32, u32) string.\n\ntest_externs(func(123)).\n\ntest_externs(x) :-\n  edge(x, _),\n  constraint(\"abc\"),\n  constraint(func2(123, 456)).\n\ntest_externs(x) :-\n  edge(x, y),\n  x = func(123),  // This can be indexed on\n  x = func(y),    // This can't, x and y defined in same relation\n  \"abc\" = func2(456, 789).\n\n//--- expected_ra.out\nif 2 = func2(456, 789) do\n  search edge as edge0 where (edge0[0] = func(123)) do\n    if edge0[0] = func(edge0[1]) do\n      project (edge0[0]) into test_externs\nif constraint(2) != 0 do\n  if constraint(func2(123, 456)) != 0 do\n    search edge as edge0 do\n      project (edge0[0]) into test_externs\nproject (func(123)) into test_externs\n//--- expected_eir.out\nexport fn eclair_program_run(*Program) -> Void\n{\n  condition = (2 == func2(FN_ARG[0].0, 456, 789))\n  if (condition)\n  {\n    lower_bound_value = edge.stack_allocate Value\n    upper_bound_value = edge.stack_allocate Value\n    lower_bound_value.0 = func(FN_ARG[0].0, 123)\n    lower_bound_value.1 = 0\n    upper_bound_value.0 = func(FN_ARG[0].0, 123)\n    upper_bound_value.1 = 4294967295\n    begin_iter = edge.stack_allocate Iter\n    end_iter = edge.stack_allocate Iter\n    edge.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter)\n    edge.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter)\n    loop\n    {\n      condition_1 = edge.iter_is_equal(begin_iter, end_iter)\n      if (condition_1)\n      {\n        goto range_query.end\n      }\n      current = edge.iter_current(begin_iter)\n      condition_2 = (current.0 == func(FN_ARG[0].0, current.1))\n      if (condition_2)\n      {\n        value = test_externs.stack_allocate Value\n        value.0 = current.0\n        test_externs.insert(FN_ARG[0].2, value)\n      }\n      edge.iter_next(begin_iter)\n    }\n    range_query.end:\n  }\n  condition_3 = (constraint(FN_ARG[0].0, 2) != 0)\n  if (condition_3)\n  {\n    condition_4 = (constraint(FN_ARG[0].0, func2(FN_ARG[0].0, 123, 456)) != 0)\n    if (condition_4)\n    {\n      lower_bound_value_1 = edge.stack_allocate Value\n      upper_bound_value_1 = edge.stack_allocate Value\n      lower_bound_value_1.0 = 0\n      lower_bound_value_1.1 = 0\n      upper_bound_value_1.0 = 4294967295\n      upper_bound_value_1.1 = 4294967295\n      begin_iter_1 = edge.stack_allocate Iter\n      end_iter_1 = edge.stack_allocate Iter\n      edge.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_1)\n      edge.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_1)\n      loop\n      {\n        condition_5 = edge.iter_is_equal(begin_iter_1, end_iter_1)\n        if (condition_5)\n        {\n          goto range_query.end_1\n        }\n        current_1 = edge.iter_current(begin_iter_1)\n        value_1 = test_externs.stack_allocate Value\n        value_1.0 = current_1.0\n        test_externs.insert(FN_ARG[0].2, value_1)\n        edge.iter_next(begin_iter_1)\n      }\n      range_query.end_1:\n    }\n  }\n  value_2 = test_externs.stack_allocate Value\n  value_2.0 = func(FN_ARG[0].0, 123)\n  test_externs.insert(FN_ARG[0].2, value_2)\n}\n//--- expected_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca [2 x i32], i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_4 = alloca [1 x i32], i32 1\n  %stack.ptr_5 = alloca [2 x i32], i32 1\n  %stack.ptr_6 = alloca [2 x i32], i32 1\n  %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_8 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_9 = alloca [1 x i32], i32 1\n  %stack.ptr_10 = alloca [1 x i32], i32 1\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %1 = call ccc i32 @func2(ptr %0, i32 456, i32 789)\n  %2 = icmp eq i32 2, %1\n  br i1 %2, label %if_0, label %end_if_2\nif_0:\n  %3 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %5 = call ccc i32 @func(ptr %4, i32 123)\n  store i32 %5, ptr %3\n  %6 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 0, ptr %6\n  %7 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  %8 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %9 = call ccc i32 @func(ptr %8, i32 123)\n  store i32 %9, ptr %7\n  %10 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1\n  store i32 4294967295, ptr %10\n  %11 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %11, ptr %stack.ptr_0, ptr %stack.ptr_2)\n  %12 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %12, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  br label %loop_0\nloop_0:\n  %13 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3)\n  br i1 %13, label %if_1, label %end_if_0\nif_1:\n  br label %range_query.end\nend_if_0:\n  %14 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2)\n  %15 = getelementptr [2 x i32], ptr %14, i32 0, i32 0\n  %16 = load i32, ptr %15\n  %17 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %18 = getelementptr [2 x i32], ptr %14, i32 0, i32 1\n  %19 = call ccc i32 @func(ptr %17, ptr %18)\n  %20 = icmp eq i32 %16, %19\n  br i1 %20, label %if_2, label %end_if_1\nif_2:\n  %21 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0\n  %22 = getelementptr [2 x i32], ptr %14, i32 0, i32 0\n  %23 = load i32, ptr %22\n  store i32 %23, ptr %21\n  %24 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %25 = call ccc i1 @eclair_btree_insert_value_1(ptr %24, ptr %stack.ptr_4)\n  br label %end_if_1\nend_if_1:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2)\n  br label %loop_0\nrange_query.end:\n  br label %end_if_2\nend_if_2:\n  %26 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %27 = call ccc i1 @constraint(ptr %26, i32 2)\n  %28 = icmp ne i1 %27, 0\n  br i1 %28, label %if_3, label %end_if_5\nif_3:\n  %29 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %30 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %31 = call ccc i32 @func2(ptr %30, i32 123, i32 456)\n  %32 = call ccc i1 @constraint(ptr %29, i32 %31)\n  %33 = icmp ne i1 %32, 0\n  br i1 %33, label %if_4, label %end_if_4\nif_4:\n  %34 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0\n  store i32 0, ptr %34\n  %35 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1\n  store i32 0, ptr %35\n  %36 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0\n  store i32 4294967295, ptr %36\n  %37 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1\n  store i32 4294967295, ptr %37\n  %38 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %38, ptr %stack.ptr_5, ptr %stack.ptr_7)\n  %39 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %39, ptr %stack.ptr_6, ptr %stack.ptr_8)\n  br label %loop_1\nloop_1:\n  %40 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_7, ptr %stack.ptr_8)\n  br i1 %40, label %if_5, label %end_if_3\nif_5:\n  br label %range_query.end_1\nend_if_3:\n  %41 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_7)\n  %42 = getelementptr [1 x i32], ptr %stack.ptr_9, i32 0, i32 0\n  %43 = getelementptr [2 x i32], ptr %41, i32 0, i32 0\n  %44 = load i32, ptr %43\n  store i32 %44, ptr %42\n  %45 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %46 = call ccc i1 @eclair_btree_insert_value_1(ptr %45, ptr %stack.ptr_9)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_7)\n  br label %loop_1\nrange_query.end_1:\n  br label %end_if_4\nend_if_4:\n  br label %end_if_5\nend_if_5:\n  %47 = getelementptr [1 x i32], ptr %stack.ptr_10, i32 0, i32 0\n  %48 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  %49 = call ccc i32 @func(ptr %48, i32 123)\n  store i32 %49, ptr %47\n  %50 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %51 = call ccc i1 @eclair_btree_insert_value_1(ptr %50, ptr %stack.ptr_10)\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/multiple_clauses_same_name.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"program = \" > %t/actual_eclair_program_type.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_init\" > %t/actual_eclair_program_init_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_destroy\" > %t/actual_eclair_program_destroy_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out\n// RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out\n// RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n//--- program.eclair\n@def link(u32, u32).\n@def chain(u32, u32, u32) output.\n\nlink(1,2).\n\nchain(x, y, z) :-\n  link(x, y),\n  link(y, z).\n\n//--- expected_ra.out\nproject (1, 2) into link\nsearch link as link0 do\n  search link as link1 where (link0[1] = link1[0]) do\n    project (link0[0], link0[1], link1[1]) into chain\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  chain btree(num_columns=3, index=[0,1,2], block_size=256, search_type=linear)\n  link btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  chain.init_empty(program.1)\n  link.init_empty(program.2)\n  symbol_table.insert(program.0, \"link\")\n  symbol_table.insert(program.0, \"chain\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  chain.destroy(FN_ARG[0].1)\n  link.destroy(FN_ARG[0].2)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  value = link.stack_allocate Value\n  value.0 = 1\n  value.1 = 2\n  link.insert(FN_ARG[0].2, value)\n  lower_bound_value = link.stack_allocate Value\n  upper_bound_value = link.stack_allocate Value\n  lower_bound_value.0 = 0\n  lower_bound_value.1 = 0\n  upper_bound_value.0 = 4294967295\n  upper_bound_value.1 = 4294967295\n  begin_iter = link.stack_allocate Iter\n  end_iter = link.stack_allocate Iter\n  link.iter_lower_bound(FN_ARG[0].2, lower_bound_value, begin_iter)\n  link.iter_upper_bound(FN_ARG[0].2, upper_bound_value, end_iter)\n  loop\n  {\n    condition = link.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = link.iter_current(begin_iter)\n    lower_bound_value_1 = link.stack_allocate Value\n    upper_bound_value_1 = link.stack_allocate Value\n    lower_bound_value_1.0 = current.1\n    lower_bound_value_1.1 = 0\n    upper_bound_value_1.0 = current.1\n    upper_bound_value_1.1 = 4294967295\n    begin_iter_1 = link.stack_allocate Iter\n    end_iter_1 = link.stack_allocate Iter\n    link.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1)\n    link.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1)\n    loop\n    {\n      condition_1 = link.iter_is_equal(begin_iter_1, end_iter_1)\n      if (condition_1)\n      {\n        goto range_query.end_1\n      }\n      current_1 = link.iter_current(begin_iter_1)\n      value_1 = chain.stack_allocate Value\n      value_1.0 = current.0\n      value_1.1 = current.1\n      value_1.2 = current_1.1\n      chain.insert(FN_ARG[0].1, value_1)\n      link.iter_next(begin_iter_1)\n    }\n    range_query.end_1:\n    link.iter_next(begin_iter)\n  }\n  range_query.end:\n}\n//--- expected_eclair_program_type.out\n%program = type {%symbol_table, %btree_t_0, %btree_t_1}\n//--- expected_eclair_program_init_llvm.out\ndefine external ccc ptr @eclair_program_init() \"wasm-export-name\"=\"eclair_program_init\" {\nstart:\n  %stack.ptr_0 = alloca %symbol_t, i32 1\n  %stack.ptr_1 = alloca %symbol_t, i32 1\n  %0 = call ccc ptr @malloc(i32 1592)\n  %1 = getelementptr %program, ptr %0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_init(ptr %1)\n  %2 = getelementptr %program, ptr %0, i32 0, i32 1\n  call ccc void @eclair_btree_init_empty_0(ptr %2)\n  %3 = getelementptr %program, ptr %0, i32 0, i32 2\n  call ccc void @eclair_btree_init_empty_1(ptr %3)\n  %4 = getelementptr %program, ptr %0, i32 0, i32 0\n  %5 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0\n  %6 = zext i32 4 to i64\n  %7 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %7, ptr %5, i64 %6, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %7)\n  %8 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %4, ptr %stack.ptr_0)\n  %9 = getelementptr %program, ptr %0, i32 0, i32 0\n  %10 = getelementptr inbounds [6 x i8], ptr @string_literal_1, i32 0, i32 0\n  %11 = zext i32 5 to i64\n  %12 = call ccc ptr @malloc(i32 5)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %12, ptr %10, i64 %11, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 5, ptr %12)\n  %13 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %9, ptr %stack.ptr_1)\n  ret ptr %0\n}\n//--- expected_eclair_program_destroy_llvm.out\ndefine external ccc void @eclair_program_destroy(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_destroy\" {\nstart:\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_destroy(ptr %0)\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_destroy_0(ptr %1)\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_destroy_1(ptr %2)\n  call ccc void @free(ptr %arg_0)\n  ret void\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca [2 x i32], i32 1\n  %stack.ptr_2 = alloca [2 x i32], i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_4 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_5 = alloca [2 x i32], i32 1\n  %stack.ptr_6 = alloca [2 x i32], i32 1\n  %stack.ptr_7 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_8 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_9 = alloca [3 x i32], i32 1\n  %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 1, ptr %0\n  %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 2, ptr %1\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %3 = call ccc i1 @eclair_btree_insert_value_1(ptr %2, ptr %stack.ptr_0)\n  %4 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 0, ptr %4\n  %5 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1\n  store i32 0, ptr %5\n  %6 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 0\n  store i32 4294967295, ptr %6\n  %7 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 1\n  store i32 4294967295, ptr %7\n  %8 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_1(ptr %8, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  %9 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_1(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4)\n  br label %loop_0\nloop_0:\n  %10 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_3, ptr %stack.ptr_4)\n  br i1 %10, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %11 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_3)\n  %12 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0\n  %13 = getelementptr [2 x i32], ptr %11, i32 0, i32 1\n  %14 = load i32, ptr %13\n  store i32 %14, ptr %12\n  %15 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1\n  store i32 0, ptr %15\n  %16 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0\n  %17 = getelementptr [2 x i32], ptr %11, i32 0, i32 1\n  %18 = load i32, ptr %17\n  store i32 %18, ptr %16\n  %19 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1\n  store i32 4294967295, ptr %19\n  %20 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_1(ptr %20, ptr %stack.ptr_5, ptr %stack.ptr_7)\n  %21 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_1(ptr %21, ptr %stack.ptr_6, ptr %stack.ptr_8)\n  br label %loop_1\nloop_1:\n  %22 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_7, ptr %stack.ptr_8)\n  br i1 %22, label %if_1, label %end_if_1\nif_1:\n  br label %range_query.end_1\nend_if_1:\n  %23 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_7)\n  %24 = getelementptr [3 x i32], ptr %stack.ptr_9, i32 0, i32 0\n  %25 = getelementptr [2 x i32], ptr %11, i32 0, i32 0\n  %26 = load i32, ptr %25\n  store i32 %26, ptr %24\n  %27 = getelementptr [3 x i32], ptr %stack.ptr_9, i32 0, i32 1\n  %28 = getelementptr [2 x i32], ptr %11, i32 0, i32 1\n  %29 = load i32, ptr %28\n  store i32 %29, ptr %27\n  %30 = getelementptr [3 x i32], ptr %stack.ptr_9, i32 0, i32 2\n  %31 = getelementptr [2 x i32], ptr %23, i32 0, i32 1\n  %32 = load i32, ptr %31\n  store i32 %32, ptr %30\n  %33 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %34 = call ccc i1 @eclair_btree_insert_value_0(ptr %33, ptr %stack.ptr_9)\n  call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_7)\n  br label %loop_1\nrange_query.end_1:\n  call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_3)\n  br label %loop_0\nrange_query.end:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/multiple_rule_clauses.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"program = \" > %t/actual_eclair_program_type.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_init\" > %t/actual_eclair_program_init_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_destroy\" > %t/actual_eclair_program_destroy_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out\n// RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out\n// RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n//--- program.eclair\n@def first(u32).\n@def second(u32, u32).\n@def third(u32, u32) output.\n\nfirst(1).\nsecond(2, 3).\n\nthird(x, y) :-\n  first(y),\n  second(x, y).\n\n//--- expected_ra.out\nproject (2, 3) into second\nproject (1) into first\nsearch first as first0 do\n  search second as second1 where (first0[0] = second1[1]) do\n    project (second1[0], first0[0]) into third\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  first btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  second btree(num_columns=2, index=[1,0], block_size=256, search_type=linear)\n  third btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  first.init_empty(program.1)\n  second.init_empty(program.2)\n  third.init_empty(program.3)\n  symbol_table.insert(program.0, \"first\")\n  symbol_table.insert(program.0, \"second\")\n  symbol_table.insert(program.0, \"third\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  first.destroy(FN_ARG[0].1)\n  second.destroy(FN_ARG[0].2)\n  third.destroy(FN_ARG[0].3)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  value = second.stack_allocate Value\n  value.0 = 2\n  value.1 = 3\n  second.insert(FN_ARG[0].2, value)\n  value_1 = first.stack_allocate Value\n  value_1.0 = 1\n  first.insert(FN_ARG[0].1, value_1)\n  lower_bound_value = first.stack_allocate Value\n  upper_bound_value = first.stack_allocate Value\n  lower_bound_value.0 = 0\n  upper_bound_value.0 = 4294967295\n  begin_iter = first.stack_allocate Iter\n  end_iter = first.stack_allocate Iter\n  first.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter)\n  first.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter)\n  loop\n  {\n    condition = first.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = first.iter_current(begin_iter)\n    lower_bound_value_1 = second.stack_allocate Value\n    upper_bound_value_1 = second.stack_allocate Value\n    lower_bound_value_1.0 = 0\n    lower_bound_value_1.1 = current.0\n    upper_bound_value_1.0 = 4294967295\n    upper_bound_value_1.1 = current.0\n    begin_iter_1 = second.stack_allocate Iter\n    end_iter_1 = second.stack_allocate Iter\n    second.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1)\n    second.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1)\n    loop\n    {\n      condition_1 = second.iter_is_equal(begin_iter_1, end_iter_1)\n      if (condition_1)\n      {\n        goto range_query.end_1\n      }\n      current_1 = second.iter_current(begin_iter_1)\n      value_2 = third.stack_allocate Value\n      value_2.0 = current_1.0\n      value_2.1 = current.0\n      third.insert(FN_ARG[0].3, value_2)\n      second.iter_next(begin_iter_1)\n    }\n    range_query.end_1:\n    first.iter_next(begin_iter)\n  }\n  range_query.end:\n}\n//--- expected_eclair_program_type.out\n%program = type {%symbol_table, %btree_t_0, %btree_t_1, %btree_t_2}\n//--- expected_eclair_program_init_llvm.out\ndefine external ccc ptr @eclair_program_init() \"wasm-export-name\"=\"eclair_program_init\" {\nstart:\n  %stack.ptr_0 = alloca %symbol_t, i32 1\n  %stack.ptr_1 = alloca %symbol_t, i32 1\n  %stack.ptr_2 = alloca %symbol_t, i32 1\n  %0 = call ccc ptr @malloc(i32 1608)\n  %1 = getelementptr %program, ptr %0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_init(ptr %1)\n  %2 = getelementptr %program, ptr %0, i32 0, i32 1\n  call ccc void @eclair_btree_init_empty_0(ptr %2)\n  %3 = getelementptr %program, ptr %0, i32 0, i32 2\n  call ccc void @eclair_btree_init_empty_1(ptr %3)\n  %4 = getelementptr %program, ptr %0, i32 0, i32 3\n  call ccc void @eclair_btree_init_empty_2(ptr %4)\n  %5 = getelementptr %program, ptr %0, i32 0, i32 0\n  %6 = getelementptr inbounds [6 x i8], ptr @string_literal_0, i32 0, i32 0\n  %7 = zext i32 5 to i64\n  %8 = call ccc ptr @malloc(i32 5)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %8, ptr %6, i64 %7, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 5, ptr %8)\n  %9 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %5, ptr %stack.ptr_0)\n  %10 = getelementptr %program, ptr %0, i32 0, i32 0\n  %11 = getelementptr inbounds [7 x i8], ptr @string_literal_1, i32 0, i32 0\n  %12 = zext i32 6 to i64\n  %13 = call ccc ptr @malloc(i32 6)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %13, ptr %11, i64 %12, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 6, ptr %13)\n  %14 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %10, ptr %stack.ptr_1)\n  %15 = getelementptr %program, ptr %0, i32 0, i32 0\n  %16 = getelementptr inbounds [6 x i8], ptr @string_literal_2, i32 0, i32 0\n  %17 = zext i32 5 to i64\n  %18 = call ccc ptr @malloc(i32 5)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %18, ptr %16, i64 %17, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_2, i32 5, ptr %18)\n  %19 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %15, ptr %stack.ptr_2)\n  ret ptr %0\n}\n//--- expected_eclair_program_destroy_llvm.out\ndefine external ccc void @eclair_program_destroy(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_destroy\" {\nstart:\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_destroy(ptr %0)\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_destroy_0(ptr %1)\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_destroy_1(ptr %2)\n  %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_destroy_2(ptr %3)\n  call ccc void @free(ptr %arg_0)\n  ret void\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca [1 x i32], i32 1\n  %stack.ptr_2 = alloca [1 x i32], i32 1\n  %stack.ptr_3 = alloca [1 x i32], i32 1\n  %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_6 = alloca [2 x i32], i32 1\n  %stack.ptr_7 = alloca [2 x i32], i32 1\n  %stack.ptr_8 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_9 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_10 = alloca [2 x i32], i32 1\n  %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 2, ptr %0\n  %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 3, ptr %1\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %3 = call ccc i1 @eclair_btree_insert_value_1(ptr %2, ptr %stack.ptr_0)\n  %4 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 1, ptr %4\n  %5 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %6 = call ccc i1 @eclair_btree_insert_value_0(ptr %5, ptr %stack.ptr_1)\n  %7 = getelementptr [1 x i32], ptr %stack.ptr_2, i32 0, i32 0\n  store i32 0, ptr %7\n  %8 = getelementptr [1 x i32], ptr %stack.ptr_3, i32 0, i32 0\n  store i32 4294967295, ptr %8\n  %9 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4)\n  %10 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %10, ptr %stack.ptr_3, ptr %stack.ptr_5)\n  br label %loop_0\nloop_0:\n  %11 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_4, ptr %stack.ptr_5)\n  br i1 %11, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %12 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_4)\n  %13 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0\n  store i32 0, ptr %13\n  %14 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1\n  %15 = getelementptr [1 x i32], ptr %12, i32 0, i32 0\n  %16 = load i32, ptr %15\n  store i32 %16, ptr %14\n  %17 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0\n  store i32 4294967295, ptr %17\n  %18 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1\n  %19 = getelementptr [1 x i32], ptr %12, i32 0, i32 0\n  %20 = load i32, ptr %19\n  store i32 %20, ptr %18\n  %21 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_1(ptr %21, ptr %stack.ptr_6, ptr %stack.ptr_8)\n  %22 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_1(ptr %22, ptr %stack.ptr_7, ptr %stack.ptr_9)\n  br label %loop_1\nloop_1:\n  %23 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_8, ptr %stack.ptr_9)\n  br i1 %23, label %if_1, label %end_if_1\nif_1:\n  br label %range_query.end_1\nend_if_1:\n  %24 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_8)\n  %25 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0\n  %26 = getelementptr [2 x i32], ptr %24, i32 0, i32 0\n  %27 = load i32, ptr %26\n  store i32 %27, ptr %25\n  %28 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1\n  %29 = getelementptr [1 x i32], ptr %12, i32 0, i32 0\n  %30 = load i32, ptr %29\n  store i32 %30, ptr %28\n  %31 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %32 = call ccc i1 @eclair_btree_insert_value_2(ptr %31, ptr %stack.ptr_10)\n  call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_8)\n  br label %loop_1\nrange_query.end_1:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_4)\n  br label %loop_0\nrange_query.end:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/mutually_recursive_rules.eclair",
    "content": "// TODO variant where one is recursive\n// TODO tests for rules with >2 clauses, ...\n\n// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"program = \" > %t/actual_eclair_program_type.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_init\" > %t/actual_eclair_program_init_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_destroy\" > %t/actual_eclair_program_destroy_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out\n// RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out\n// RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n//--- program.eclair\n@def a(u32) output.\n@def b(u32) output.\n@def c(u32) output.\n@def d(u32) output.\n\na(x) :- b(x), c(x).\nb(1).\nb(x) :- c(x), d(x).\nc(2).\nc(x) :- b(x), d(x).\nd(3).\n\n//--- expected_ra.out\nproject (3) into d\nproject (2) into c\nproject (1) into b\nmerge c delta_c\nmerge b delta_b\nloop do\n  purge new_c\n  purge new_b\n  parallel do\n    search delta_b as delta_b0 do\n      if (delta_b0[0]) ∉ c do\n        search d as d1 where (delta_b0[0] = d1[0]) do\n          project (delta_b0[0]) into new_c\n    search delta_c as delta_c0 do\n      if (delta_c0[0]) ∉ b do\n        search d as d1 where (delta_c0[0] = d1[0]) do\n          project (delta_c0[0]) into new_b\n  exit if counttuples(new_c) = 0 and counttuples(new_b) = 0\n  merge new_c c\n  swap new_c delta_c\n  merge new_b b\n  swap new_b delta_b\nsearch b as b0 do\n  search c as c1 where (b0[0] = c1[0]) do\n    project (b0[0]) into a\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  a btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  b btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  c btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  d btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  delta_b btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  delta_c btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  new_b btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n  new_c btree(num_columns=1, index=[0], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  a.init_empty(program.1)\n  b.init_empty(program.2)\n  c.init_empty(program.3)\n  d.init_empty(program.4)\n  delta_b.init_empty(program.5)\n  delta_c.init_empty(program.6)\n  new_b.init_empty(program.7)\n  new_c.init_empty(program.8)\n  symbol_table.insert(program.0, \"a\")\n  symbol_table.insert(program.0, \"b\")\n  symbol_table.insert(program.0, \"c\")\n  symbol_table.insert(program.0, \"d\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  a.destroy(FN_ARG[0].1)\n  b.destroy(FN_ARG[0].2)\n  c.destroy(FN_ARG[0].3)\n  d.destroy(FN_ARG[0].4)\n  delta_b.destroy(FN_ARG[0].5)\n  delta_c.destroy(FN_ARG[0].6)\n  new_b.destroy(FN_ARG[0].7)\n  new_c.destroy(FN_ARG[0].8)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  value = d.stack_allocate Value\n  value.0 = 3\n  d.insert(FN_ARG[0].4, value)\n  value_1 = c.stack_allocate Value\n  value_1.0 = 2\n  c.insert(FN_ARG[0].3, value_1)\n  value_2 = b.stack_allocate Value\n  value_2.0 = 1\n  b.insert(FN_ARG[0].2, value_2)\n  begin_iter = c.stack_allocate Iter\n  end_iter = c.stack_allocate Iter\n  c.iter_begin(FN_ARG[0].3, begin_iter)\n  c.iter_end(FN_ARG[0].3, end_iter)\n  delta_c.insert_range<c[0]>(FN_ARG[0].6, begin_iter, end_iter)\n  begin_iter_1 = b.stack_allocate Iter\n  end_iter_1 = b.stack_allocate Iter\n  b.iter_begin(FN_ARG[0].2, begin_iter_1)\n  b.iter_end(FN_ARG[0].2, end_iter_1)\n  delta_b.insert_range<b[0]>(FN_ARG[0].5, begin_iter_1, end_iter_1)\n  loop\n  {\n    new_c.purge(FN_ARG[0].8)\n    new_b.purge(FN_ARG[0].7)\n    parallel\n    {\n      lower_bound_value = b.stack_allocate Value\n      upper_bound_value = b.stack_allocate Value\n      lower_bound_value.0 = 0\n      upper_bound_value.0 = 4294967295\n      begin_iter_2 = b.stack_allocate Iter\n      end_iter_2 = b.stack_allocate Iter\n      delta_b.iter_lower_bound(FN_ARG[0].5, lower_bound_value, begin_iter_2)\n      delta_b.iter_upper_bound(FN_ARG[0].5, upper_bound_value, end_iter_2)\n      loop\n      {\n        condition = delta_b.iter_is_equal(begin_iter_2, end_iter_2)\n        if (condition)\n        {\n          goto range_query.end\n        }\n        current = delta_b.iter_current(begin_iter_2)\n        value_3 = c.stack_allocate Value\n        value_3.0 = current.0\n        contains_result = c.contains(FN_ARG[0].3, value_3)\n        condition_1 = not contains_result\n        if (condition_1)\n        {\n          lower_bound_value_1 = d.stack_allocate Value\n          upper_bound_value_1 = d.stack_allocate Value\n          lower_bound_value_1.0 = current.0\n          upper_bound_value_1.0 = current.0\n          begin_iter_3 = d.stack_allocate Iter\n          end_iter_3 = d.stack_allocate Iter\n          d.iter_lower_bound(FN_ARG[0].4, lower_bound_value_1, begin_iter_3)\n          d.iter_upper_bound(FN_ARG[0].4, upper_bound_value_1, end_iter_3)\n          loop\n          {\n            condition_2 = d.iter_is_equal(begin_iter_3, end_iter_3)\n            if (condition_2)\n            {\n              goto range_query.end_1\n            }\n            current_1 = d.iter_current(begin_iter_3)\n            value_4 = c.stack_allocate Value\n            value_4.0 = current.0\n            new_c.insert(FN_ARG[0].8, value_4)\n            d.iter_next(begin_iter_3)\n          }\n          range_query.end_1:\n        }\n        delta_b.iter_next(begin_iter_2)\n      }\n      range_query.end:\n      lower_bound_value_2 = c.stack_allocate Value\n      upper_bound_value_2 = c.stack_allocate Value\n      lower_bound_value_2.0 = 0\n      upper_bound_value_2.0 = 4294967295\n      begin_iter_4 = c.stack_allocate Iter\n      end_iter_4 = c.stack_allocate Iter\n      delta_c.iter_lower_bound(FN_ARG[0].6, lower_bound_value_2, begin_iter_4)\n      delta_c.iter_upper_bound(FN_ARG[0].6, upper_bound_value_2, end_iter_4)\n      loop\n      {\n        condition_3 = delta_c.iter_is_equal(begin_iter_4, end_iter_4)\n        if (condition_3)\n        {\n          goto range_query.end_2\n        }\n        current_2 = delta_c.iter_current(begin_iter_4)\n        value_5 = b.stack_allocate Value\n        value_5.0 = current_2.0\n        contains_result_1 = b.contains(FN_ARG[0].2, value_5)\n        condition_4 = not contains_result_1\n        if (condition_4)\n        {\n          lower_bound_value_3 = d.stack_allocate Value\n          upper_bound_value_3 = d.stack_allocate Value\n          lower_bound_value_3.0 = current_2.0\n          upper_bound_value_3.0 = current_2.0\n          begin_iter_5 = d.stack_allocate Iter\n          end_iter_5 = d.stack_allocate Iter\n          d.iter_lower_bound(FN_ARG[0].4, lower_bound_value_3, begin_iter_5)\n          d.iter_upper_bound(FN_ARG[0].4, upper_bound_value_3, end_iter_5)\n          loop\n          {\n            condition_5 = d.iter_is_equal(begin_iter_5, end_iter_5)\n            if (condition_5)\n            {\n              goto range_query.end_3\n            }\n            current_3 = d.iter_current(begin_iter_5)\n            value_6 = b.stack_allocate Value\n            value_6.0 = current_2.0\n            new_b.insert(FN_ARG[0].7, value_6)\n            d.iter_next(begin_iter_5)\n          }\n          range_query.end_3:\n        }\n        delta_c.iter_next(begin_iter_4)\n      }\n      range_query.end_2:\n    }\n    condition_6 = new_b.is_empty(FN_ARG[0].7)\n    if (condition_6)\n    {\n      condition_7 = new_c.is_empty(FN_ARG[0].8)\n      if (condition_7)\n      {\n        goto loop.end\n      }\n    }\n    begin_iter_6 = c.stack_allocate Iter\n    end_iter_6 = c.stack_allocate Iter\n    new_c.iter_begin(FN_ARG[0].8, begin_iter_6)\n    new_c.iter_end(FN_ARG[0].8, end_iter_6)\n    c.insert_range<new_c[0]>(FN_ARG[0].3, begin_iter_6, end_iter_6)\n    new_c.swap(FN_ARG[0].8, FN_ARG[0].6)\n    begin_iter_7 = b.stack_allocate Iter\n    end_iter_7 = b.stack_allocate Iter\n    new_b.iter_begin(FN_ARG[0].7, begin_iter_7)\n    new_b.iter_end(FN_ARG[0].7, end_iter_7)\n    b.insert_range<new_b[0]>(FN_ARG[0].2, begin_iter_7, end_iter_7)\n    new_b.swap(FN_ARG[0].7, FN_ARG[0].5)\n  }\n  loop.end:\n  lower_bound_value_4 = b.stack_allocate Value\n  upper_bound_value_4 = b.stack_allocate Value\n  lower_bound_value_4.0 = 0\n  upper_bound_value_4.0 = 4294967295\n  begin_iter_8 = b.stack_allocate Iter\n  end_iter_8 = b.stack_allocate Iter\n  b.iter_lower_bound(FN_ARG[0].2, lower_bound_value_4, begin_iter_8)\n  b.iter_upper_bound(FN_ARG[0].2, upper_bound_value_4, end_iter_8)\n  loop\n  {\n    condition_8 = b.iter_is_equal(begin_iter_8, end_iter_8)\n    if (condition_8)\n    {\n      goto range_query.end_4\n    }\n    current_4 = b.iter_current(begin_iter_8)\n    lower_bound_value_5 = c.stack_allocate Value\n    upper_bound_value_5 = c.stack_allocate Value\n    lower_bound_value_5.0 = current_4.0\n    upper_bound_value_5.0 = current_4.0\n    begin_iter_9 = c.stack_allocate Iter\n    end_iter_9 = c.stack_allocate Iter\n    c.iter_lower_bound(FN_ARG[0].3, lower_bound_value_5, begin_iter_9)\n    c.iter_upper_bound(FN_ARG[0].3, upper_bound_value_5, end_iter_9)\n    loop\n    {\n      condition_9 = c.iter_is_equal(begin_iter_9, end_iter_9)\n      if (condition_9)\n      {\n        goto range_query.end_5\n      }\n      current_5 = c.iter_current(begin_iter_9)\n      value_7 = a.stack_allocate Value\n      value_7.0 = current_4.0\n      a.insert(FN_ARG[0].1, value_7)\n      c.iter_next(begin_iter_9)\n    }\n    range_query.end_5:\n    b.iter_next(begin_iter_8)\n  }\n  range_query.end_4:\n}\n//--- expected_eclair_program_type.out\n%program = type {%symbol_table, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0}\n//--- expected_eclair_program_init_llvm.out\ndefine external ccc ptr @eclair_program_init() \"wasm-export-name\"=\"eclair_program_init\" {\nstart:\n  %stack.ptr_0 = alloca %symbol_t, i32 1\n  %stack.ptr_1 = alloca %symbol_t, i32 1\n  %stack.ptr_2 = alloca %symbol_t, i32 1\n  %stack.ptr_3 = alloca %symbol_t, i32 1\n  %0 = call ccc ptr @malloc(i32 1688)\n  %1 = getelementptr %program, ptr %0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_init(ptr %1)\n  %2 = getelementptr %program, ptr %0, i32 0, i32 1\n  call ccc void @eclair_btree_init_empty_0(ptr %2)\n  %3 = getelementptr %program, ptr %0, i32 0, i32 2\n  call ccc void @eclair_btree_init_empty_0(ptr %3)\n  %4 = getelementptr %program, ptr %0, i32 0, i32 3\n  call ccc void @eclair_btree_init_empty_0(ptr %4)\n  %5 = getelementptr %program, ptr %0, i32 0, i32 4\n  call ccc void @eclair_btree_init_empty_0(ptr %5)\n  %6 = getelementptr %program, ptr %0, i32 0, i32 5\n  call ccc void @eclair_btree_init_empty_0(ptr %6)\n  %7 = getelementptr %program, ptr %0, i32 0, i32 6\n  call ccc void @eclair_btree_init_empty_0(ptr %7)\n  %8 = getelementptr %program, ptr %0, i32 0, i32 7\n  call ccc void @eclair_btree_init_empty_0(ptr %8)\n  %9 = getelementptr %program, ptr %0, i32 0, i32 8\n  call ccc void @eclair_btree_init_empty_0(ptr %9)\n  %10 = getelementptr %program, ptr %0, i32 0, i32 0\n  %11 = getelementptr inbounds [2 x i8], ptr @string_literal_0, i32 0, i32 0\n  %12 = zext i32 1 to i64\n  %13 = call ccc ptr @malloc(i32 1)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %13, ptr %11, i64 %12, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 1, ptr %13)\n  %14 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %10, ptr %stack.ptr_0)\n  %15 = getelementptr %program, ptr %0, i32 0, i32 0\n  %16 = getelementptr inbounds [2 x i8], ptr @string_literal_1, i32 0, i32 0\n  %17 = zext i32 1 to i64\n  %18 = call ccc ptr @malloc(i32 1)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %18, ptr %16, i64 %17, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 1, ptr %18)\n  %19 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %15, ptr %stack.ptr_1)\n  %20 = getelementptr %program, ptr %0, i32 0, i32 0\n  %21 = getelementptr inbounds [2 x i8], ptr @string_literal_2, i32 0, i32 0\n  %22 = zext i32 1 to i64\n  %23 = call ccc ptr @malloc(i32 1)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %23, ptr %21, i64 %22, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_2, i32 1, ptr %23)\n  %24 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %20, ptr %stack.ptr_2)\n  %25 = getelementptr %program, ptr %0, i32 0, i32 0\n  %26 = getelementptr inbounds [2 x i8], ptr @string_literal_3, i32 0, i32 0\n  %27 = zext i32 1 to i64\n  %28 = call ccc ptr @malloc(i32 1)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %28, ptr %26, i64 %27, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_3, i32 1, ptr %28)\n  %29 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %25, ptr %stack.ptr_3)\n  ret ptr %0\n}\n//--- expected_eclair_program_destroy_llvm.out\ndefine external ccc void @eclair_program_destroy(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_destroy\" {\nstart:\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_destroy(ptr %0)\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_destroy_0(ptr %1)\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_destroy_0(ptr %2)\n  %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_destroy_0(ptr %3)\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_destroy_0(ptr %4)\n  %5 = getelementptr %program, ptr %arg_0, i32 0, i32 5\n  call ccc void @eclair_btree_destroy_0(ptr %5)\n  %6 = getelementptr %program, ptr %arg_0, i32 0, i32 6\n  call ccc void @eclair_btree_destroy_0(ptr %6)\n  %7 = getelementptr %program, ptr %arg_0, i32 0, i32 7\n  call ccc void @eclair_btree_destroy_0(ptr %7)\n  %8 = getelementptr %program, ptr %arg_0, i32 0, i32 8\n  call ccc void @eclair_btree_destroy_0(ptr %8)\n  call ccc void @free(ptr %arg_0)\n  ret void\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [1 x i32], i32 1\n  %stack.ptr_1 = alloca [1 x i32], i32 1\n  %stack.ptr_2 = alloca [1 x i32], i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_7 = alloca [1 x i32], i32 1\n  %stack.ptr_8 = alloca [1 x i32], i32 1\n  %stack.ptr_9 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_10 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_11 = alloca [1 x i32], i32 1\n  %stack.ptr_12 = alloca [1 x i32], i32 1\n  %stack.ptr_13 = alloca [1 x i32], i32 1\n  %stack.ptr_14 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_15 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_16 = alloca [1 x i32], i32 1\n  %stack.ptr_17 = alloca [1 x i32], i32 1\n  %stack.ptr_18 = alloca [1 x i32], i32 1\n  %stack.ptr_19 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_20 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_21 = alloca [1 x i32], i32 1\n  %stack.ptr_22 = alloca [1 x i32], i32 1\n  %stack.ptr_23 = alloca [1 x i32], i32 1\n  %stack.ptr_24 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_25 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_26 = alloca [1 x i32], i32 1\n  %stack.ptr_27 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_28 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_29 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_30 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_31 = alloca [1 x i32], i32 1\n  %stack.ptr_32 = alloca [1 x i32], i32 1\n  %stack.ptr_33 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_34 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_35 = alloca [1 x i32], i32 1\n  %stack.ptr_36 = alloca [1 x i32], i32 1\n  %stack.ptr_37 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_38 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_39 = alloca [1 x i32], i32 1\n  %0 = getelementptr [1 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 3, ptr %0\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  %2 = call ccc i1 @eclair_btree_insert_value_0(ptr %1, ptr %stack.ptr_0)\n  %3 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 2, ptr %3\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %5 = call ccc i1 @eclair_btree_insert_value_0(ptr %4, ptr %stack.ptr_1)\n  %6 = getelementptr [1 x i32], ptr %stack.ptr_2, i32 0, i32 0\n  store i32 1, ptr %6\n  %7 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %8 = call ccc i1 @eclair_btree_insert_value_0(ptr %7, ptr %stack.ptr_2)\n  %9 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_begin_0(ptr %9, ptr %stack.ptr_3)\n  %10 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_end_0(ptr %10, ptr %stack.ptr_4)\n  %11 = getelementptr %program, ptr %arg_0, i32 0, i32 6\n  call ccc void @eclair_btree_insert_range_delta_c_c(ptr %11, ptr %stack.ptr_3, ptr %stack.ptr_4)\n  %12 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_begin_0(ptr %12, ptr %stack.ptr_5)\n  %13 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_end_0(ptr %13, ptr %stack.ptr_6)\n  %14 = getelementptr %program, ptr %arg_0, i32 0, i32 5\n  call ccc void @eclair_btree_insert_range_delta_b_b(ptr %14, ptr %stack.ptr_5, ptr %stack.ptr_6)\n  br label %loop_0\nloop_0:\n  %15 = getelementptr %program, ptr %arg_0, i32 0, i32 8\n  call ccc void @eclair_btree_clear_0(ptr %15)\n  %16 = getelementptr %program, ptr %arg_0, i32 0, i32 7\n  call ccc void @eclair_btree_clear_0(ptr %16)\n  %17 = getelementptr [1 x i32], ptr %stack.ptr_7, i32 0, i32 0\n  store i32 0, ptr %17\n  %18 = getelementptr [1 x i32], ptr %stack.ptr_8, i32 0, i32 0\n  store i32 4294967295, ptr %18\n  %19 = getelementptr %program, ptr %arg_0, i32 0, i32 5\n  call ccc void @eclair_btree_lower_bound_0(ptr %19, ptr %stack.ptr_7, ptr %stack.ptr_9)\n  %20 = getelementptr %program, ptr %arg_0, i32 0, i32 5\n  call ccc void @eclair_btree_upper_bound_0(ptr %20, ptr %stack.ptr_8, ptr %stack.ptr_10)\n  br label %loop_1\nloop_1:\n  %21 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_9, ptr %stack.ptr_10)\n  br i1 %21, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %22 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_9)\n  %23 = getelementptr [1 x i32], ptr %stack.ptr_11, i32 0, i32 0\n  %24 = getelementptr [1 x i32], ptr %22, i32 0, i32 0\n  %25 = load i32, ptr %24\n  store i32 %25, ptr %23\n  %26 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %27 = call ccc i1 @eclair_btree_contains_0(ptr %26, ptr %stack.ptr_11)\n  %28 = select i1 %27, i1 0, i1 1\n  br i1 %28, label %if_1, label %end_if_2\nif_1:\n  %29 = getelementptr [1 x i32], ptr %stack.ptr_12, i32 0, i32 0\n  %30 = getelementptr [1 x i32], ptr %22, i32 0, i32 0\n  %31 = load i32, ptr %30\n  store i32 %31, ptr %29\n  %32 = getelementptr [1 x i32], ptr %stack.ptr_13, i32 0, i32 0\n  %33 = getelementptr [1 x i32], ptr %22, i32 0, i32 0\n  %34 = load i32, ptr %33\n  store i32 %34, ptr %32\n  %35 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_lower_bound_0(ptr %35, ptr %stack.ptr_12, ptr %stack.ptr_14)\n  %36 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_upper_bound_0(ptr %36, ptr %stack.ptr_13, ptr %stack.ptr_15)\n  br label %loop_2\nloop_2:\n  %37 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_14, ptr %stack.ptr_15)\n  br i1 %37, label %if_2, label %end_if_1\nif_2:\n  br label %range_query.end_1\nend_if_1:\n  %38 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_14)\n  %39 = getelementptr [1 x i32], ptr %stack.ptr_16, i32 0, i32 0\n  %40 = getelementptr [1 x i32], ptr %22, i32 0, i32 0\n  %41 = load i32, ptr %40\n  store i32 %41, ptr %39\n  %42 = getelementptr %program, ptr %arg_0, i32 0, i32 8\n  %43 = call ccc i1 @eclair_btree_insert_value_0(ptr %42, ptr %stack.ptr_16)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_14)\n  br label %loop_2\nrange_query.end_1:\n  br label %end_if_2\nend_if_2:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_9)\n  br label %loop_1\nrange_query.end:\n  %44 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0\n  store i32 0, ptr %44\n  %45 = getelementptr [1 x i32], ptr %stack.ptr_18, i32 0, i32 0\n  store i32 4294967295, ptr %45\n  %46 = getelementptr %program, ptr %arg_0, i32 0, i32 6\n  call ccc void @eclair_btree_lower_bound_0(ptr %46, ptr %stack.ptr_17, ptr %stack.ptr_19)\n  %47 = getelementptr %program, ptr %arg_0, i32 0, i32 6\n  call ccc void @eclair_btree_upper_bound_0(ptr %47, ptr %stack.ptr_18, ptr %stack.ptr_20)\n  br label %loop_3\nloop_3:\n  %48 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_19, ptr %stack.ptr_20)\n  br i1 %48, label %if_3, label %end_if_3\nif_3:\n  br label %range_query.end_2\nend_if_3:\n  %49 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_19)\n  %50 = getelementptr [1 x i32], ptr %stack.ptr_21, i32 0, i32 0\n  %51 = getelementptr [1 x i32], ptr %49, i32 0, i32 0\n  %52 = load i32, ptr %51\n  store i32 %52, ptr %50\n  %53 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %54 = call ccc i1 @eclair_btree_contains_0(ptr %53, ptr %stack.ptr_21)\n  %55 = select i1 %54, i1 0, i1 1\n  br i1 %55, label %if_4, label %end_if_5\nif_4:\n  %56 = getelementptr [1 x i32], ptr %stack.ptr_22, i32 0, i32 0\n  %57 = getelementptr [1 x i32], ptr %49, i32 0, i32 0\n  %58 = load i32, ptr %57\n  store i32 %58, ptr %56\n  %59 = getelementptr [1 x i32], ptr %stack.ptr_23, i32 0, i32 0\n  %60 = getelementptr [1 x i32], ptr %49, i32 0, i32 0\n  %61 = load i32, ptr %60\n  store i32 %61, ptr %59\n  %62 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_lower_bound_0(ptr %62, ptr %stack.ptr_22, ptr %stack.ptr_24)\n  %63 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_upper_bound_0(ptr %63, ptr %stack.ptr_23, ptr %stack.ptr_25)\n  br label %loop_4\nloop_4:\n  %64 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_24, ptr %stack.ptr_25)\n  br i1 %64, label %if_5, label %end_if_4\nif_5:\n  br label %range_query.end_3\nend_if_4:\n  %65 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_24)\n  %66 = getelementptr [1 x i32], ptr %stack.ptr_26, i32 0, i32 0\n  %67 = getelementptr [1 x i32], ptr %49, i32 0, i32 0\n  %68 = load i32, ptr %67\n  store i32 %68, ptr %66\n  %69 = getelementptr %program, ptr %arg_0, i32 0, i32 7\n  %70 = call ccc i1 @eclair_btree_insert_value_0(ptr %69, ptr %stack.ptr_26)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_24)\n  br label %loop_4\nrange_query.end_3:\n  br label %end_if_5\nend_if_5:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_19)\n  br label %loop_3\nrange_query.end_2:\n  %71 = getelementptr %program, ptr %arg_0, i32 0, i32 7\n  %72 = call ccc i1 @eclair_btree_is_empty_0(ptr %71)\n  br i1 %72, label %if_6, label %end_if_7\nif_6:\n  %73 = getelementptr %program, ptr %arg_0, i32 0, i32 8\n  %74 = call ccc i1 @eclair_btree_is_empty_0(ptr %73)\n  br i1 %74, label %if_7, label %end_if_6\nif_7:\n  br label %loop.end\nend_if_6:\n  br label %end_if_7\nend_if_7:\n  %75 = getelementptr %program, ptr %arg_0, i32 0, i32 8\n  call ccc void @eclair_btree_begin_0(ptr %75, ptr %stack.ptr_27)\n  %76 = getelementptr %program, ptr %arg_0, i32 0, i32 8\n  call ccc void @eclair_btree_end_0(ptr %76, ptr %stack.ptr_28)\n  %77 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_insert_range_c_new_c(ptr %77, ptr %stack.ptr_27, ptr %stack.ptr_28)\n  %78 = getelementptr %program, ptr %arg_0, i32 0, i32 8\n  %79 = getelementptr %program, ptr %arg_0, i32 0, i32 6\n  call ccc void @eclair_btree_swap_0(ptr %78, ptr %79)\n  %80 = getelementptr %program, ptr %arg_0, i32 0, i32 7\n  call ccc void @eclair_btree_begin_0(ptr %80, ptr %stack.ptr_29)\n  %81 = getelementptr %program, ptr %arg_0, i32 0, i32 7\n  call ccc void @eclair_btree_end_0(ptr %81, ptr %stack.ptr_30)\n  %82 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_insert_range_b_new_b(ptr %82, ptr %stack.ptr_29, ptr %stack.ptr_30)\n  %83 = getelementptr %program, ptr %arg_0, i32 0, i32 7\n  %84 = getelementptr %program, ptr %arg_0, i32 0, i32 5\n  call ccc void @eclair_btree_swap_0(ptr %83, ptr %84)\n  br label %loop_0\nloop.end:\n  %85 = getelementptr [1 x i32], ptr %stack.ptr_31, i32 0, i32 0\n  store i32 0, ptr %85\n  %86 = getelementptr [1 x i32], ptr %stack.ptr_32, i32 0, i32 0\n  store i32 4294967295, ptr %86\n  %87 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_0(ptr %87, ptr %stack.ptr_31, ptr %stack.ptr_33)\n  %88 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_0(ptr %88, ptr %stack.ptr_32, ptr %stack.ptr_34)\n  br label %loop_5\nloop_5:\n  %89 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_33, ptr %stack.ptr_34)\n  br i1 %89, label %if_8, label %end_if_8\nif_8:\n  br label %range_query.end_4\nend_if_8:\n  %90 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_33)\n  %91 = getelementptr [1 x i32], ptr %stack.ptr_35, i32 0, i32 0\n  %92 = getelementptr [1 x i32], ptr %90, i32 0, i32 0\n  %93 = load i32, ptr %92\n  store i32 %93, ptr %91\n  %94 = getelementptr [1 x i32], ptr %stack.ptr_36, i32 0, i32 0\n  %95 = getelementptr [1 x i32], ptr %90, i32 0, i32 0\n  %96 = load i32, ptr %95\n  store i32 %96, ptr %94\n  %97 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_lower_bound_0(ptr %97, ptr %stack.ptr_35, ptr %stack.ptr_37)\n  %98 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_upper_bound_0(ptr %98, ptr %stack.ptr_36, ptr %stack.ptr_38)\n  br label %loop_6\nloop_6:\n  %99 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_37, ptr %stack.ptr_38)\n  br i1 %99, label %if_9, label %end_if_9\nif_9:\n  br label %range_query.end_5\nend_if_9:\n  %100 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_37)\n  %101 = getelementptr [1 x i32], ptr %stack.ptr_39, i32 0, i32 0\n  %102 = getelementptr [1 x i32], ptr %90, i32 0, i32 0\n  %103 = load i32, ptr %102\n  store i32 %103, ptr %101\n  %104 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %105 = call ccc i1 @eclair_btree_insert_value_0(ptr %104, ptr %stack.ptr_39)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_37)\n  br label %loop_6\nrange_query.end_5:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_33)\n  br label %loop_5\nrange_query.end_4:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/negation.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: %extract_snippet %t/actual_eir.out \"fn.*eclair_program_run\" > %t/actual_eir_snippet.out\n// RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_run\" > %t/actual_llvm_snippet.out\n// RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out\n\n//--- program.eclair\n@def fact1(u32) input.\n@def fact2(u32) input.\n@def fact3(u32) output.\n\nfact3(x) :-\n  fact1(x),\n  !fact1(x),\n  !fact2(x),\n  !fact2(y),\n  y = x + 1.\n\nfact3(x) :-\n  fact1(x),\n  fact2(y),\n  !fact1(y).\n\n//--- expected_ra.out\nsearch fact1 as fact10 do\n  search fact2 as fact21 do\n    if (fact21[0]) ∉ fact1 do\n      project (fact10[0]) into fact3\nsearch fact1 as fact10 do\n  if (fact10[0]) ∉ fact1 do\n    if (fact10[0]) ∉ fact2 do\n      if ((fact10[0] + 1)) ∉ fact2 do\n        project (fact10[0]) into fact3\n//--- expected_eir.out\nexport fn eclair_program_run(*Program) -> Void\n{\n  lower_bound_value = fact1.stack_allocate Value\n  upper_bound_value = fact1.stack_allocate Value\n  lower_bound_value.0 = 0\n  upper_bound_value.0 = 4294967295\n  begin_iter = fact1.stack_allocate Iter\n  end_iter = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter)\n  loop\n  {\n    condition = fact1.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = fact1.iter_current(begin_iter)\n    lower_bound_value_1 = fact2.stack_allocate Value\n    upper_bound_value_1 = fact2.stack_allocate Value\n    lower_bound_value_1.0 = 0\n    upper_bound_value_1.0 = 4294967295\n    begin_iter_1 = fact2.stack_allocate Iter\n    end_iter_1 = fact2.stack_allocate Iter\n    fact2.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1)\n    fact2.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1)\n    loop\n    {\n      condition_1 = fact2.iter_is_equal(begin_iter_1, end_iter_1)\n      if (condition_1)\n      {\n        goto range_query.end_1\n      }\n      current_1 = fact2.iter_current(begin_iter_1)\n      value = fact1.stack_allocate Value\n      value.0 = current_1.0\n      contains_result = fact1.contains(FN_ARG[0].1, value)\n      condition_2 = not contains_result\n      if (condition_2)\n      {\n        value_1 = fact3.stack_allocate Value\n        value_1.0 = current.0\n        fact3.insert(FN_ARG[0].3, value_1)\n      }\n      fact2.iter_next(begin_iter_1)\n    }\n    range_query.end_1:\n    fact1.iter_next(begin_iter)\n  }\n  range_query.end:\n  lower_bound_value_2 = fact1.stack_allocate Value\n  upper_bound_value_2 = fact1.stack_allocate Value\n  lower_bound_value_2.0 = 0\n  upper_bound_value_2.0 = 4294967295\n  begin_iter_2 = fact1.stack_allocate Iter\n  end_iter_2 = fact1.stack_allocate Iter\n  fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_2)\n  fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_2)\n  loop\n  {\n    condition_3 = fact1.iter_is_equal(begin_iter_2, end_iter_2)\n    if (condition_3)\n    {\n      goto range_query.end_2\n    }\n    current_2 = fact1.iter_current(begin_iter_2)\n    value_2 = fact1.stack_allocate Value\n    value_2.0 = current_2.0\n    contains_result_1 = fact1.contains(FN_ARG[0].1, value_2)\n    condition_4 = not contains_result_1\n    if (condition_4)\n    {\n      value_3 = fact2.stack_allocate Value\n      value_3.0 = current_2.0\n      contains_result_2 = fact2.contains(FN_ARG[0].2, value_3)\n      condition_5 = not contains_result_2\n      if (condition_5)\n      {\n        value_4 = fact2.stack_allocate Value\n        value_4.0 = (current_2.0 + 1)\n        contains_result_3 = fact2.contains(FN_ARG[0].2, value_4)\n        condition_6 = not contains_result_3\n        if (condition_6)\n        {\n          value_5 = fact3.stack_allocate Value\n          value_5.0 = current_2.0\n          fact3.insert(FN_ARG[0].3, value_5)\n        }\n      }\n    }\n    fact1.iter_next(begin_iter_2)\n  }\n  range_query.end_2:\n}\n//--- expected_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [1 x i32], i32 1\n  %stack.ptr_1 = alloca [1 x i32], i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_4 = alloca [1 x i32], i32 1\n  %stack.ptr_5 = alloca [1 x i32], i32 1\n  %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_8 = alloca [1 x i32], i32 1\n  %stack.ptr_9 = alloca [1 x i32], i32 1\n  %stack.ptr_10 = alloca [1 x i32], i32 1\n  %stack.ptr_11 = alloca [1 x i32], i32 1\n  %stack.ptr_12 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_14 = alloca [1 x i32], i32 1\n  %stack.ptr_15 = alloca [1 x i32], i32 1\n  %stack.ptr_16 = alloca [1 x i32], i32 1\n  %stack.ptr_17 = alloca [1 x i32], i32 1\n  %0 = getelementptr [1 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 0, ptr %0\n  %1 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 4294967295, ptr %1\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %2, ptr %stack.ptr_0, ptr %stack.ptr_2)\n  %3 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %3, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  br label %loop_0\nloop_0:\n  %4 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3)\n  br i1 %4, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %5 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2)\n  %6 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0\n  store i32 0, ptr %6\n  %7 = getelementptr [1 x i32], ptr %stack.ptr_5, i32 0, i32 0\n  store i32 4294967295, ptr %7\n  %8 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_0(ptr %8, ptr %stack.ptr_4, ptr %stack.ptr_6)\n  %9 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_0(ptr %9, ptr %stack.ptr_5, ptr %stack.ptr_7)\n  br label %loop_1\nloop_1:\n  %10 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_6, ptr %stack.ptr_7)\n  br i1 %10, label %if_1, label %end_if_1\nif_1:\n  br label %range_query.end_1\nend_if_1:\n  %11 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_6)\n  %12 = getelementptr [1 x i32], ptr %stack.ptr_8, i32 0, i32 0\n  %13 = getelementptr [1 x i32], ptr %11, i32 0, i32 0\n  %14 = load i32, ptr %13\n  store i32 %14, ptr %12\n  %15 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %16 = call ccc i1 @eclair_btree_contains_0(ptr %15, ptr %stack.ptr_8)\n  %17 = select i1 %16, i1 0, i1 1\n  br i1 %17, label %if_2, label %end_if_2\nif_2:\n  %18 = getelementptr [1 x i32], ptr %stack.ptr_9, i32 0, i32 0\n  %19 = getelementptr [1 x i32], ptr %5, i32 0, i32 0\n  %20 = load i32, ptr %19\n  store i32 %20, ptr %18\n  %21 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %22 = call ccc i1 @eclair_btree_insert_value_0(ptr %21, ptr %stack.ptr_9)\n  br label %end_if_2\nend_if_2:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_6)\n  br label %loop_1\nrange_query.end_1:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2)\n  br label %loop_0\nrange_query.end:\n  %23 = getelementptr [1 x i32], ptr %stack.ptr_10, i32 0, i32 0\n  store i32 0, ptr %23\n  %24 = getelementptr [1 x i32], ptr %stack.ptr_11, i32 0, i32 0\n  store i32 4294967295, ptr %24\n  %25 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %25, ptr %stack.ptr_10, ptr %stack.ptr_12)\n  %26 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %26, ptr %stack.ptr_11, ptr %stack.ptr_13)\n  br label %loop_2\nloop_2:\n  %27 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_12, ptr %stack.ptr_13)\n  br i1 %27, label %if_3, label %end_if_3\nif_3:\n  br label %range_query.end_2\nend_if_3:\n  %28 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_12)\n  %29 = getelementptr [1 x i32], ptr %stack.ptr_14, i32 0, i32 0\n  %30 = getelementptr [1 x i32], ptr %28, i32 0, i32 0\n  %31 = load i32, ptr %30\n  store i32 %31, ptr %29\n  %32 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %33 = call ccc i1 @eclair_btree_contains_0(ptr %32, ptr %stack.ptr_14)\n  %34 = select i1 %33, i1 0, i1 1\n  br i1 %34, label %if_4, label %end_if_6\nif_4:\n  %35 = getelementptr [1 x i32], ptr %stack.ptr_15, i32 0, i32 0\n  %36 = getelementptr [1 x i32], ptr %28, i32 0, i32 0\n  %37 = load i32, ptr %36\n  store i32 %37, ptr %35\n  %38 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %39 = call ccc i1 @eclair_btree_contains_0(ptr %38, ptr %stack.ptr_15)\n  %40 = select i1 %39, i1 0, i1 1\n  br i1 %40, label %if_5, label %end_if_5\nif_5:\n  %41 = getelementptr [1 x i32], ptr %stack.ptr_16, i32 0, i32 0\n  %42 = getelementptr [1 x i32], ptr %28, i32 0, i32 0\n  %43 = load i32, ptr %42\n  %44 = add i32 %43, 1\n  store i32 %44, ptr %41\n  %45 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %46 = call ccc i1 @eclair_btree_contains_0(ptr %45, ptr %stack.ptr_16)\n  %47 = select i1 %46, i1 0, i1 1\n  br i1 %47, label %if_6, label %end_if_4\nif_6:\n  %48 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0\n  %49 = getelementptr [1 x i32], ptr %28, i32 0, i32 0\n  %50 = load i32, ptr %49\n  store i32 %50, ptr %48\n  %51 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %52 = call ccc i1 @eclair_btree_insert_value_0(ptr %51, ptr %stack.ptr_17)\n  br label %end_if_4\nend_if_4:\n  br label %end_if_5\nend_if_5:\n  br label %end_if_6\nend_if_6:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_12)\n  br label %loop_2\nrange_query.end_2:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/negation_with_wildcards.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: %extract_snippet %t/actual_eir.out \"fn.*eclair_program_run\" > %t/actual_eir_snippet.out\n// RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_run\" > %t/actual_llvm_snippet.out\n// RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out\n\n//--- program.eclair\n@def first(u32).\n@def second(u32, u32).\n@def third(u32, u32) output.\n\nfirst(1).\nsecond(2, 3).\n\nthird(x, x) :-\n  first(x),\n  !second(_, x).\n//--- expected_ra.out\nproject (2, 3) into second\nproject (1) into first\nsearch first as first0 do\n  if (undefined, first0[0]) ∉ second do\n    project (first0[0], first0[0]) into third\n//--- expected_eir.out\nexport fn eclair_program_run(*Program) -> Void\n{\n  value = second.stack_allocate Value\n  value.0 = 2\n  value.1 = 3\n  second.insert(FN_ARG[0].2, value)\n  value_1 = first.stack_allocate Value\n  value_1.0 = 1\n  first.insert(FN_ARG[0].1, value_1)\n  lower_bound_value = first.stack_allocate Value\n  upper_bound_value = first.stack_allocate Value\n  lower_bound_value.0 = 0\n  upper_bound_value.0 = 4294967295\n  begin_iter = first.stack_allocate Iter\n  end_iter = first.stack_allocate Iter\n  first.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter)\n  first.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter)\n  loop\n  {\n    condition = first.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = first.iter_current(begin_iter)\n    lower_bound_value_1 = second.stack_allocate Value\n    lower_bound_value_1.0 = 0\n    lower_bound_value_1.1 = current.0\n    upper_bound_value_1 = second.stack_allocate Value\n    upper_bound_value_1.0 = 4294967295\n    upper_bound_value_1.1 = current.0\n    begin_iter_1 = second.stack_allocate Iter\n    end_iter_1 = second.stack_allocate Iter\n    second.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1)\n    second.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1)\n    condition_1 = second.iter_is_equal(begin_iter_1, end_iter_1)\n    if (condition_1)\n    {\n      value_2 = third.stack_allocate Value\n      value_2.0 = current.0\n      value_2.1 = current.0\n      third.insert(FN_ARG[0].3, value_2)\n    }\n    first.iter_next(begin_iter)\n  }\n  range_query.end:\n}\n//--- expected_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca [1 x i32], i32 1\n  %stack.ptr_2 = alloca [1 x i32], i32 1\n  %stack.ptr_3 = alloca [1 x i32], i32 1\n  %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_6 = alloca [2 x i32], i32 1\n  %stack.ptr_7 = alloca [2 x i32], i32 1\n  %stack.ptr_8 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_9 = alloca %btree_iterator_t_1, i32 1\n  %stack.ptr_10 = alloca [2 x i32], i32 1\n  %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 2, ptr %0\n  %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 3, ptr %1\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %3 = call ccc i1 @eclair_btree_insert_value_1(ptr %2, ptr %stack.ptr_0)\n  %4 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 1, ptr %4\n  %5 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %6 = call ccc i1 @eclair_btree_insert_value_0(ptr %5, ptr %stack.ptr_1)\n  %7 = getelementptr [1 x i32], ptr %stack.ptr_2, i32 0, i32 0\n  store i32 0, ptr %7\n  %8 = getelementptr [1 x i32], ptr %stack.ptr_3, i32 0, i32 0\n  store i32 4294967295, ptr %8\n  %9 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4)\n  %10 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %10, ptr %stack.ptr_3, ptr %stack.ptr_5)\n  br label %loop_0\nloop_0:\n  %11 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_4, ptr %stack.ptr_5)\n  br i1 %11, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %12 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_4)\n  %13 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0\n  store i32 0, ptr %13\n  %14 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1\n  %15 = getelementptr [1 x i32], ptr %12, i32 0, i32 0\n  %16 = load i32, ptr %15\n  store i32 %16, ptr %14\n  %17 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0\n  store i32 4294967295, ptr %17\n  %18 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1\n  %19 = getelementptr [1 x i32], ptr %12, i32 0, i32 0\n  %20 = load i32, ptr %19\n  store i32 %20, ptr %18\n  %21 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_1(ptr %21, ptr %stack.ptr_6, ptr %stack.ptr_8)\n  %22 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_1(ptr %22, ptr %stack.ptr_7, ptr %stack.ptr_9)\n  %23 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_8, ptr %stack.ptr_9)\n  br i1 %23, label %if_1, label %end_if_1\nif_1:\n  %24 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0\n  %25 = getelementptr [1 x i32], ptr %12, i32 0, i32 0\n  %26 = load i32, ptr %25\n  store i32 %26, ptr %24\n  %27 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1\n  %28 = getelementptr [1 x i32], ptr %12, i32 0, i32 0\n  %29 = load i32, ptr %28\n  store i32 %29, ptr %27\n  %30 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %31 = call ccc i1 @eclair_btree_insert_value_2(ptr %30, ptr %stack.ptr_10)\n  br label %end_if_1\nend_if_1:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_4)\n  br label %loop_0\nrange_query.end:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/no_top_level_facts.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"program = \" > %t/actual_eclair_program_type.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_init\" > %t/actual_eclair_program_init_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_destroy\" > %t/actual_eclair_program_destroy_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out\n// RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out\n// RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_add_facts\" > %t/actual_eclair_add_facts_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@eclair_get_facts\" > %t/actual_eclair_get_facts_llvm.out\n// RUN: diff %t/expected_eclair_add_facts_llvm.out %t/actual_eclair_add_facts_llvm.out\n// RUN: diff %t/expected_eclair_get_facts_llvm.out %t/actual_eclair_get_facts_llvm.out\n\n//--- program.eclair\n@def edge(u32, u32) input.\n@def path(u32, u32) output.\n\npath(x, y) :-\n  edge(x, y).\n\npath(x, z) :-\n  edge(x, y),\n  path(y, z).\n\n//--- expected_ra.out\nsearch edge as edge0 do\n  project (edge0[0], edge0[1]) into path\nmerge path delta_path\nloop do\n  purge new_path\n  search edge as edge0 do\n    search delta_path as delta_path1 where (edge0[1] = delta_path1[0]) do\n      if (edge0[0], delta_path1[1]) ∉ path do\n        project (edge0[0], delta_path1[1]) into new_path\n  exit if counttuples(new_path) = 0\n  merge new_path path\n  swap new_path delta_path\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  delta_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n  edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n  new_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n  path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  delta_path.init_empty(program.1)\n  edge.init_empty(program.2)\n  new_path.init_empty(program.3)\n  path.init_empty(program.4)\n  symbol_table.insert(program.0, \"edge\")\n  symbol_table.insert(program.0, \"path\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  delta_path.destroy(FN_ARG[0].1)\n  edge.destroy(FN_ARG[0].2)\n  new_path.destroy(FN_ARG[0].3)\n  path.destroy(FN_ARG[0].4)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  lower_bound_value = edge.stack_allocate Value\n  upper_bound_value = edge.stack_allocate Value\n  lower_bound_value.0 = 0\n  lower_bound_value.1 = 0\n  upper_bound_value.0 = 4294967295\n  upper_bound_value.1 = 4294967295\n  begin_iter = edge.stack_allocate Iter\n  end_iter = edge.stack_allocate Iter\n  edge.iter_lower_bound(FN_ARG[0].2, lower_bound_value, begin_iter)\n  edge.iter_upper_bound(FN_ARG[0].2, upper_bound_value, end_iter)\n  loop\n  {\n    condition = edge.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = edge.iter_current(begin_iter)\n    value = path.stack_allocate Value\n    value.0 = current.0\n    value.1 = current.1\n    path.insert(FN_ARG[0].4, value)\n    edge.iter_next(begin_iter)\n  }\n  range_query.end:\n  begin_iter_1 = path.stack_allocate Iter\n  end_iter_1 = path.stack_allocate Iter\n  path.iter_begin(FN_ARG[0].4, begin_iter_1)\n  path.iter_end(FN_ARG[0].4, end_iter_1)\n  delta_path.insert_range<path[0, 1]>(FN_ARG[0].1, begin_iter_1, end_iter_1)\n  loop\n  {\n    new_path.purge(FN_ARG[0].3)\n    lower_bound_value_1 = edge.stack_allocate Value\n    upper_bound_value_1 = edge.stack_allocate Value\n    lower_bound_value_1.0 = 0\n    lower_bound_value_1.1 = 0\n    upper_bound_value_1.0 = 4294967295\n    upper_bound_value_1.1 = 4294967295\n    begin_iter_2 = edge.stack_allocate Iter\n    end_iter_2 = edge.stack_allocate Iter\n    edge.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_2)\n    edge.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_2)\n    loop\n    {\n      condition_1 = edge.iter_is_equal(begin_iter_2, end_iter_2)\n      if (condition_1)\n      {\n        goto range_query.end_1\n      }\n      current_1 = edge.iter_current(begin_iter_2)\n      lower_bound_value_2 = path.stack_allocate Value\n      upper_bound_value_2 = path.stack_allocate Value\n      lower_bound_value_2.0 = current_1.1\n      lower_bound_value_2.1 = 0\n      upper_bound_value_2.0 = current_1.1\n      upper_bound_value_2.1 = 4294967295\n      begin_iter_3 = path.stack_allocate Iter\n      end_iter_3 = path.stack_allocate Iter\n      delta_path.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_3)\n      delta_path.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_3)\n      loop\n      {\n        condition_2 = delta_path.iter_is_equal(begin_iter_3, end_iter_3)\n        if (condition_2)\n        {\n          goto range_query.end_2\n        }\n        current_2 = delta_path.iter_current(begin_iter_3)\n        value_1 = path.stack_allocate Value\n        value_1.0 = current_1.0\n        value_1.1 = current_2.1\n        contains_result = path.contains(FN_ARG[0].4, value_1)\n        condition_3 = not contains_result\n        if (condition_3)\n        {\n          value_2 = path.stack_allocate Value\n          value_2.0 = current_1.0\n          value_2.1 = current_2.1\n          new_path.insert(FN_ARG[0].3, value_2)\n        }\n        delta_path.iter_next(begin_iter_3)\n      }\n      range_query.end_2:\n      edge.iter_next(begin_iter_2)\n    }\n    range_query.end_1:\n    condition_4 = new_path.is_empty(FN_ARG[0].3)\n    if (condition_4)\n    {\n      goto loop.end\n    }\n    begin_iter_4 = path.stack_allocate Iter\n    end_iter_4 = path.stack_allocate Iter\n    new_path.iter_begin(FN_ARG[0].3, begin_iter_4)\n    new_path.iter_end(FN_ARG[0].3, end_iter_4)\n    path.insert_range<new_path[0, 1]>(FN_ARG[0].4, begin_iter_4, end_iter_4)\n    new_path.swap(FN_ARG[0].3, FN_ARG[0].1)\n  }\n  loop.end:\n}\n//--- expected_eclair_program_type.out\n%program = type {%symbol_table, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0}\n//--- expected_eclair_program_init_llvm.out\ndefine external ccc ptr @eclair_program_init() \"wasm-export-name\"=\"eclair_program_init\" {\nstart:\n  %stack.ptr_0 = alloca %symbol_t, i32 1\n  %stack.ptr_1 = alloca %symbol_t, i32 1\n  %0 = call ccc ptr @malloc(i32 1624)\n  %1 = getelementptr %program, ptr %0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_init(ptr %1)\n  %2 = getelementptr %program, ptr %0, i32 0, i32 1\n  call ccc void @eclair_btree_init_empty_0(ptr %2)\n  %3 = getelementptr %program, ptr %0, i32 0, i32 2\n  call ccc void @eclair_btree_init_empty_0(ptr %3)\n  %4 = getelementptr %program, ptr %0, i32 0, i32 3\n  call ccc void @eclair_btree_init_empty_0(ptr %4)\n  %5 = getelementptr %program, ptr %0, i32 0, i32 4\n  call ccc void @eclair_btree_init_empty_0(ptr %5)\n  %6 = getelementptr %program, ptr %0, i32 0, i32 0\n  %7 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0\n  %8 = zext i32 4 to i64\n  %9 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %9, ptr %7, i64 %8, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %9)\n  %10 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %6, ptr %stack.ptr_0)\n  %11 = getelementptr %program, ptr %0, i32 0, i32 0\n  %12 = getelementptr inbounds [5 x i8], ptr @string_literal_1, i32 0, i32 0\n  %13 = zext i32 4 to i64\n  %14 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %14, ptr %12, i64 %13, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 4, ptr %14)\n  %15 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %11, ptr %stack.ptr_1)\n  ret ptr %0\n}\n//--- expected_eclair_program_destroy_llvm.out\ndefine external ccc void @eclair_program_destroy(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_destroy\" {\nstart:\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_destroy(ptr %0)\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_destroy_0(ptr %1)\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_destroy_0(ptr %2)\n  %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_destroy_0(ptr %3)\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_destroy_0(ptr %4)\n  call ccc void @free(ptr %arg_0)\n  ret void\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca [2 x i32], i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_4 = alloca [2 x i32], i32 1\n  %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_7 = alloca [2 x i32], i32 1\n  %stack.ptr_8 = alloca [2 x i32], i32 1\n  %stack.ptr_9 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_10 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_11 = alloca [2 x i32], i32 1\n  %stack.ptr_12 = alloca [2 x i32], i32 1\n  %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_14 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_15 = alloca [2 x i32], i32 1\n  %stack.ptr_16 = alloca [2 x i32], i32 1\n  %stack.ptr_17 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_18 = alloca %btree_iterator_t_0, i32 1\n  %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 0, ptr %0\n  %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 0, ptr %1\n  %2 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 4294967295, ptr %2\n  %3 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1\n  store i32 4294967295, ptr %3\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_0(ptr %4, ptr %stack.ptr_0, ptr %stack.ptr_2)\n  %5 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_0(ptr %5, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  br label %loop_0\nloop_0:\n  %6 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3)\n  br i1 %6, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %7 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2)\n  %8 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 0\n  %9 = getelementptr [2 x i32], ptr %7, i32 0, i32 0\n  %10 = load i32, ptr %9\n  store i32 %10, ptr %8\n  %11 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 1\n  %12 = getelementptr [2 x i32], ptr %7, i32 0, i32 1\n  %13 = load i32, ptr %12\n  store i32 %13, ptr %11\n  %14 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  %15 = call ccc i1 @eclair_btree_insert_value_0(ptr %14, ptr %stack.ptr_4)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2)\n  br label %loop_0\nrange_query.end:\n  %16 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_begin_0(ptr %16, ptr %stack.ptr_5)\n  %17 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_end_0(ptr %17, ptr %stack.ptr_6)\n  %18 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_insert_range_delta_path_path(ptr %18, ptr %stack.ptr_5, ptr %stack.ptr_6)\n  br label %loop_1\nloop_1:\n  %19 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_clear_0(ptr %19)\n  %20 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0\n  store i32 0, ptr %20\n  %21 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1\n  store i32 0, ptr %21\n  %22 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 0\n  store i32 4294967295, ptr %22\n  %23 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 1\n  store i32 4294967295, ptr %23\n  %24 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_0(ptr %24, ptr %stack.ptr_7, ptr %stack.ptr_9)\n  %25 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_0(ptr %25, ptr %stack.ptr_8, ptr %stack.ptr_10)\n  br label %loop_2\nloop_2:\n  %26 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_9, ptr %stack.ptr_10)\n  br i1 %26, label %if_1, label %end_if_1\nif_1:\n  br label %range_query.end_1\nend_if_1:\n  %27 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_9)\n  %28 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 0\n  %29 = getelementptr [2 x i32], ptr %27, i32 0, i32 1\n  %30 = load i32, ptr %29\n  store i32 %30, ptr %28\n  %31 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 1\n  store i32 0, ptr %31\n  %32 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 0\n  %33 = getelementptr [2 x i32], ptr %27, i32 0, i32 1\n  %34 = load i32, ptr %33\n  store i32 %34, ptr %32\n  %35 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 1\n  store i32 4294967295, ptr %35\n  %36 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %36, ptr %stack.ptr_11, ptr %stack.ptr_13)\n  %37 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %37, ptr %stack.ptr_12, ptr %stack.ptr_14)\n  br label %loop_3\nloop_3:\n  %38 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_13, ptr %stack.ptr_14)\n  br i1 %38, label %if_2, label %end_if_2\nif_2:\n  br label %range_query.end_2\nend_if_2:\n  %39 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_13)\n  %40 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 0\n  %41 = getelementptr [2 x i32], ptr %27, i32 0, i32 0\n  %42 = load i32, ptr %41\n  store i32 %42, ptr %40\n  %43 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 1\n  %44 = getelementptr [2 x i32], ptr %39, i32 0, i32 1\n  %45 = load i32, ptr %44\n  store i32 %45, ptr %43\n  %46 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  %47 = call ccc i1 @eclair_btree_contains_0(ptr %46, ptr %stack.ptr_15)\n  %48 = select i1 %47, i1 0, i1 1\n  br i1 %48, label %if_3, label %end_if_3\nif_3:\n  %49 = getelementptr [2 x i32], ptr %stack.ptr_16, i32 0, i32 0\n  %50 = getelementptr [2 x i32], ptr %27, i32 0, i32 0\n  %51 = load i32, ptr %50\n  store i32 %51, ptr %49\n  %52 = getelementptr [2 x i32], ptr %stack.ptr_16, i32 0, i32 1\n  %53 = getelementptr [2 x i32], ptr %39, i32 0, i32 1\n  %54 = load i32, ptr %53\n  store i32 %54, ptr %52\n  %55 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %56 = call ccc i1 @eclair_btree_insert_value_0(ptr %55, ptr %stack.ptr_16)\n  br label %end_if_3\nend_if_3:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_13)\n  br label %loop_3\nrange_query.end_2:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_9)\n  br label %loop_2\nrange_query.end_1:\n  %57 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %58 = call ccc i1 @eclair_btree_is_empty_0(ptr %57)\n  br i1 %58, label %if_4, label %end_if_4\nif_4:\n  br label %loop.end\nend_if_4:\n  %59 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_begin_0(ptr %59, ptr %stack.ptr_17)\n  %60 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_end_0(ptr %60, ptr %stack.ptr_18)\n  %61 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_insert_range_path_new_path(ptr %61, ptr %stack.ptr_17, ptr %stack.ptr_18)\n  %62 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %63 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_swap_0(ptr %62, ptr %63)\n  br label %loop_1\nloop.end:\n  ret void\n}\n//--- expected_eclair_add_facts_llvm.out\ndefine external ccc void @eclair_add_facts(ptr %eclair_program_0, i32 %fact_type_0, ptr %memory_0, i32 %fact_count_0) \"wasm-export-name\"=\"eclair_add_facts\" {\nstart:\n  switch i32 %fact_type_0, label %switch.default_0 [i32 0, label %edge_0]\nedge_0:\n  %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 2\n  br label %for_begin_0\nfor_begin_0:\n  %1 = phi i32 [0, %edge_0], [%5, %for_body_0]\n  %2 = icmp ult i32 %1, %fact_count_0\n  br i1 %2, label %for_body_0, label %for_end_0\nfor_body_0:\n  %3 = getelementptr [2 x i32], ptr %memory_0, i32 %1\n  %4 = call ccc i1 @eclair_btree_insert_value_0(ptr %0, ptr %3)\n  %5 = add i32 1, %1\n  br label %for_begin_0\nfor_end_0:\n  br label %end_0\nswitch.default_0:\n  ret void\nend_0:\n  ret void\n}\n//--- expected_eclair_get_facts_llvm.out\ndefine external ccc ptr @eclair_get_facts(ptr %eclair_program_0, i32 %fact_type_0) \"wasm-export-name\"=\"eclair_get_facts\" {\nstart:\n  %stack.ptr_0 = alloca i32, i32 1\n  %stack.ptr_1 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  switch i32 %fact_type_0, label %switch.default_0 [i32 1, label %path_0]\npath_0:\n  %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 4\n  %1 = call ccc i64 @eclair_btree_size_0(ptr %0)\n  %2 = trunc i64 %1 to i32\n  %3 = mul i32 %2, 8\n  %4 = call ccc ptr @malloc(i32 %3)\n  store i32 0, ptr %stack.ptr_0\n  call ccc void @eclair_btree_begin_0(ptr %0, ptr %stack.ptr_1)\n  call ccc void @eclair_btree_end_0(ptr %0, ptr %stack.ptr_2)\n  br label %while_begin_0\nwhile_begin_0:\n  %5 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_1, ptr %stack.ptr_2)\n  %6 = select i1 %5, i1 0, i1 1\n  br i1 %6, label %while_body_0, label %while_end_0\nwhile_body_0:\n  %7 = load i32, ptr %stack.ptr_0\n  %8 = getelementptr [2 x i32], ptr %4, i32 %7\n  %9 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_1)\n  %10 = getelementptr [2 x i32], ptr %9, i32 0\n  %11 = load [2 x i32], ptr %10\n  %12 = getelementptr [2 x i32], ptr %8, i32 0\n  store [2 x i32] %11, ptr %12\n  %13 = add i32 %7, 1\n  store i32 %13, ptr %stack.ptr_0\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_1)\n  br label %while_begin_0\nwhile_end_0:\n  ret ptr %4\nswitch.default_0:\n  ret ptr zeroinitializer\n}\n"
  },
  {
    "path": "tests/lowering/recursive_mix_of_rules.eclair",
    "content": "\n// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n//--- program.eclair\n@def edge(u32, u32) input.\n@def reachable(u32) output.\n\nreachable(x) :- edge(x, _).\nreachable(x) :- edge(_, x).\n\nreachable(x) :-\n  edge(x, y),\n  reachable(y).\n\nreachable(x) :-\n  edge(y, x),\n  reachable(y).\n\n//--- expected_ra.out\nsearch edge as edge0 do\n  project (edge0[1]) into reachable\nsearch edge as edge0 do\n  project (edge0[0]) into reachable\nmerge reachable delta_reachable\nloop do\n  purge new_reachable\n  parallel do\n    search edge as edge0 do\n      if (edge0[1]) ∉ reachable do\n        search delta_reachable as delta_reachable1 where (edge0[0] = delta_reachable1[0]) do\n          project (edge0[1]) into new_reachable\n    search edge as edge0 do\n      if (edge0[0]) ∉ reachable do\n        search delta_reachable as delta_reachable1 where (edge0[1] = delta_reachable1[0]) do\n          project (edge0[0]) into new_reachable\n  exit if counttuples(new_reachable) = 0\n  merge new_reachable reachable\n  swap new_reachable delta_reachable\n"
  },
  {
    "path": "tests/lowering/single_non_recursive_rule.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"program = \" > %t/actual_eclair_program_type.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_init\" > %t/actual_eclair_program_init_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_destroy\" > %t/actual_eclair_program_destroy_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out\n// RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out\n// RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n//--- program.eclair\n@def edge(u32, u32).\n@def path(u32, u32) output.\n\nedge(1,2).\n\npath(x,y) :-\n  edge(x,y).\n\n//--- expected_ra.out\nproject (1, 2) into edge\nsearch edge as edge0 do\n  project (edge0[0], edge0[1]) into path\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n  path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  edge.init_empty(program.1)\n  path.init_empty(program.2)\n  symbol_table.insert(program.0, \"edge\")\n  symbol_table.insert(program.0, \"path\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  edge.destroy(FN_ARG[0].1)\n  path.destroy(FN_ARG[0].2)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  value = edge.stack_allocate Value\n  value.0 = 1\n  value.1 = 2\n  edge.insert(FN_ARG[0].1, value)\n  lower_bound_value = edge.stack_allocate Value\n  upper_bound_value = edge.stack_allocate Value\n  lower_bound_value.0 = 0\n  lower_bound_value.1 = 0\n  upper_bound_value.0 = 4294967295\n  upper_bound_value.1 = 4294967295\n  begin_iter = edge.stack_allocate Iter\n  end_iter = edge.stack_allocate Iter\n  edge.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter)\n  edge.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter)\n  loop\n  {\n    condition = edge.iter_is_equal(begin_iter, end_iter)\n    if (condition)\n    {\n      goto range_query.end\n    }\n    current = edge.iter_current(begin_iter)\n    value_1 = path.stack_allocate Value\n    value_1.0 = current.0\n    value_1.1 = current.1\n    path.insert(FN_ARG[0].2, value_1)\n    edge.iter_next(begin_iter)\n  }\n  range_query.end:\n}\n//--- expected_eclair_program_type.out\n%program = type {%symbol_table, %btree_t_0, %btree_t_0}\n//--- expected_eclair_program_init_llvm.out\ndefine external ccc ptr @eclair_program_init() \"wasm-export-name\"=\"eclair_program_init\" {\nstart:\n  %stack.ptr_0 = alloca %symbol_t, i32 1\n  %stack.ptr_1 = alloca %symbol_t, i32 1\n  %0 = call ccc ptr @malloc(i32 1592)\n  %1 = getelementptr %program, ptr %0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_init(ptr %1)\n  %2 = getelementptr %program, ptr %0, i32 0, i32 1\n  call ccc void @eclair_btree_init_empty_0(ptr %2)\n  %3 = getelementptr %program, ptr %0, i32 0, i32 2\n  call ccc void @eclair_btree_init_empty_0(ptr %3)\n  %4 = getelementptr %program, ptr %0, i32 0, i32 0\n  %5 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0\n  %6 = zext i32 4 to i64\n  %7 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %7, ptr %5, i64 %6, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %7)\n  %8 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %4, ptr %stack.ptr_0)\n  %9 = getelementptr %program, ptr %0, i32 0, i32 0\n  %10 = getelementptr inbounds [5 x i8], ptr @string_literal_1, i32 0, i32 0\n  %11 = zext i32 4 to i64\n  %12 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %12, ptr %10, i64 %11, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 4, ptr %12)\n  %13 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %9, ptr %stack.ptr_1)\n  ret ptr %0\n}\n//--- expected_eclair_program_destroy_llvm.out\ndefine external ccc void @eclair_program_destroy(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_destroy\" {\nstart:\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_destroy(ptr %0)\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_destroy_0(ptr %1)\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_destroy_0(ptr %2)\n  call ccc void @free(ptr %arg_0)\n  ret void\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca [2 x i32], i32 1\n  %stack.ptr_2 = alloca [2 x i32], i32 1\n  %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_5 = alloca [2 x i32], i32 1\n  %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 1, ptr %0\n  %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 2, ptr %1\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %3 = call ccc i1 @eclair_btree_insert_value_0(ptr %2, ptr %stack.ptr_0)\n  %4 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 0, ptr %4\n  %5 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1\n  store i32 0, ptr %5\n  %6 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 0\n  store i32 4294967295, ptr %6\n  %7 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 1\n  store i32 4294967295, ptr %7\n  %8 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %8, ptr %stack.ptr_1, ptr %stack.ptr_3)\n  %9 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4)\n  br label %loop_0\nloop_0:\n  %10 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_3, ptr %stack.ptr_4)\n  br i1 %10, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %11 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_3)\n  %12 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0\n  %13 = getelementptr [2 x i32], ptr %11, i32 0, i32 0\n  %14 = load i32, ptr %13\n  store i32 %14, ptr %12\n  %15 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1\n  %16 = getelementptr [2 x i32], ptr %11, i32 0, i32 1\n  %17 = load i32, ptr %16\n  store i32 %17, ptr %15\n  %18 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %19 = call ccc i1 @eclair_btree_insert_value_0(ptr %18, ptr %stack.ptr_5)\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_3)\n  br label %loop_0\nrange_query.end:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/single_recursive_rule.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// NOTE: program for now also contains delta_ and new_ relations,\n// probably it's more efficient to move these to the stack (but left out of scope for now)\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"program = \" > %t/actual_eclair_program_type.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_init\" > %t/actual_eclair_program_init_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_destroy\" > %t/actual_eclair_program_destroy_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out\n// RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out\n// RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n//--- program.eclair\n@def edge(u32, u32).\n@def path(u32, u32) output.\n\nedge(1,2).\n\npath(x, y) :-\n  edge(x, z),\n  path(z, y).\n\n//--- expected_ra.out\nproject (1, 2) into edge\nmerge path delta_path\nloop do\n  purge new_path\n  search edge as edge0 do\n    search delta_path as delta_path1 where (edge0[1] = delta_path1[0]) do\n      if (edge0[0], delta_path1[1]) ∉ path do\n        project (edge0[0], delta_path1[1]) into new_path\n  exit if counttuples(new_path) = 0\n  merge new_path path\n  swap new_path delta_path\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  delta_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n  edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n  new_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n  path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  delta_path.init_empty(program.1)\n  edge.init_empty(program.2)\n  new_path.init_empty(program.3)\n  path.init_empty(program.4)\n  symbol_table.insert(program.0, \"edge\")\n  symbol_table.insert(program.0, \"path\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  delta_path.destroy(FN_ARG[0].1)\n  edge.destroy(FN_ARG[0].2)\n  new_path.destroy(FN_ARG[0].3)\n  path.destroy(FN_ARG[0].4)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  value = edge.stack_allocate Value\n  value.0 = 1\n  value.1 = 2\n  edge.insert(FN_ARG[0].2, value)\n  begin_iter = path.stack_allocate Iter\n  end_iter = path.stack_allocate Iter\n  path.iter_begin(FN_ARG[0].4, begin_iter)\n  path.iter_end(FN_ARG[0].4, end_iter)\n  delta_path.insert_range<path[0, 1]>(FN_ARG[0].1, begin_iter, end_iter)\n  loop\n  {\n    new_path.purge(FN_ARG[0].3)\n    lower_bound_value = edge.stack_allocate Value\n    upper_bound_value = edge.stack_allocate Value\n    lower_bound_value.0 = 0\n    lower_bound_value.1 = 0\n    upper_bound_value.0 = 4294967295\n    upper_bound_value.1 = 4294967295\n    begin_iter_1 = edge.stack_allocate Iter\n    end_iter_1 = edge.stack_allocate Iter\n    edge.iter_lower_bound(FN_ARG[0].2, lower_bound_value, begin_iter_1)\n    edge.iter_upper_bound(FN_ARG[0].2, upper_bound_value, end_iter_1)\n    loop\n    {\n      condition = edge.iter_is_equal(begin_iter_1, end_iter_1)\n      if (condition)\n      {\n        goto range_query.end\n      }\n      current = edge.iter_current(begin_iter_1)\n      lower_bound_value_1 = path.stack_allocate Value\n      upper_bound_value_1 = path.stack_allocate Value\n      lower_bound_value_1.0 = current.1\n      lower_bound_value_1.1 = 0\n      upper_bound_value_1.0 = current.1\n      upper_bound_value_1.1 = 4294967295\n      begin_iter_2 = path.stack_allocate Iter\n      end_iter_2 = path.stack_allocate Iter\n      delta_path.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_2)\n      delta_path.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_2)\n      loop\n      {\n        condition_1 = delta_path.iter_is_equal(begin_iter_2, end_iter_2)\n        if (condition_1)\n        {\n          goto range_query.end_1\n        }\n        current_1 = delta_path.iter_current(begin_iter_2)\n        value_1 = path.stack_allocate Value\n        value_1.0 = current.0\n        value_1.1 = current_1.1\n        contains_result = path.contains(FN_ARG[0].4, value_1)\n        condition_2 = not contains_result\n        if (condition_2)\n        {\n          value_2 = path.stack_allocate Value\n          value_2.0 = current.0\n          value_2.1 = current_1.1\n          new_path.insert(FN_ARG[0].3, value_2)\n        }\n        delta_path.iter_next(begin_iter_2)\n      }\n      range_query.end_1:\n      edge.iter_next(begin_iter_1)\n    }\n    range_query.end:\n    condition_3 = new_path.is_empty(FN_ARG[0].3)\n    if (condition_3)\n    {\n      goto loop.end\n    }\n    begin_iter_3 = path.stack_allocate Iter\n    end_iter_3 = path.stack_allocate Iter\n    new_path.iter_begin(FN_ARG[0].3, begin_iter_3)\n    new_path.iter_end(FN_ARG[0].3, end_iter_3)\n    path.insert_range<new_path[0, 1]>(FN_ARG[0].4, begin_iter_3, end_iter_3)\n    new_path.swap(FN_ARG[0].3, FN_ARG[0].1)\n  }\n  loop.end:\n}\n//--- expected_eclair_program_type.out\n%program = type {%symbol_table, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0}\n//--- expected_eclair_program_init_llvm.out\ndefine external ccc ptr @eclair_program_init() \"wasm-export-name\"=\"eclair_program_init\" {\nstart:\n  %stack.ptr_0 = alloca %symbol_t, i32 1\n  %stack.ptr_1 = alloca %symbol_t, i32 1\n  %0 = call ccc ptr @malloc(i32 1624)\n  %1 = getelementptr %program, ptr %0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_init(ptr %1)\n  %2 = getelementptr %program, ptr %0, i32 0, i32 1\n  call ccc void @eclair_btree_init_empty_0(ptr %2)\n  %3 = getelementptr %program, ptr %0, i32 0, i32 2\n  call ccc void @eclair_btree_init_empty_0(ptr %3)\n  %4 = getelementptr %program, ptr %0, i32 0, i32 3\n  call ccc void @eclair_btree_init_empty_0(ptr %4)\n  %5 = getelementptr %program, ptr %0, i32 0, i32 4\n  call ccc void @eclair_btree_init_empty_0(ptr %5)\n  %6 = getelementptr %program, ptr %0, i32 0, i32 0\n  %7 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0\n  %8 = zext i32 4 to i64\n  %9 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %9, ptr %7, i64 %8, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %9)\n  %10 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %6, ptr %stack.ptr_0)\n  %11 = getelementptr %program, ptr %0, i32 0, i32 0\n  %12 = getelementptr inbounds [5 x i8], ptr @string_literal_1, i32 0, i32 0\n  %13 = zext i32 4 to i64\n  %14 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %14, ptr %12, i64 %13, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 4, ptr %14)\n  %15 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %11, ptr %stack.ptr_1)\n  ret ptr %0\n}\n//--- expected_eclair_program_destroy_llvm.out\ndefine external ccc void @eclair_program_destroy(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_destroy\" {\nstart:\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_destroy(ptr %0)\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_destroy_0(ptr %1)\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_destroy_0(ptr %2)\n  %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_destroy_0(ptr %3)\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_destroy_0(ptr %4)\n  call ccc void @free(ptr %arg_0)\n  ret void\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [2 x i32], i32 1\n  %stack.ptr_1 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_3 = alloca [2 x i32], i32 1\n  %stack.ptr_4 = alloca [2 x i32], i32 1\n  %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_7 = alloca [2 x i32], i32 1\n  %stack.ptr_8 = alloca [2 x i32], i32 1\n  %stack.ptr_9 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_10 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_11 = alloca [2 x i32], i32 1\n  %stack.ptr_12 = alloca [2 x i32], i32 1\n  %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1\n  %stack.ptr_14 = alloca %btree_iterator_t_0, i32 1\n  %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 1, ptr %0\n  %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 2, ptr %1\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %3 = call ccc i1 @eclair_btree_insert_value_0(ptr %2, ptr %stack.ptr_0)\n  %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_begin_0(ptr %4, ptr %stack.ptr_1)\n  %5 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_end_0(ptr %5, ptr %stack.ptr_2)\n  %6 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_insert_range_delta_path_path(ptr %6, ptr %stack.ptr_1, ptr %stack.ptr_2)\n  br label %loop_0\nloop_0:\n  %7 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_clear_0(ptr %7)\n  %8 = getelementptr [2 x i32], ptr %stack.ptr_3, i32 0, i32 0\n  store i32 0, ptr %8\n  %9 = getelementptr [2 x i32], ptr %stack.ptr_3, i32 0, i32 1\n  store i32 0, ptr %9\n  %10 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 0\n  store i32 4294967295, ptr %10\n  %11 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 1\n  store i32 4294967295, ptr %11\n  %12 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_lower_bound_0(ptr %12, ptr %stack.ptr_3, ptr %stack.ptr_5)\n  %13 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_upper_bound_0(ptr %13, ptr %stack.ptr_4, ptr %stack.ptr_6)\n  br label %loop_1\nloop_1:\n  %14 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_5, ptr %stack.ptr_6)\n  br i1 %14, label %if_0, label %end_if_0\nif_0:\n  br label %range_query.end\nend_if_0:\n  %15 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_5)\n  %16 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0\n  %17 = getelementptr [2 x i32], ptr %15, i32 0, i32 1\n  %18 = load i32, ptr %17\n  store i32 %18, ptr %16\n  %19 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1\n  store i32 0, ptr %19\n  %20 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 0\n  %21 = getelementptr [2 x i32], ptr %15, i32 0, i32 1\n  %22 = load i32, ptr %21\n  store i32 %22, ptr %20\n  %23 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 1\n  store i32 4294967295, ptr %23\n  %24 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_lower_bound_0(ptr %24, ptr %stack.ptr_7, ptr %stack.ptr_9)\n  %25 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_upper_bound_0(ptr %25, ptr %stack.ptr_8, ptr %stack.ptr_10)\n  br label %loop_2\nloop_2:\n  %26 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_9, ptr %stack.ptr_10)\n  br i1 %26, label %if_1, label %end_if_1\nif_1:\n  br label %range_query.end_1\nend_if_1:\n  %27 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_9)\n  %28 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 0\n  %29 = getelementptr [2 x i32], ptr %15, i32 0, i32 0\n  %30 = load i32, ptr %29\n  store i32 %30, ptr %28\n  %31 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 1\n  %32 = getelementptr [2 x i32], ptr %27, i32 0, i32 1\n  %33 = load i32, ptr %32\n  store i32 %33, ptr %31\n  %34 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  %35 = call ccc i1 @eclair_btree_contains_0(ptr %34, ptr %stack.ptr_11)\n  %36 = select i1 %35, i1 0, i1 1\n  br i1 %36, label %if_2, label %end_if_2\nif_2:\n  %37 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 0\n  %38 = getelementptr [2 x i32], ptr %15, i32 0, i32 0\n  %39 = load i32, ptr %38\n  store i32 %39, ptr %37\n  %40 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 1\n  %41 = getelementptr [2 x i32], ptr %27, i32 0, i32 1\n  %42 = load i32, ptr %41\n  store i32 %42, ptr %40\n  %43 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %44 = call ccc i1 @eclair_btree_insert_value_0(ptr %43, ptr %stack.ptr_12)\n  br label %end_if_2\nend_if_2:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_9)\n  br label %loop_2\nrange_query.end_1:\n  call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_5)\n  br label %loop_1\nrange_query.end:\n  %45 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %46 = call ccc i1 @eclair_btree_is_empty_0(ptr %45)\n  br i1 %46, label %if_3, label %end_if_3\nif_3:\n  br label %loop.end\nend_if_3:\n  %47 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_begin_0(ptr %47, ptr %stack.ptr_13)\n  %48 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  call ccc void @eclair_btree_end_0(ptr %48, ptr %stack.ptr_14)\n  %49 = getelementptr %program, ptr %arg_0, i32 0, i32 4\n  call ccc void @eclair_btree_insert_range_path_new_path(ptr %49, ptr %stack.ptr_13, ptr %stack.ptr_14)\n  %50 = getelementptr %program, ptr %arg_0, i32 0, i32 3\n  %51 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_swap_0(ptr %50, ptr %51)\n  br label %loop_0\nloop.end:\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/stratification.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit ra %t/program2.eclair > %t/actual_ra2.out\n// RUN: diff %t/expected_ra2.out %t/actual_ra2.out\n\n//--- program.eclair\n@def a(u32) input.\n@def b(u32) input.\n@def c(u32).\n@def d(u32).\n@def e(u32) output.\n@def f(u32, u32) input.\n@def g(u32, u32) output.\n\nc(x) :-\n  a(x),\n  !b(x).\n\nd(x) :-\n  b(x).\n\ne(x) :-\n  !c(x),\n  d(x).\n\ng(x, y) :-\n  f(x, y).\n\ng(x, z) :-\n  f(x, y),\n  g(y, z),\n  !d(x).\n\n//--- expected_ra.out\nsearch f as f0 do\n  project (f0[0], f0[1]) into g\nsearch b as b0 do\n  project (b0[0]) into d\nmerge g delta_g\nloop do\n  purge new_g\n  search f as f0 do\n    if (f0[0]) ∉ d do\n      search delta_g as delta_g1 where (f0[1] = delta_g1[0]) do\n        if (f0[0], delta_g1[1]) ∉ g do\n          project (f0[0], delta_g1[1]) into new_g\n  exit if counttuples(new_g) = 0\n  merge new_g g\n  swap new_g delta_g\nsearch a as a0 do\n  if (a0[0]) ∉ b do\n    project (a0[0]) into c\nsearch d as d0 do\n  if (d0[0]) ∉ c do\n    project (d0[0]) into e\n//--- program2.eclair\n@def a(u32, string) input.\n@def b(u32, string, u32, u32) input.\n@def c(u32, string) input.\n@def d(u32, u32) input.\n@def e(u32, u32).\n@def f(u32, string) output.\n\ne(x, y) :-\n  d(x, y),\n  c(y, _).\n\ne(x, y) :-\n  e(x, z),\n  e(x, a),\n  b(y, _, z, a).\n\nf(x, y) :-\n  e(x, z),\n  a(z, y).\n\n//--- expected_ra2.out\nsearch d as d0 do\n  search c as c1 do\n    if d0[1] = c1[0] do\n      project (d0[0], d0[1]) into e\nmerge e delta_e\nloop do\n  purge new_e\n  parallel do\n    search delta_e as delta_e0 do\n      search e as e1 do\n        search b as b2 do\n          if (delta_e0[0], e1[1]) ∉ delta_e do\n            if e1[1] = b2[3] do\n              if delta_e0[0] = e1[0] do\n                if delta_e0[1] = b2[2] do\n                  if (delta_e0[0], b2[0]) ∉ e do\n                    project (delta_e0[0], b2[0]) into new_e\n    search e as e0 do\n      search delta_e as delta_e1 do\n        search b as b2 do\n          if delta_e1[1] = b2[3] do\n            if e0[0] = delta_e1[0] do\n              if e0[1] = b2[2] do\n                if (e0[0], b2[0]) ∉ e do\n                  project (e0[0], b2[0]) into new_e\n  exit if counttuples(new_e) = 0\n  merge new_e e\n  swap new_e delta_e\nsearch e as e0 do\n  search a as a1 do\n    if e0[1] = a1[0] do\n      project (e0[0], a1[1]) into f\n"
  },
  {
    "path": "tests/lowering/top_level_facts.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out\n// RUN: diff %t/expected_ra.out %t/actual_ra.out\n\n// RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out\n// RUN: diff %t/expected_eir.out %t/actual_eir.out\n\n// RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_init\" > %t/actual_eclair_program_init_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_destroy\" > %t/actual_eclair_program_destroy_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"@eclair_program_run\" > %t/actual_eclair_program_run_llvm.out\n// RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out\n// RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out\n// RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out\n\n//--- program.eclair\n@def edge(u32, u32) output.\n@def another(u32, u32, u32) output.\n\nedge(1, 2).\nedge(2, 3).\n\nanother(1,2,3).\n\n//--- expected_ra.out\nproject (1, 2, 3) into another\nproject (2, 3) into edge\nproject (1, 2) into edge\n//--- expected_eir.out\ndeclare_type Program\n{\n  symbol_table\n  another btree(num_columns=3, index=[0,1,2], block_size=256, search_type=linear)\n  edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear)\n}\n\nexport fn eclair_program_init() -> *Program\n{\n  program = heap_allocate_program\n  symbol_table.init(program.0)\n  another.init_empty(program.1)\n  edge.init_empty(program.2)\n  symbol_table.insert(program.0, \"edge\")\n  symbol_table.insert(program.0, \"another\")\n  return program\n}\n\nexport fn eclair_program_destroy(*Program) -> Void\n{\n  symbol_table.destroy(FN_ARG[0].0)\n  another.destroy(FN_ARG[0].1)\n  edge.destroy(FN_ARG[0].2)\n  free_program(FN_ARG[0])\n}\n\nexport fn eclair_program_run(*Program) -> Void\n{\n  value = another.stack_allocate Value\n  value.0 = 1\n  value.1 = 2\n  value.2 = 3\n  another.insert(FN_ARG[0].1, value)\n  value_1 = edge.stack_allocate Value\n  value_1.0 = 2\n  value_1.1 = 3\n  edge.insert(FN_ARG[0].2, value_1)\n  value_2 = edge.stack_allocate Value\n  value_2.0 = 1\n  value_2.1 = 2\n  edge.insert(FN_ARG[0].2, value_2)\n}\n//--- expected_eclair_program_init_llvm.out\ndefine external ccc ptr @eclair_program_init() \"wasm-export-name\"=\"eclair_program_init\" {\nstart:\n  %stack.ptr_0 = alloca %symbol_t, i32 1\n  %stack.ptr_1 = alloca %symbol_t, i32 1\n  %0 = call ccc ptr @malloc(i32 1592)\n  %1 = getelementptr %program, ptr %0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_init(ptr %1)\n  %2 = getelementptr %program, ptr %0, i32 0, i32 1\n  call ccc void @eclair_btree_init_empty_0(ptr %2)\n  %3 = getelementptr %program, ptr %0, i32 0, i32 2\n  call ccc void @eclair_btree_init_empty_1(ptr %3)\n  %4 = getelementptr %program, ptr %0, i32 0, i32 0\n  %5 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0\n  %6 = zext i32 4 to i64\n  %7 = call ccc ptr @malloc(i32 4)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %7, ptr %5, i64 %6, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %7)\n  %8 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %4, ptr %stack.ptr_0)\n  %9 = getelementptr %program, ptr %0, i32 0, i32 0\n  %10 = getelementptr inbounds [8 x i8], ptr @string_literal_1, i32 0, i32 0\n  %11 = zext i32 7 to i64\n  %12 = call ccc ptr @malloc(i32 7)\n  call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %12, ptr %10, i64 %11, i1 0)\n  call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 7, ptr %12)\n  %13 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %9, ptr %stack.ptr_1)\n  ret ptr %0\n}\n//--- expected_eclair_program_destroy_llvm.out\ndefine external ccc void @eclair_program_destroy(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_destroy\" {\nstart:\n  %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0\n  call ccc void @eclair_symbol_table_destroy(ptr %0)\n  %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  call ccc void @eclair_btree_destroy_0(ptr %1)\n  %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  call ccc void @eclair_btree_destroy_1(ptr %2)\n  call ccc void @free(ptr %arg_0)\n  ret void\n}\n//--- expected_eclair_program_run_llvm.out\ndefine external ccc void @eclair_program_run(ptr %arg_0) \"wasm-export-name\"=\"eclair_program_run\" {\nstart:\n  %stack.ptr_0 = alloca [3 x i32], i32 1\n  %stack.ptr_1 = alloca [2 x i32], i32 1\n  %stack.ptr_2 = alloca [2 x i32], i32 1\n  %0 = getelementptr [3 x i32], ptr %stack.ptr_0, i32 0, i32 0\n  store i32 1, ptr %0\n  %1 = getelementptr [3 x i32], ptr %stack.ptr_0, i32 0, i32 1\n  store i32 2, ptr %1\n  %2 = getelementptr [3 x i32], ptr %stack.ptr_0, i32 0, i32 2\n  store i32 3, ptr %2\n  %3 = getelementptr %program, ptr %arg_0, i32 0, i32 1\n  %4 = call ccc i1 @eclair_btree_insert_value_0(ptr %3, ptr %stack.ptr_0)\n  %5 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0\n  store i32 2, ptr %5\n  %6 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1\n  store i32 3, ptr %6\n  %7 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %8 = call ccc i1 @eclair_btree_insert_value_1(ptr %7, ptr %stack.ptr_1)\n  %9 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 0\n  store i32 1, ptr %9\n  %10 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 1\n  store i32 2, ptr %10\n  %11 = getelementptr %program, ptr %arg_0, i32 0, i32 2\n  %12 = call ccc i1 @eclair_btree_insert_value_1(ptr %11, ptr %stack.ptr_2)\n  ret void\n}\n"
  },
  {
    "path": "tests/lowering/wasm_codegen.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile --target wasm32 --emit llvm %t/program.eclair > %t/actual_llvm.out\n// RUN: %extract_snippet %t/actual_llvm.out \"define.*@memcmp_wasm32\" > %t/actual_wasm_memcmp.out\n// RUN: diff %t/expected_wasm_memcmp.out %t/actual_wasm_memcmp.out\n\n//--- program.eclair\n@def edge(u32, u32) output.\n@def another(u32, u32, u32) output.\n\nedge(1, 2).\n\nanother(1,2,3).\n\n//--- expected_wasm_memcmp.out\ndefine external ccc i32 @memcmp_wasm32(ptr %array1_0, ptr %array2_0, i64 %byte_count_0) {\nstart:\n  %0 = udiv i64 %byte_count_0, 8\n  %1 = and i64 %byte_count_0, 7\n  br label %for_begin_0\nfor_begin_0:\n  %2 = phi i64 [0, %start], [%9, %end_if_0]\n  %3 = icmp ult i64 %2, %0\n  br i1 %3, label %for_body_0, label %for_end_0\nfor_body_0:\n  %4 = getelementptr i64, ptr %array1_0, i64 %2\n  %5 = getelementptr i64, ptr %array2_0, i64 %2\n  %6 = load i64, ptr %4\n  %7 = load i64, ptr %5\n  %8 = icmp ne i64 %6, %7\n  br i1 %8, label %if_0, label %end_if_0\nif_0:\n  ret i32 1\nend_if_0:\n  %9 = add i64 1, %2\n  br label %for_begin_0\nfor_end_0:\n  %10 = mul i64 %0, 8\n  br label %for_begin_1\nfor_begin_1:\n  %11 = phi i64 [0, %for_end_0], [%19, %end_if_1]\n  %12 = icmp ult i64 %11, %1\n  br i1 %12, label %for_body_1, label %for_end_1\nfor_body_1:\n  %13 = add i64 %11, %10\n  %14 = getelementptr i8, ptr %array1_0, i64 %13\n  %15 = getelementptr i8, ptr %array2_0, i64 %13\n  %16 = load i8, ptr %14\n  %17 = load i8, ptr %15\n  %18 = icmp ne i8 %16, %17\n  br i1 %18, label %if_1, label %end_if_1\nif_1:\n  ret i32 1\nend_if_1:\n  %19 = add i64 1, %11\n  br label %for_begin_1\nfor_end_1:\n  ret i32 0\n}\n"
  },
  {
    "path": "tests/parser/error_recovery.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile --emit ra-transformed %t/program.eclair 2> %t/actual_output\n// RUN: diff -w %t/expected_output %t/actual_output\n\n//--- program.eclair\n@def edge(u32,).\n@def path(u32, u32) output.\n@def broken.\n\nedge(1, 2).\n\npath(x, y) :\n  edge(x, y).\n\npath(x, y) :-\n  edge(x, z),\n  path(z, y).\n\nedge(2, 3).\n\n//--- expected_output\n[error]: Failed to parse file\n     ╭──▶ TEST_DIR/program.eclair@1:15-1:16\n     │\n   1 │ @def edge(u32,).\n     •               ┬\n     •               ├╸ unexpected \").<newline>@de\"\n     •               ╰╸ expecting field name or type\n─────╯\n\n[error]: Failed to parse file\n     ╭──▶ TEST_DIR/program.eclair@3:12-3:13\n     │\n   3 │ @def broken.\n     •            ┬\n     •            ├╸ unexpected '.'\n     •            ╰╸ expecting '(' or rest of identifier\n─────╯\n\n[error]: Failed to parse file\n     ╭──▶ TEST_DIR/program.eclair@7:12-7:13\n     │\n   7 │ path(x, y) :\n     •            ┬\n     •            ├╸ unexpected \":<newline>\"\n     •            ╰╸ expecting \":-\" or '.'\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@10:6-10:7\n     │\n  10 │ ╭┤ path(x, y) :-\n     • │       ┬\n     • │       ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  11 │ │    edge(x, z),\n  12 │ ├┤   path(z, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'x'.\n     •\n     │ Hint: Use the variable 'x' as an argument in a relation.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@5:1-5:12\n     │\n   5 │ edge(1, 2).\n     • ┬──────────\n     • ╰╸ Could not find a type definition for 'edge'.\n     •\n     │ Hint: Add a type definition for 'edge'.\n     │ Hint: Add an extern definition for 'edge'.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@11:3-11:13\n     │\n  11 │   edge(x, z),\n     •   ┬─────────\n     •   ╰╸ Could not find a type definition for 'edge'.\n     •\n     │ Hint: Add a type definition for 'edge'.\n     │ Hint: Add an extern definition for 'edge'.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@14:1-14:12\n     │\n  14 │ edge(2, 3).\n     • ┬──────────\n     • ╰╸ Could not find a type definition for 'edge'.\n     •\n     │ Hint: Add a type definition for 'edge'.\n     │ Hint: Add an extern definition for 'edge'.\n─────╯\n"
  },
  {
    "path": "tests/parser/file_not_found.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile foo.eclair 2> %t/actual_output\n// RUN: diff -w %t/expected_output %t/actual_output\n\n//--- expected_output\nFile not found: foo.eclair.\n"
  },
  {
    "path": "tests/parser/valid.eclair",
    "content": "// RUN: %eclair compile --emit ra-transformed %s | FileCheck %s\n// CHECK: project\n\n// u32\n@def fact1(u32) output.\n@def fact2(u32, u32, u32) output.\n  @def  fact3  (  u32  ,  u32  ) output.\n\nfact3(0, 1).\nfact3(2, 3).\n\nfact2(1,2,3).\n\n// strings\n@def fact4(string) output.\n@def fact5(u32, string) output.\n@def fact6(string  ,  string,string  )     output  .\n\nfact4(\"\").\nfact4(\"a\").\nfact4(  \"b\"   ).\nfact6(  \"c\"   , \"d\",\"e\"   ).\nfact4(\"\\\"\\n\\r\\t\\b\\f\\v\\0\").\n\n// NOTE: rules and assignments are tested implicitly in other tests\n\n// mix of everything\n@def edge(field1: u32, field2: u32) input.\n@def path(u32, only_one_named_field: u32) output.\n@def literals_in_rule(u32) output.\n\nedge(1, 2).\nedge(2, 3).\n\npath(x, y) :-\n  edge(x, y).\n\npath(x, y) :-\n  edge(x, z),\n  path(z, y).\n\n// This also checks for potential naming collisions\n@def u32(u32) input.\n@def string(string) input.\n@def mix(u32, string) input.\nliterals_in_rule(789) :-\n  u32(  123 ),\n  string( \"abc\"),\n  mix(456, \"def\").\n\n@def constraints(u32).\nconstraints(x) :-\n  u32(x),\n  x < 100,\n  x <= 100,\n  100 > x,\n  100 >= x,\n  100 != x.\n\n@def arithmetic(u32).\narithmetic(x) :-\n  u32(x),\n  x = 1 + 2 * (7 / 4),\n  1 + 2 - 0 < x.\narithmetic(123 + 456 * 789).\n\n@extern constraint(string).\n@extern func(u32) u32.\n@extern func2(u32, u32) string.\n@def test_externs(u32).\n\ntest_externs(func(123)).\ntest_externs(x) :-\n  edge(x, y),\n  func(y) + 1 = x,\n  x = func(y) + y,\n  func(x) = func(y).\n\n@extern func_with_named_fields(field1: u32, field2: u32) string.\n@extern constraint_with_named_field(u32, another_field: u32).\n\n@def test_negation(u32).\n\ntest_negation(x) :-\n  edge(x, x),\n  !edge(1, 2),\n  edge(123, x).\n\n// TODO: failure cases, more thorough testing in general\n"
  },
  {
    "path": "tests/runtime/hashmap_test.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile %t/program.eclair > %t/program.ll\n// RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll\n// RUN: %t/program\n\n//--- program.eclair\n@def fact(u32) output.\n\n//--- main.c\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\ntypedef uint32_t u32;\n\ntypedef struct symbol {\n  u32 size;\n  char *data;\n} __attribute__((packed)) symbol_t;\n\ntypedef struct entry {\n  symbol_t symbol;\n  u32 value;\n} entry_t;\n\ntypedef struct bucket {\n  entry_t *start;\n  entry_t *end;\n  u32 capacity;\n} bucket_t;\n\ntypedef struct hashmap {\n  bucket_t buckets[64];\n} eclair_hashmap_t;\n\nstatic void ASSERT(bool result, const char *msg) {\n  if (!result) {\n    printf(\"Assertion failed: %s\\n\", msg);\n    exit(1);\n  }\n}\n\nextern void eclair_hashmap_init(void *map);\nextern void eclair_hashmap_destroy(void *map);\nextern u32 eclair_hashmap_get_or_put_value(void *map, void *key, u32 value);\nextern bool eclair_hashmap_contains(void *map, void *key);\nextern u32 eclair_hashmap_lookup(void *map, void *key);\n\nstatic void test_eclair_hashmap_init() {\n  eclair_hashmap_t map;\n  eclair_hashmap_init(&map);\n  eclair_hashmap_destroy(&map);\n}\n\nstatic void test_eclair_hashmap_collisions() {\n  eclair_hashmap_t map;\n  eclair_hashmap_init(&map);\n\n  // NOTE: these 2 create a collision\n  symbol_t symbol1 = {.size = 1, .data = \"<\"};\n  symbol_t symbol2 = {.size = 1, .data = \"|\"};\n  eclair_hashmap_get_or_put_value(&map, &symbol1, 0);\n  eclair_hashmap_get_or_put_value(&map, &symbol2, 1);\n\n  u32 hash = 60; // 60 % 64 or 124 % 64 = 60\n  printf(\"%s, %s\\n\", map.buckets[hash].start[0].symbol.data, symbol1.data);\n  ASSERT(map.buckets[hash].start[0].symbol.data == symbol1.data,\n         \"Unexpected symbol1 in hashmap\");\n  ASSERT(map.buckets[hash].start[0].value == 0, \"Unexpected value1 in hashmap\");\n  ASSERT(map.buckets[hash].start[1].symbol.data == symbol2.data,\n         \"Unexpected symbol2 in hashmap\");\n  ASSERT(map.buckets[hash].start[1].value == 1, \"Unexpected value2 in hashmap\");\n\n  eclair_hashmap_destroy(&map);\n}\n\nstatic void test_eclair_hashmap_get_or_put_value() {\n  eclair_hashmap_t map;\n  eclair_hashmap_init(&map);\n\n  // NOTE: these 2 create a collision\n  symbol_t symbol1 = {.size = 1, .data = \"<\"};\n  symbol_t symbol2 = {.size = 1, .data = \"|\"};\n\n  // non-existing value => adds new value\n  u32 result1 = eclair_hashmap_get_or_put_value(&map, &symbol1, 0);\n  u32 result2 = eclair_hashmap_get_or_put_value(&map, &symbol2, 1);\n  ASSERT(result1 == 0, \"Unexpected result from eclair_hashmap_get_or_put_value\");\n  ASSERT(result2 == 1, \"Unexpected result from eclair_hashmap_get_or_put_value\");\n\n  // existing value => finds existing value\n  u32 result3 = eclair_hashmap_get_or_put_value(&map, &symbol1, 2);\n  u32 result4 = eclair_hashmap_get_or_put_value(&map, &symbol2, 3);\n  ASSERT(result1 == 0, \"Unexpected result from eclair_hashmap_get_or_put_value\");\n  ASSERT(result2 == 1, \"Unexpected result from eclair_hashmap_get_or_put_value\");\n\n  eclair_hashmap_destroy(&map);\n}\n\nstatic void test_eclair_hashmap_contains() {\n  eclair_hashmap_t map;\n  eclair_hashmap_init(&map);\n\n  // NOTE: these 2 create a collision\n  symbol_t symbol1 = {.size = 1, .data = \"<\"};\n  symbol_t symbol2 = {.size = 1, .data = \"|\"};\n  eclair_hashmap_get_or_put_value(&map, &symbol1, 0);\n  eclair_hashmap_get_or_put_value(&map, &symbol2, 1);\n\n  ASSERT(eclair_hashmap_contains(&map, &symbol1), \"Expected to find symbol1!\");\n  ASSERT(eclair_hashmap_contains(&map, &symbol2), \"Expected to find symbol2!\");\n\n  symbol_t symbol3 = {.size = 1, .data = \"a\"};\n  ASSERT(!eclair_hashmap_contains(&map, &symbol3), \"Expected not to find symbol3!\");\n\n  eclair_hashmap_destroy(&map);\n}\n\nstatic void test_eclair_hashmap_lookup() {\n  // NOTE: can't be used for value not present in map!\n  eclair_hashmap_t map;\n  eclair_hashmap_init(&map);\n\n  // NOTE: these 2 create a collision\n  symbol_t symbol1 = {.size = 1, .data = \"<\"};\n  symbol_t symbol2 = {.size = 1, .data = \"|\"};\n  eclair_hashmap_get_or_put_value(&map, &symbol1, 0);\n  eclair_hashmap_get_or_put_value(&map, &symbol2, 1);\n\n  symbol_t symbol3 = {.size = 1, .data = \"a\"};\n  eclair_hashmap_get_or_put_value(&map, &symbol3, 2);\n\n  ASSERT(eclair_hashmap_lookup(&map, &symbol1) == 0, \"Expected to find symbol1!\");\n  ASSERT(eclair_hashmap_lookup(&map, &symbol2) == 1, \"Expected to find symbol2!\");\n  ASSERT(eclair_hashmap_lookup(&map, &symbol3) == 2, \"Expected to find symbol3!\");\n\n  eclair_hashmap_destroy(&map);\n}\n\nint main(int argc, char **argv) {\n  printf(\"Starting tests..\\n\");\n  test_eclair_hashmap_init();\n  printf(\"hashmap init: OK!\\n\");\n  test_eclair_hashmap_collisions();\n  printf(\"hashmap collisions: OK!\\n\");\n  test_eclair_hashmap_get_or_put_value();\n  printf(\"hashmap get or put value: OK!\\n\");\n  test_eclair_hashmap_contains();\n  printf(\"hashmap contains: OK!\\n\");\n  test_eclair_hashmap_lookup();\n  printf(\"Hashmap: all good!\\n\");\n  return 0;\n}\n"
  },
  {
    "path": "tests/runtime/symbol_table_test.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile %t/program.eclair > %t/program.ll\n// RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll\n// RUN: %t/program\n\n//--- program.eclair\n@def fact(u32) output.\n\n//--- main.c\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n    // NOTE: strings need to be malloc'ed / strdup'ed since they get freed\n    // when calling eclair_symbol_table_destroy\n\n    typedef uint32_t u32;\n\ntypedef struct symbol {\n  u32 size;\n  char *data;\n} __attribute__((packed)) symbol_t;\n\ntypedef symbol_t elem_t;\ntypedef struct vector {\n  elem_t *start;\n  elem_t *end;\n  uint32_t capacity;\n} eclair_vector_t;\n\ntypedef struct entry {\n  symbol_t symbol;\n  u32 value;\n} entry_t;\n\ntypedef struct bucket {\n  entry_t *start;\n  entry_t *end;\n  u32 capacity;\n} bucket_t;\n\ntypedef struct hashmap {\n  bucket_t buckets[64];\n} eclair_hashmap_t;\n\ntypedef struct symbol_table {\n  eclair_vector_t idx2sym;\n  eclair_hashmap_t sym2idx;\n} eclair_symbol_table_t;\n\nextern void eclair_symbol_table_init(eclair_symbol_table_t *table);\nextern void eclair_symbol_table_destroy(eclair_symbol_table_t *table);\nextern u32 eclair_symbol_table_find_or_insert(eclair_symbol_table_t *table, symbol_t *symbol);\nextern bool eclair_symbol_table_contains_index(eclair_symbol_table_t *table, u32 index);\nextern bool eclair_symbol_table_contains_symbol(eclair_symbol_table_t *table,\n                                         symbol_t *symbol);\nextern u32 eclair_symbol_table_lookup_index(eclair_symbol_table_t *table, symbol_t *symbol);\nextern symbol_t *eclair_symbol_table_lookup_symbol(eclair_symbol_table_t *table, u32 index);\n\nstatic void ASSERT(bool result, const char *msg) {\n  if (!result) {\n    printf(\"Assertion failed: %s\\n\", msg);\n    exit(1);\n  }\n}\n\nstatic void test_eclair_symbol_table_init() {\n  eclair_symbol_table_t table;\n  eclair_symbol_table_init(&table);\n  eclair_symbol_table_destroy(&table);\n}\n\nstatic void test_eclair_symbol_table_find_or_insert() {\n  // NOTE: hash collisions are checked on hashmap level\n\n  eclair_symbol_table_t table;\n  eclair_symbol_table_init(&table);\n\n  symbol_t symbol1 = {.size = 1, .data = strdup(\"a\")};\n  symbol_t symbol2 = {.size = 1, .data = strdup(\"b\")};\n  symbol_t symbol3 = {.size = 1, .data = strdup(\"c\")};\n  u32 index1 = eclair_symbol_table_find_or_insert(&table, &symbol1);\n  u32 index2 = eclair_symbol_table_find_or_insert(&table, &symbol2);\n  u32 index3 = eclair_symbol_table_find_or_insert(&table, &symbol3);\n\n  ASSERT(index1 == 0, \"Received unexpected index for symbol1!\");\n  ASSERT(index2 == 1, \"Received unexpected index for symbol2!\");\n  ASSERT(index3 == 2, \"Received unexpected index for symbol3!\");\n\n  u32 index4 = eclair_symbol_table_find_or_insert(&table, &symbol1);\n  ASSERT(index4 == 0, \"Received unexpected index for symbol1!\");\n\n  eclair_symbol_table_destroy(&table);\n}\n\nstatic void test_eclair_symbol_table_contains_index() {\n  eclair_symbol_table_t table;\n  eclair_symbol_table_init(&table);\n\n  symbol_t symbol = {.size = 1, .data = strdup(\"a\")};\n  u32 index = eclair_symbol_table_find_or_insert(&table, &symbol);\n\n  ASSERT(eclair_symbol_table_contains_index(&table, index),\n         \"Expected to find symbol for known index!\");\n\n  u32 unknown_index = 42;\n  ASSERT(!eclair_symbol_table_contains_index(&table, unknown_index),\n         \"Expected not to find symbol for known index!\");\n\n  eclair_symbol_table_destroy(&table);\n}\n\nstatic void test_eclair_symbol_table_contains_symbol() {\n  eclair_symbol_table_t table;\n  eclair_symbol_table_init(&table);\n\n  symbol_t symbol = {.size = 1, .data = strdup(\"a\")};\n  eclair_symbol_table_find_or_insert(&table, &symbol);\n  ASSERT(eclair_symbol_table_contains_symbol(&table, &symbol),\n         \"Expected to find symbol!\");\n\n  symbol_t not_added = {.size = 1, .data = \"b\"};\n  ASSERT(!eclair_symbol_table_contains_symbol(&table, &not_added),\n         \"Did not expect to find symbol!\");\n\n  eclair_symbol_table_destroy(&table);\n}\n\nstatic void test_eclair_symbol_table_lookup_index() {\n  eclair_symbol_table_t table;\n  eclair_symbol_table_init(&table);\n\n  symbol_t symbol1 = {.size = 1, .data = strdup(\"a\")};\n  symbol_t symbol2 = {.size = 1, .data = strdup(\"b\")};\n  eclair_symbol_table_find_or_insert(&table, &symbol1);\n  eclair_symbol_table_find_or_insert(&table, &symbol2);\n\n  u32 index1 = eclair_symbol_table_lookup_index(&table, &symbol1);\n  u32 index2 = eclair_symbol_table_lookup_index(&table, &symbol2);\n  ASSERT(index1 == 0, \"Expected to find index for symbol1!\");\n  ASSERT(index2 == 1, \"Expected to find index for symbol2!\");\n\n  // This should not occur in Datalog!\n  // symbol_t not_added = {.size = 1, .data = \"c\"};\n  // u32 missing_index = eclair_symbol_table_lookup_symbol(&table, &not_added);\n  // ASSERT(missing_index == 0xFFFFFFFF,\n  //        \"Did not expect to find index for missing symbol!\");\n\n  eclair_symbol_table_destroy(&table);\n}\n\nstatic void test_eclair_symbol_table_lookup_symbol() {\n  eclair_symbol_table_t table;\n  eclair_symbol_table_init(&table);\n\n  symbol_t symbol1 = {.size = 1, .data = strdup(\"a\")};\n  symbol_t symbol2 = {.size = 1, .data = strdup(\"b\")};\n  u32 index1 = eclair_symbol_table_find_or_insert(&table, &symbol1);\n  u32 index2 = eclair_symbol_table_find_or_insert(&table, &symbol2);\n\n  symbol_t *symbol1_result = eclair_symbol_table_lookup_symbol(&table, index1);\n  symbol_t *symbol2_result = eclair_symbol_table_lookup_symbol(&table, index2);\n  ASSERT(strcmp(symbol1_result->data, \"a\") == 0,\n         \"Expected to find index for symbol1!\");\n  ASSERT(strcmp(symbol2_result->data, \"b\") == 0,\n         \"Expected to find index for symbol2!\");\n\n  // This reads uninitialized memory, no safety checks are done!\n  // Should not occur in a real DL program\n  // u32 missing = 42;\n  // ASSERT(strcmp(eclair_symbol_table_lookup_index(&table, missing)->data, \"a\") != 0,\n  //        \"Expected not to find unknown index\");\n\n  eclair_symbol_table_destroy(&table);\n}\n\nint main(int argc, char **argv) {\n  printf(\"Testing symbol_table...\\n\");\n  test_eclair_symbol_table_init();\n  printf(\"eclair_symbol_table_init: OK!\\n\");\n  test_eclair_symbol_table_find_or_insert();\n  printf(\"eclair_symbol_table_find_or_insert: OK!\\n\");\n  test_eclair_symbol_table_contains_index();\n  printf(\"eclair_symbol_table_contains_index: OK!\\n\");\n  test_eclair_symbol_table_contains_symbol();\n  printf(\"eclair_symbol_table_contains_symbol: OK!\\n\");\n  test_eclair_symbol_table_lookup_symbol();\n  printf(\"eclair_symbol_table_lookup_symbol: OK!\\n\");\n  test_eclair_symbol_table_lookup_index();\n  printf(\"symbol_table: all good!\\n\");\n  return 0;\n}\n"
  },
  {
    "path": "tests/runtime/vector_test.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile %t/program.eclair > %t/program.ll\n// RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll\n// RUN: %t/program\n\n//--- program.eclair\n@def fact(u32) output.\n\n//--- main.c\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\ntypedef uint32_t u32;\n\ntypedef struct symbol {\n  u32 length;\n  char *data;\n} __attribute__((packed)) symbol_t;\n\ntypedef symbol_t elem_t;\ntypedef struct vector {\n  elem_t *start;\n  elem_t *end;\n  uint32_t capacity;\n} eclair_vector_t;\n\nextern void eclair_vector_init_symbol(struct vector *);\nextern void eclair_vector_destroy_symbol(struct vector *);\nextern uint32_t eclair_vector_push_symbol(struct vector *, elem_t *);\nextern uint32_t eclair_vector_size_symbol(struct vector *);\nextern elem_t *eclair_vector_get_value_symbol(struct vector *, uint32_t);\n\nvoid ASSERT(bool result, const char *msg) {\n  if (!result) {\n    printf(\"Assertion failed: %s\\n\", msg);\n    exit(1);\n  }\n}\n\n#define INITIAL_CAPACITY 16\n#define GROW_FACTOR 2\n\nstatic void test_eclair_vector_init() {\n  eclair_vector_t vec;\n  eclair_vector_init_symbol(&vec);\n\n  ASSERT(eclair_vector_size_symbol(&vec) == 0, \"vector size should be 0\");\n  ASSERT(vec.start == vec.end, \"\");\n  ASSERT(vec.capacity == INITIAL_CAPACITY,\n         \"vector capacity should be INITIAL_CAPACITY\");\n\n  eclair_vector_destroy_symbol(&vec);\n}\n\nstatic void test_eclair_vector_single_push() {\n  eclair_vector_t vec;\n  eclair_vector_init_symbol(&vec);\n\n  ASSERT(eclair_vector_size_symbol(&vec) == 0, \"Unexpected size before!\");\n  // strdup needed since the vector frees symbols\n  symbol_t symbol = {.data = strdup(\"some_symbol\")};\n  u32 idx = eclair_vector_push_symbol(&vec, &symbol);\n  ASSERT(eclair_vector_size_symbol(&vec) == 1, \"Unexpected size after!\");\n\n  eclair_vector_destroy_symbol(&vec);\n}\n\nstatic void test_eclair_vector_push() {\n  eclair_vector_t vec;\n  eclair_vector_init_symbol(&vec);\n\n  for (u32 i = 0; i < INITIAL_CAPACITY; ++i) {\n    // strdup needed since the vector frees symbols\n    symbol_t symbol = {.data = strdup(\"some_symbol\")};\n\n    ASSERT(eclair_vector_size_symbol(&vec) == i, \"Unexpected size before!\");\n    u32 idx = eclair_vector_push_symbol(&vec, &symbol);\n    ASSERT(idx == i, \"Unexpected index!\");\n    ASSERT(eclair_vector_size_symbol(&vec) == i + 1, \"Unexpected size after!\");\n    ASSERT(vec.capacity == INITIAL_CAPACITY, \"Unexpected capacity\");\n  }\n\n  ASSERT(eclair_vector_size_symbol(&vec) == INITIAL_CAPACITY,\n         \"Unexpected size before resize!\");\n  symbol_t symbol = {.data = strdup(\"final_symbol\")};\n  u32 idx = eclair_vector_push_symbol(&vec, &symbol);\n  ASSERT(idx == INITIAL_CAPACITY, \"Unexpected index!\");\n  ASSERT(eclair_vector_size_symbol(&vec) == INITIAL_CAPACITY + 1,\n         \"Unexpected size after resize!\");\n  ASSERT(vec.capacity == GROW_FACTOR * INITIAL_CAPACITY, \"Unexpected capacity\");\n\n  eclair_vector_destroy_symbol(&vec);\n}\n\nstatic void test_eclair_vector_resize() {\n  eclair_vector_t vec;\n  eclair_vector_init_symbol(&vec);\n\n  ASSERT(vec.capacity == INITIAL_CAPACITY, \"Unexpected capacity\");\n\n  for (u32 i = 0; i < 2 * INITIAL_CAPACITY; ++i) {\n    // strdup needed since the vector frees symbols\n    symbol_t symbol = {.data = strdup(\"some_symbol\")};\n    u32 idx = eclair_vector_push_symbol(&vec, &symbol);\n  }\n\n  ASSERT(vec.capacity == GROW_FACTOR * INITIAL_CAPACITY, \"Unexpected capacity\");\n\n  for (u32 i = 0; i < 2 * INITIAL_CAPACITY; ++i) {\n    // strdup needed since the vector frees symbols\n    symbol_t symbol = {.data = strdup(\"some_symbol\")};\n    u32 idx = eclair_vector_push_symbol(&vec, &symbol);\n  }\n\n  ASSERT(vec.capacity == GROW_FACTOR * GROW_FACTOR * INITIAL_CAPACITY,\n         \"Unexpected capacity\");\n\n  eclair_vector_destroy_symbol(&vec);\n}\n\nint main(int argc, char **argv) {\n  printf(\"Start tests..\\n\");\n  test_eclair_vector_init();\n  printf(\"eclair_vector_init: OK!\\n\");\n  test_eclair_vector_single_push();\n  printf(\"eclair_vector_single_push: OK!\\n\");\n  test_eclair_vector_push();\n  printf(\"eclair_vector_push: OK!\\n\");\n  test_eclair_vector_resize();\n  printf(\"Vector: all good!\\n\");\n  return 0;\n}\n"
  },
  {
    "path": "tests/semantic_analysis/cyclic_negation.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out\n// RUN: diff -w %t/expected1.out %t/actual1.out\n\n//--- program1.eclair\n@def a(u32) input.\n@def b(u32) output.\n@def c(u32) input.\n@def d(u32) output.\n@def e(u32) input.\n@def f(u32) input.\n@def g(u32) output.\n\nb(x) :-\n  a(x),\n  !b(x).\n\nd(x) :-\n  c(x),\n  !d(x).\n\nd(x) :-\n  !c(x),\n  d(x).\n\ng(x) :-\n  f(x).\n\nf(x) :-\n  e(x),\n  !g(x).\n\n//--- expected1.out\n[error]: Negation used in recursive set of rules\n     ╭──▶ TEST_DIR/program1.eclair@11:3-11:8\n     │\n  11 │   !b(x).\n     •   ┬────\n     •   ╰╸ This negation is used in a set of rules that is recursive, which is not allowed.\n     •\n     │ Hint: Restructure the program so the negation does not occur in the set of recursive rules.\n     │ Hint: Remove the negation entirely.\n─────╯\n\n[error]: Negation used in recursive set of rules\n     ╭──▶ TEST_DIR/program1.eclair@15:3-15:8\n     │\n  15 │   !d(x).\n     •   ┬────\n     •   ╰╸ This negation is used in a set of rules that is recursive, which is not allowed.\n     •\n     │ Hint: Restructure the program so the negation does not occur in the set of recursive rules.\n     │ Hint: Remove the negation entirely.\n─────╯\n\n[error]: Negation used in recursive set of rules\n     ╭──▶ TEST_DIR/program1.eclair@26:3-26:8\n     │\n  26 │   !g(x).\n     •   ┬────\n     •   ╰╸ This negation is used in a set of rules that is recursive, which is not allowed.\n     •\n     │ Hint: Restructure the program so the negation does not occur in the set of recursive rules.\n     │ Hint: Remove the negation entirely.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/dead_internal_relation.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair --emit ra-transformed 2> %t/actual.out\n// RUN: diff %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact(u32).\n@def fact2(u32) output.\n\n@def internal_rule(u32).\ninternal_rule(x) :-\n  x = 1.\n\n//--- expected.out\n[error]: Dead internal relation\n     ╭──▶ TEST_DIR/program.eclair@1:1-1:16\n     │\n   1 │ @def fact(u32).\n     • ┬──────────────\n     • ╰╸ The internal rule 'fact' has no facts or rules defined and will never produce results.\n     •\n     │ Hint: This might indicate a logic error in your code.\n     │ Hint: Remove this rule if it is no longer needed.\n     │ Hint: Add 'input' to the declaration to indicate this rule is an input.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/invalid_extern_usage.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n//--- program1.eclair\n// RUN: %eclair compile --emit ra-transformed %t/program1.eclair 2> %t/program1.out\n// RUN: diff -w %t/expected1.out %t/program1.out\n\n@def edge(u32, u32) input.\n@def test_externs(u32) output.\n\n@extern constraint1(string).\n@extern func1(u32) u32.\n@extern func2(u32, u32) string.\n\n// Duplicate externs / functions\n@extern constraint1(string).\n@extern func1(string).\n@def func2(u32) output.\n\n// Usage of externs as facts\nconstraint1(\"abc\").\nfunc1(123).\n\n// Usage of externs as rules\n@extern constraint2(u32).\n@extern func3(u32) u32.\n@extern func4(u32, u32) u32.\n\nconstraint2(x) :- edge(x, 123).\nfunc3(x) :- edge(x, 123).\n\n// Invalid wildcards in extern constraints / functions\ntest_externs(x) :-\n  edge(x, func3(_)),\n  edge(x, func4(_, _)),\n  constraint2(_).\n\n// Ungrounded variables\ntest_externs(x) :-\n  constraint2(x),\n  x = func4(x, x).\n\n//--- expected1.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program1.eclair@35:14-35:15\n     │\n  35 │ ╭┤ test_externs(x) :-\n     • │               ┬\n     • │               ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  36 │ │    constraint2(x),\n  37 │ ├┤   x = func4(x, x).\n     • │\n     • ╰╸ This contains no clauses that refer to 'x'.\n     •\n     │ Hint: Use the variable 'x' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program1.eclair@37:3-37:4\n     │\n  35 │ ╭┤ test_externs(x) :-\n  36 │ │    constraint2(x),\n  37 │ ├┤   x = func4(x, x).\n     • │    ┬\n     • │    ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'x'.\n     •\n     │ Hint: Use the variable 'x' as an argument in a relation.\n─────╯\n\n[error]: Wildcard in externally defined atom\n     ╭──▶ TEST_DIR/program1.eclair@30:17-30:18\n     │\n  30 │   edge(x, func3(_)),\n     •           ┬─────┬─\n     •           │     ╰╸ Wildcard found.\n     •           ╰╸ An external atom only supports constants or grounded variables.\n     •\n     │ Hint: Replace the wildcard with a constant or grounded variable.\n─────╯\n\n[error]: Wildcard in externally defined atom\n     ╭──▶ TEST_DIR/program1.eclair@31:17-31:18\n     │\n  31 │   edge(x, func4(_, _)),\n     •           ┬─────┬────\n     •           │     ╰╸ Wildcard found.\n     •           ╰╸ An external atom only supports constants or grounded variables.\n     •\n     │ Hint: Replace the wildcard with a constant or grounded variable.\n─────╯\n\n[error]: Wildcard in externally defined atom\n     ╭──▶ TEST_DIR/program1.eclair@31:20-31:21\n     │\n  31 │   edge(x, func4(_, _)),\n     •           ┬────────┬─\n     •           │        ╰╸ Wildcard found.\n     •           ╰╸ An external atom only supports constants or grounded variables.\n     •\n     │ Hint: Replace the wildcard with a constant or grounded variable.\n─────╯\n\n[error]: Wildcard in externally defined atom\n     ╭──▶ TEST_DIR/program1.eclair@32:15-32:16\n     │\n  32 │   constraint2(_).\n     •   ┬───────────┬─\n     •   │           ╰╸ Wildcard found.\n     •   ╰╸ An external atom only supports constants or grounded variables.\n     •\n     │ Hint: Replace the wildcard with a constant or grounded variable.\n─────╯\n\n[error]: Multiple definitions for 'constraint1'\n     ╭──▶ TEST_DIR/program1.eclair@7:1-7:29\n     │\n   7 │ @extern constraint1(string).\n     • ┬───────────────────────────\n     • ╰╸ 'constraint1' is originally defined here.\n     •\n  12 │ @extern constraint1(string).\n     • ┬───────────────────────────\n     • ╰╸ 'constraint1' is re-defined here.\n     •\n     │ Hint: You can solve this by removing the duplicate definitions for 'constraint1'.\n─────╯\n\n[error]: Multiple definitions for 'func1'\n     ╭──▶ TEST_DIR/program1.eclair@8:1-8:24\n     │\n   8 │ @extern func1(u32) u32.\n     • ┬──────────────────────\n     • ╰╸ 'func1' is originally defined here.\n     •\n  13 │ @extern func1(string).\n     • ┬─────────────────────\n     • ╰╸ 'func1' is re-defined here.\n     •\n     │ Hint: You can solve this by removing the duplicate definitions for 'func1'.\n─────╯\n\n[error]: Multiple definitions for 'func2'\n     ╭──▶ TEST_DIR/program1.eclair@9:1-9:32\n     │\n   9 │ @extern func2(u32, u32) string.\n     • ┬──────────────────────────────\n     • ╰╸ 'func2' is originally defined here.\n     •\n  14 │ @def func2(u32) output.\n     • ┬──────────────────────\n     • ╰╸ 'func2' is re-defined here.\n     •\n     │ Hint: You can solve this by removing the duplicate definitions for 'func2'.\n─────╯\n\n[error]: Extern definition used as top level fact\n     ╭──▶ TEST_DIR/program1.eclair@17:1-17:20\n     │\n   7 │ @extern constraint1(string).\n     • ┬───────────────────────────\n     • ╰╸ 'constraint1' previously defined here as external.\n     •\n  17 │ constraint1(\"abc\").\n     • ┬──────────────────\n     • ╰╸ 'constraint1' is used as a fact here, which is not allowed for extern definitions.\n     •\n     │ Hint: Convert 'constraint1' to a relation.\n     │ Hint: Remove the top level fact.\n─────╯\n\n[error]: Extern definition used as top level fact\n     ╭──▶ TEST_DIR/program1.eclair@17:1-17:20\n     │\n  12 │ @extern constraint1(string).\n     • ┬───────────────────────────\n     • ╰╸ 'constraint1' previously defined here as external.\n     •\n  17 │ constraint1(\"abc\").\n     • ┬──────────────────\n     • ╰╸ 'constraint1' is used as a fact here, which is not allowed for extern definitions.\n     •\n     │ Hint: Convert 'constraint1' to a relation.\n     │ Hint: Remove the top level fact.\n─────╯\n\n[error]: Extern definition used as top level fact\n     ╭──▶ TEST_DIR/program1.eclair@18:1-18:12\n     │\n   8 │ @extern func1(u32) u32.\n     • ┬──────────────────────\n     • ╰╸ 'func1' previously defined here as external.\n     •\n  18 │ func1(123).\n     • ┬──────────\n     • ╰╸ 'func1' is used as a fact here, which is not allowed for extern definitions.\n     •\n     │ Hint: Convert 'func1' to a relation.\n     │ Hint: Remove the top level fact.\n─────╯\n\n[error]: Extern definition used as top level fact\n     ╭──▶ TEST_DIR/program1.eclair@18:1-18:12\n     │\n  13 │ @extern func1(string).\n     • ┬─────────────────────\n     • ╰╸ 'func1' previously defined here as external.\n     •\n  18 │ func1(123).\n     • ┬──────────\n     • ╰╸ 'func1' is used as a fact here, which is not allowed for extern definitions.\n     •\n     │ Hint: Convert 'func1' to a relation.\n     │ Hint: Remove the top level fact.\n─────╯\n\n[error]: Extern definition used in rule head\n     ╭──▶ TEST_DIR/program1.eclair@25:1-25:32\n     │\n  21 │ @extern constraint2(u32).\n     • ┬────────────────────────\n     • ╰╸ 'constraint2' previously defined here as external.\n     •\n  25 │ constraint2(x) :- edge(x, 123).\n     • ┬──────────────────────────────\n     • ╰╸ 'constraint2' is used as a rule head here, which is not allowed for extern definitions.\n     •\n     │ Hint: Convert 'constraint2' to a relation.\n     │ Hint: Remove the rule.\n─────╯\n\n[error]: Extern definition used in rule head\n     ╭──▶ TEST_DIR/program1.eclair@26:1-26:26\n     │\n  22 │ @extern func3(u32) u32.\n     • ┬──────────────────────\n     • ╰╸ 'func3' previously defined here as external.\n     •\n  26 │ func3(x) :- edge(x, 123).\n     • ┬────────────────────────\n     • ╰╸ 'func3' is used as a rule head here, which is not allowed for extern definitions.\n     •\n     │ Hint: Convert 'func3' to a relation.\n     │ Hint: Remove the rule.\n─────╯\n\n[error]: Invalid use of function\n     ╭──▶ TEST_DIR/program1.eclair@18:1-18:12\n     │\n   8 │ @extern func1(u32) u32.\n     • ┬──────────────────────\n     • ╰╸ Previously defined as a function here.\n     •\n  18 │ func1(123).\n     • ┬──────────\n     • ╰╸ Expected a constraint here.\n     •\n     │ Hint: Maybe you meant to declare this an external constraint instead?\n     │ Hint: Remove the invalid function.\n─────╯\n\n[error]: Invalid use of function\n     ╭──▶ TEST_DIR/program1.eclair@26:1-26:26\n     │\n  22 │ @extern func3(u32) u32.\n     • ┬──────────────────────\n     • ╰╸ Previously defined as a function here.\n     •\n  26 │ func3(x) :- edge(x, 123).\n     • ┬────────────────────────\n     • ╰╸ Expected a constraint here.\n     •\n     │ Hint: Maybe you meant to declare this an external constraint instead?\n     │ Hint: Remove the invalid function.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/invalid_options_usage.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n//--- program1.eclair\n// RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out\n// RUN: diff %t/expected1.out %t/actual1.out\n\n@def fact(string) input input.\n@def fact2(string) output output output.\n\n//--- expected1.out\n[error]: Failed to parse file\n     ╭──▶ TEST_DIR/program1.eclair@4:30-4:31\n     │\n   4 │ @def fact(string) input input.\n     •                              ┬\n     •                              ╰╸ More than one option of type 'input' is not allowed.\n─────╯\n\n[error]: Failed to parse file\n     ╭──▶ TEST_DIR/program1.eclair@5:40-5:41\n     │\n   5 │ @def fact2(string) output output output.\n     •                                        ┬\n     •                                        ╰╸ More than one option of type 'output' is not allowed.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/invalid_wildcard_usage.eclair",
    "content": "// NOTE: happy path is mostly skipped, this is tested implicitly by running\n// the compiler end-to-end in most other tests\n\n// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program1.eclair 2> %t/top_level_facts_actual.out\n// RUN: diff %t/top_level_facts_expected.out %t/top_level_facts_actual.out\n\n//--- program1.eclair\n@def a(u32, u32) output.\n\na(123, _).\na(_, 123).\n\n//--- top_level_facts_expected.out\n[error]: Wildcard in top level fact\n     ╭──▶ TEST_DIR/program1.eclair@3:8-3:9\n     │\n   3 │ a(123, _).\n     • ┬──────┬──\n     • │      ╰╸ Wildcard found.\n     • ╰╸ A top level fact only supports constants.\n     •    Variables or wildcards are not allowed.\n     •\n     │ Hint: Replace the wildcard with a constant.\n─────╯\n\n[error]: Wildcard in top level fact\n     ╭──▶ TEST_DIR/program1.eclair@4:3-4:4\n     │\n   4 │ a(_, 123).\n     • ┬─┬───────\n     • │ ╰╸ Wildcard found.\n     • ╰╸ A top level fact only supports constants.\n     •    Variables or wildcards are not allowed.\n     •\n     │ Hint: Replace the wildcard with a constant.\n─────╯\n//--- program2.eclair\n\n// RUN: %eclair compile %t/program2.eclair 2> %t/wildcard_in_rule_head_actual.out\n// RUN: diff -w %t/wildcard_in_rule_head_expected.out %t/wildcard_in_rule_head_actual.out\n\n@def a(u32, u32) output.\n@def b(u32) input.\n\na(x, _) :-\n  b(x).\n\na(_, x) :-\n  b(x).\n\n//--- wildcard_in_rule_head_expected.out\n[error]: Wildcard in 'head' of rule\n     ╭──▶ TEST_DIR/program2.eclair@8:6-8:7\n     │\n   8 │ ╭┤ a(x, _) :-\n     • │       ┬\n     • │       ╰╸ Wildcard found.\n   9 │ ├┤   b(x).\n     • │\n     • ╰╸ Only constants and variables are allowed in the head of a rule.\n     •    Wildcards are not allowed.\n     •\n     │ Hint: Replace the wildcard with a constant or a variable.\n─────╯\n\n[error]: Wildcard in 'head' of rule\n     ╭──▶ TEST_DIR/program2.eclair@11:3-11:4\n     │\n  11 │ ╭┤ a(_, x) :-\n     • │    ┬\n     • │    ╰╸ Wildcard found.\n  12 │ ├┤   b(x).\n     • │\n     • ╰╸ Only constants and variables are allowed in the head of a rule.\n     •    Wildcards are not allowed.\n     •\n     │ Hint: Replace the wildcard with a constant or a variable.\n─────╯\n//--- program3.eclair\n\n// RUN: %eclair compile %t/program3.eclair 2> %t/wildcard_in_assignment_actual.out\n// RUN: diff -w %t/wildcard_in_assignment_expected.out %t/wildcard_in_assignment_actual.out\n\n@def a(u32) output.\n@def b(u32) input.\n\na(x) :-\n  b(x),\n  _ = 123.\n\na(x) :-\n  b(x),\n  123 = _.\n\na(x) :-\n  b(x),\n  _ = _.\n\n//--- wildcard_in_assignment_expected.out\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program3.eclair@10:3-10:4\n     │\n  10 │   _ = 123.\n     •   ┬──────\n     •   ├╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program3.eclair@14:9-14:10\n     │\n  14 │   123 = _.\n     •   ┬─────┬\n     •   │     ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program3.eclair@18:3-18:4\n     │\n  18 │   _ = _.\n     •   ┬────\n     •   ├╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program3.eclair@18:7-18:8\n     │\n  18 │   _ = _.\n     •   ┬───┬\n     •   │   ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n//--- program4.eclair\n\n// RUN: %eclair compile %t/program4.eclair 2> %t/wildcard_in_valid_actual.out\n// RUN: diff %t/wildcard_valid_expected.out %t/wildcard_in_valid_actual.out\n\n@def a(u32) output.\n@def b(u32, u32, u32) input.\n\na(x) :-\n  b(x, _, _),\n  b(_, x, _),\n  b(_, _, x).\n\n//--- wildcard_valid_expected.out\n//--- program5.eclair\n\n// RUN: %eclair compile %t/program5.eclair 2> %t/wildcard_in_comparison_actual.out\n// RUN: diff -w %t/wildcard_in_comparison_expected.out %t/wildcard_in_comparison_actual.out\n\n@def a(u32) output.\n@def b(u32) input.\n\na(x) :-\n  b(x),\n  123 < _,\n  123 <= _,\n  123 > _,\n  123 >= _,\n  123 != _.\n\n//--- wildcard_in_comparison_expected.out\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program5.eclair@10:9-10:10\n     │\n  10 │   123 < _,\n     •   ┬─────┬\n     •   │     ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program5.eclair@11:10-11:11\n     │\n  11 │   123 <= _,\n     •   ┬──────┬\n     •   │      ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program5.eclair@12:9-12:10\n     │\n  12 │   123 > _,\n     •   ┬─────┬\n     •   │     ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program5.eclair@13:10-13:11\n     │\n  13 │   123 >= _,\n     •   ┬──────┬\n     •   │      ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n\n[error]: Found wildcard in constraint\n     ╭──▶ TEST_DIR/program5.eclair@14:10-14:11\n     │\n  14 │   123 != _.\n     •   ┬──────┬\n     •   │      ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a constraint.\n     •\n     │ Hint: This statement can be removed since it has no effect.\n     │ Hint: Replace the wildcard with a variable.\n─────╯\n//--- program6.eclair\n\n// RUN: %eclair compile %t/program6.eclair 2> %t/wildcard_in_binop_actual.out\n// RUN: diff -w %t/wildcard_in_binop_expected.out %t/wildcard_in_binop_actual.out\n\n@def a(u32) output.\n@def b(u32) output.\n\nb(123 + _).\nb(_ + 123).\n\na(x) :-\n  b(x),\n  x = 123 + _,\n  x = _ + 123.\n\n//--- wildcard_in_binop_expected.out\n[error]: Found wildcard in binary operation\n     ╭──▶ TEST_DIR/program6.eclair@8:9-8:10\n     │\n   8 │ b(123 + _).\n     •   ┬─────┬\n     •   │     ╰╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a binary operation.\n     •\n     │ Hint: Replace the wildcard with a variable or literal.\n─────╯\n\n[error]: Found wildcard in binary operation\n     ╭──▶ TEST_DIR/program6.eclair@9:3-9:4\n     │\n   9 │ b(_ + 123).\n     •   ┬──────\n     •   ├╸ Wildcard found.\n     •   ╰╸ Only constants and variables are allowed in a binary operation.\n     •\n     │ Hint: Replace the wildcard with a variable or literal.\n─────╯\n\n[error]: Found wildcard in binary operation\n     ╭──▶ TEST_DIR/program6.eclair@13:13-13:14\n     │\n  13 │   x = 123 + _,\n     •       ┬─────┬\n     •       │     ╰╸ Wildcard found.\n     •       ╰╸ Only constants and variables are allowed in a binary operation.\n     •\n     │ Hint: Replace the wildcard with a variable or literal.\n─────╯\n\n[error]: Found wildcard in binary operation\n     ╭──▶ TEST_DIR/program6.eclair@14:7-14:8\n     │\n  14 │   x = _ + 123.\n     •       ┬──────\n     •       ├╸ Wildcard found.\n     •       ╰╸ Only constants and variables are allowed in a binary operation.\n     •\n     │ Hint: Replace the wildcard with a variable or literal.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/no_output_relations.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out\n// RUN: diff -w %t/expected1.out %t/actual1.out\n\n// RUN: %eclair compile %t/program2.eclair | FileCheck %t/program2.eclair\n\n//--- program1.eclair\n@def fact(u32) input.\n\nfact(123).\n\n//--- expected1.out\n[error]: No output relations found\n     ╭──▶ TEST_DIR/program1.eclair@1:1-1:2\n     │\n   1 │ @def fact(u32) input.\n     • ┬\n     • ╰╸ This module does not produce any results\n     •\n     │ Hint: Add an 'output' qualifier to one of the relations defined in this module.\n─────╯\n//--- program2.eclair\n@def fact(u32) input.\n@def fact2(u32) output.\n\nfact(123).\n\nfact2(x) :- fact(x).\n\n// CHECK: eclair_program_run\n"
  },
  {
    "path": "tests/semantic_analysis/unconstrained_variables.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32, u32) output.\n@def fact2(u32) output.\n\nfact2(x) :- fact1(x, 1).\n\nfact2(2) :-\n  fact1(x, 2),\n  !fact1(2, x).\n\nfact2(3) :-\n  fact1(x, 3),\n  fact1(3, x).\n\nfact2(4) :-\n  fact1(x, 4),\n  x = 4.\n\nfact2(2) :-\n  fact1(x, y).\n\n//--- expected.out\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program.eclair@19:9-19:10\n     │\n  18 │ ╭┤ fact2(2) :-\n  19 │ ├┤   fact1(x, y).\n     • │          ┬\n     • │          ╰╸ The variable 'x' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'x'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program.eclair@19:12-19:13\n     │\n  18 │ ╭┤ fact2(2) :-\n  19 │ ├┤   fact1(x, y).\n     • │             ┬\n     • │             ╰╸ The variable 'y' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'y'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/ungrounded_variables.eclair",
    "content": "// NOTE: happy path is not tested, this is tested implicitly by running\n// the compiler end-to-end in most other tests\n\n// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/top_level_facts_actual.out\n// RUN: diff %t/top_level_facts_expected.out %t/top_level_facts_actual.out\n\n//--- program.eclair\n@def edge(u32, u32) output.\n\nedge(a, 1).\nedge(2, b).\nedge(c, d).\nedge(e + 1, 1 + f).\n\n//--- top_level_facts_expected.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@3:6-3:7\n     │\n   3 │ edge(a, 1).\n     • ┬────┬─────\n     • │    ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • ╰╸ This contains no clauses that refer to 'a'.\n     •\n     │ Hint: Use the variable 'a' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@4:9-4:10\n     │\n   4 │ edge(2, b).\n     • ┬───────┬──\n     • │       ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • ╰╸ This contains no clauses that refer to 'b'.\n     •\n     │ Hint: Use the variable 'b' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@5:6-5:7\n     │\n   5 │ edge(c, d).\n     • ┬────┬─────\n     • │    ╰╸ The variable 'c' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • ╰╸ This contains no clauses that refer to 'c'.\n     •\n     │ Hint: Use the variable 'c' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@5:9-5:10\n     │\n   5 │ edge(c, d).\n     • ┬───────┬──\n     • │       ╰╸ The variable 'd' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • ╰╸ This contains no clauses that refer to 'd'.\n     •\n     │ Hint: Use the variable 'd' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@6:6-6:7\n     │\n   6 │ edge(e + 1, 1 + f).\n     • ┬────┬─────────────\n     • │    ╰╸ The variable 'e' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • ╰╸ This contains no clauses that refer to 'e'.\n     •\n     │ Hint: Use the variable 'e' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@6:17-6:18\n     │\n   6 │ edge(e + 1, 1 + f).\n     • ┬───────────────┬──\n     • │               ╰╸ The variable 'f' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • ╰╸ This contains no clauses that refer to 'f'.\n     •\n     │ Hint: Use the variable 'f' as an argument in a relation.\n─────╯\n//--- program2.eclair\n\n// RUN: %eclair compile %t/program2.eclair 2> %t/ungrounded_var_in_rule_actual.out\n// RUN: diff -w %t/ungrounded_var_in_rule_expected.out %t/ungrounded_var_in_rule_actual.out\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n\nreachable(x, z) :-\n  edge(x, y).\n\nreachable(a, b) :-\n  edge(x, y).\n\n//--- ungrounded_var_in_rule_expected.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@8:14-8:15\n     │\n   8 │ ╭┤ reachable(x, z) :-\n     • │               ┬\n     • │               ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation.\n   9 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'z'.\n     •\n     │ Hint: Use the variable 'z' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@11:11-11:12\n     │\n  11 │ ╭┤ reachable(a, b) :-\n     • │            ┬\n     • │            ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  12 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'a'.\n     •\n     │ Hint: Use the variable 'a' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@11:14-11:15\n     │\n  11 │ ╭┤ reachable(a, b) :-\n     • │               ┬\n     • │               ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  12 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'b'.\n     •\n     │ Hint: Use the variable 'b' as an argument in a relation.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program2.eclair@8:14-8:15\n     │\n   8 │ ╭┤ reachable(x, z) :-\n     • │               ┬\n     • │               ╰╸ The variable 'z' only occurs once.\n   9 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This rule contains no other references to 'z'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program2.eclair@9:11-9:12\n     │\n   8 │ ╭┤ reachable(x, z) :-\n   9 │ ├┤   edge(x, y).\n     • │            ┬\n     • │            ╰╸ The variable 'y' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'y'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program2.eclair@11:11-11:12\n     │\n  11 │ ╭┤ reachable(a, b) :-\n     • │            ┬\n     • │            ╰╸ The variable 'a' only occurs once.\n  12 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This rule contains no other references to 'a'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program2.eclair@11:14-11:15\n     │\n  11 │ ╭┤ reachable(a, b) :-\n     • │               ┬\n     • │               ╰╸ The variable 'b' only occurs once.\n  12 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This rule contains no other references to 'b'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program2.eclair@12:8-12:9\n     │\n  11 │ ╭┤ reachable(a, b) :-\n  12 │ ├┤   edge(x, y).\n     • │         ┬\n     • │         ╰╸ The variable 'x' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'x'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program2.eclair@12:11-12:12\n     │\n  11 │ ╭┤ reachable(a, b) :-\n  12 │ ├┤   edge(x, y).\n     • │            ┬\n     • │            ╰╸ The variable 'y' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'y'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n//--- program3.eclair\n\n// RUN: %eclair compile %t/program3.eclair 2> %t/ungrounded_var_check_in_rule_body_actual.out\n// RUN: diff -w %t/ungrounded_var_check_in_rule_body_expected.out %t/ungrounded_var_check_in_rule_body_actual.out\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n\nreachable(x, z) :-\n  edge(x, y),\n  reachable(y, z),\n  edge(a, b).\n\n//--- ungrounded_var_check_in_rule_body_expected.out\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program3.eclair@11:8-11:9\n     │\n   8 │ ╭┤ reachable(x, z) :-\n   9 │ │    edge(x, y),\n  10 │ │    reachable(y, z),\n  11 │ ├┤   edge(a, b).\n     • │         ┬\n     • │         ╰╸ The variable 'a' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'a'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program3.eclair@11:11-11:12\n     │\n   8 │ ╭┤ reachable(x, z) :-\n   9 │ │    edge(x, y),\n  10 │ │    reachable(y, z),\n  11 │ ├┤   edge(a, b).\n     • │            ┬\n     • │            ╰╸ The variable 'b' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'b'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n//--- program4.eclair\n\n// RUN: %eclair compile %t/program4.eclair 2> %t/ungrounded_var_check_for_edge_cases.out\n// RUN: diff -w %t/ungrounded_var_check_for_edge_cases.out %t/ungrounded_var_check_for_edge_cases_actual.out\n\n@def bar(u32) input.\n@def foo(u32) output.\n\nfoo(x) :-\n  bar(x),\n  y = y.\n\nfoo(x) :-\n  bar(x),\n  a = b,\n  b = a.\n\nfoo(x) :-\n  bar(x),\n  a = b,\n  b = c,\n  c = a.\n\n//--- ungrounded_var_check_for_edge_cases_actual.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@10:3-10:4\n     │\n   8 │ ╭┤ foo(x) :-\n   9 │ │    bar(x),\n  10 │ ├┤   y = y.\n     • │    ┬\n     • │    ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'y'.\n     •\n     │ Hint: Use the variable 'y' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@10:7-10:8\n     │\n   8 │ ╭┤ foo(x) :-\n   9 │ │    bar(x),\n  10 │ ├┤   y = y.\n     • │        ┬\n     • │        ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'y'.\n     •\n     │ Hint: Use the variable 'y' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@14:3-14:4\n     │\n  12 │ ╭┤ foo(x) :-\n  13 │ │    bar(x),\n  14 │ │    a = b,\n     • │    ┬\n     • │    ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  15 │ ├┤   b = a.\n     • │\n     • ╰╸ This contains no clauses that refer to 'a'.\n     •\n     │ Hint: Use the variable 'a' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@14:7-14:8\n     │\n  12 │ ╭┤ foo(x) :-\n  13 │ │    bar(x),\n  14 │ │    a = b,\n     • │        ┬\n     • │        ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  15 │ ├┤   b = a.\n     • │\n     • ╰╸ This contains no clauses that refer to 'b'.\n     •\n     │ Hint: Use the variable 'b' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@15:3-15:4\n     │\n  12 │ ╭┤ foo(x) :-\n  13 │ │    bar(x),\n  14 │ │    a = b,\n  15 │ ├┤   b = a.\n     • │    ┬\n     • │    ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'b'.\n     •\n     │ Hint: Use the variable 'b' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@15:7-15:8\n     │\n  12 │ ╭┤ foo(x) :-\n  13 │ │    bar(x),\n  14 │ │    a = b,\n  15 │ ├┤   b = a.\n     • │        ┬\n     • │        ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'a'.\n     •\n     │ Hint: Use the variable 'a' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@19:3-19:4\n     │\n  17 │ ╭┤ foo(x) :-\n  18 │ │    bar(x),\n  19 │ │    a = b,\n     • │    ┬\n     • │    ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  20 │ │    b = c,\n  21 │ ├┤   c = a.\n     • │\n     • ╰╸ This contains no clauses that refer to 'a'.\n     •\n     │ Hint: Use the variable 'a' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@19:7-19:8\n     │\n  17 │ ╭┤ foo(x) :-\n  18 │ │    bar(x),\n  19 │ │    a = b,\n     • │        ┬\n     • │        ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  20 │ │    b = c,\n  21 │ ├┤   c = a.\n     • │\n     • ╰╸ This contains no clauses that refer to 'b'.\n     •\n     │ Hint: Use the variable 'b' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@20:3-20:4\n     │\n  17 │ ╭┤ foo(x) :-\n  18 │ │    bar(x),\n  19 │ │    a = b,\n  20 │ │    b = c,\n     • │    ┬\n     • │    ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  21 │ ├┤   c = a.\n     • │\n     • ╰╸ This contains no clauses that refer to 'b'.\n     •\n     │ Hint: Use the variable 'b' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@20:7-20:8\n     │\n  17 │ ╭┤ foo(x) :-\n  18 │ │    bar(x),\n  19 │ │    a = b,\n  20 │ │    b = c,\n     • │        ┬\n     • │        ╰╸ The variable 'c' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  21 │ ├┤   c = a.\n     • │\n     • ╰╸ This contains no clauses that refer to 'c'.\n     •\n     │ Hint: Use the variable 'c' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@21:3-21:4\n     │\n  17 │ ╭┤ foo(x) :-\n  18 │ │    bar(x),\n  19 │ │    a = b,\n  20 │ │    b = c,\n  21 │ ├┤   c = a.\n     • │    ┬\n     • │    ╰╸ The variable 'c' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'c'.\n     •\n     │ Hint: Use the variable 'c' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program4.eclair@21:7-21:8\n     │\n  17 │ ╭┤ foo(x) :-\n  18 │ │    bar(x),\n  19 │ │    a = b,\n  20 │ │    b = c,\n  21 │ ├┤   c = a.\n     • │        ┬\n     • │        ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'a'.\n     •\n     │ Hint: Use the variable 'a' as an argument in a relation.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/ungrounded_variables_arithmetic.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out\n// RUN: diff -w %t/expected1.out %t/actual1.out\n\n//--- program1.eclair\n@def a(u32) output.\n@def b(u32) output.\n\nb(123 + a).\n\na(abc) :-\n  def = 123,\n  abc = 123 + def,\n  b(abc).\n\na(abc) :-\n  abc = 123,\n  // Should detect cycle correctly.\n  def = abc + def.\n\n//--- expected1.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program1.eclair@4:9-4:10\n     │\n   4 │ b(123 + a).\n     • ┬───────┬──\n     • │       ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • ╰╸ This contains no clauses that refer to 'a'.\n     •\n     │ Hint: Use the variable 'a' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program1.eclair@14:3-14:6\n     │\n  11 │ ╭┤ a(abc) :-\n  12 │ │    abc = 123,\n  13 │ │    // Should detect cycle correctly.\n  14 │ ├┤   def = abc + def.\n     • │    ┬──\n     • │    ╰╸ The variable 'def' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'def'.\n     •\n     │ Hint: Use the variable 'def' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program1.eclair@14:15-14:18\n     │\n  11 │ ╭┤ a(abc) :-\n  12 │ │    abc = 123,\n  13 │ │    // Should detect cycle correctly.\n  14 │ ├┤   def = abc + def.\n     • │                ┬──\n     • │                ╰╸ The variable 'def' is ungrounded, meaning it is not directly bound as an argument to a relation.\n     • │\n     • ╰╸ This contains no clauses that refer to 'def'.\n     •\n     │ Hint: Use the variable 'def' as an argument in a relation.\n─────╯\n"
  },
  {
    "path": "tests/semantic_analysis/ungrounded_variables_comparisons.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program1.eclair 2> %t/program1_actual.out\n// RUN: diff -w %t/program1_expected.out %t/program1_actual.out\n\n//--- program1.eclair\n@def fact(u32, u32) input.\n@def rule(u32, u32) output.\n\nrule(x, y) :-\n  x < y,\n  x <= y,\n  x > y,\n  x >= y,\n  x != y,\n  fact(x, y).\n\n//--- program1_expected.out\n//--- program2.eclair\n\n// RUN: %eclair compile %t/program2.eclair 2> %t/program2_actual.out\n// RUN: diff -w %t/program2_expected.out %t/program2_actual.out\n\n@def fact(u32, u32) input.\n@def rule(u32, u32) output.\n\nrule(x, y) :-\n  x < z,\n  z <= y,\n  x > z,\n  z >= y,\n  x != z,\n  fact(x, y).\n\n//--- program2_expected.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@9:7-9:8\n     │\n   8 │ ╭┤ rule(x, y) :-\n   9 │ │    x < z,\n     • │        ┬\n     • │        ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  10 │ │    z <= y,\n  11 │ │    x > z,\n  12 │ │    z >= y,\n  13 │ │    x != z,\n  14 │ ├┤   fact(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'z'.\n     •\n     │ Hint: Use the variable 'z' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@10:3-10:4\n     │\n   8 │ ╭┤ rule(x, y) :-\n   9 │ │    x < z,\n  10 │ │    z <= y,\n     • │    ┬\n     • │    ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  11 │ │    x > z,\n  12 │ │    z >= y,\n  13 │ │    x != z,\n  14 │ ├┤   fact(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'z'.\n     •\n     │ Hint: Use the variable 'z' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@11:7-11:8\n     │\n   8 │ ╭┤ rule(x, y) :-\n   9 │ │    x < z,\n  10 │ │    z <= y,\n  11 │ │    x > z,\n     • │        ┬\n     • │        ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  12 │ │    z >= y,\n  13 │ │    x != z,\n  14 │ ├┤   fact(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'z'.\n     •\n     │ Hint: Use the variable 'z' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@12:3-12:4\n     │\n   8 │ ╭┤ rule(x, y) :-\n   9 │ │    x < z,\n  10 │ │    z <= y,\n  11 │ │    x > z,\n  12 │ │    z >= y,\n     • │    ┬\n     • │    ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  13 │ │    x != z,\n  14 │ ├┤   fact(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'z'.\n     •\n     │ Hint: Use the variable 'z' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program2.eclair@13:8-13:9\n     │\n   8 │ ╭┤ rule(x, y) :-\n   9 │ │    x < z,\n  10 │ │    z <= y,\n  11 │ │    x > z,\n  12 │ │    z >= y,\n  13 │ │    x != z,\n     • │         ┬\n     • │         ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation.\n  14 │ ├┤   fact(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'z'.\n     •\n     │ Hint: Use the variable 'z' as an argument in a relation.\n─────╯\n//--- program3.eclair\n\n// RUN: %eclair compile %t/program3.eclair 2> %t/program3_actual.out\n// RUN: diff -w %t/program3_expected.out %t/program3_actual.out\n\n@def fact(u32, u32) input.\n@def rule(u32, u32) output.\n\nrule(x, y) :-\n  x = z,\n  y = a,\n  a < z,\n  fact(x, y).\n\nrule(x, y) :-\n  x = z,\n  y = a,\n  a < z,\n  fact(z, y).\n\n//--- program3_expected.out\n"
  },
  {
    "path": "tests/semantic_analysis/ungrounded_variables_negations.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out\n// RUN: diff -w %t/expected1.out %t/actual1.out\n\n//--- program1.eclair\n@def a(u32) output.\n@def b(u32) output.\n\na(abc) :-\n  abc = 123,\n  !b(abc),\n  !b(def),\n  !b(abc + 123).\n\n//--- expected1.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program1.eclair@7:6-7:9\n     │\n   4 │ ╭┤ a(abc) :-\n   5 │ │    abc = 123,\n   6 │ │    !b(abc),\n   7 │ │    !b(def),\n     • │       ┬──\n     • │       ╰╸ The variable 'def' is ungrounded, meaning it is not directly bound as an argument to a relation.\n   8 │ ├┤   !b(abc + 123).\n     • │\n     • ╰╸ This contains no clauses that refer to 'def'.\n     •\n     │ Hint: Use the variable 'def' as an argument in a relation.\n─────╯\n\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program1.eclair@7:6-7:9\n     │\n   4 │ ╭┤ a(abc) :-\n   5 │ │    abc = 123,\n   6 │ │    !b(abc),\n   7 │ │    !b(def),\n     • │       ┬──\n     • │       ╰╸ The variable 'def' only occurs once.\n   8 │ ├┤   !b(abc + 123).\n     • │\n     • ╰╸ This rule contains no other references to 'def'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n"
  },
  {
    "path": "tests/string_support/encode_decode_string.eclair",
    "content": "// RUN: split-file %s %t\n\n// RUN: %eclair compile %t/program.eclair > %t/program.ll\n// RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll\n\n// RUN: %t/program | FileCheck %s\n\n// CHECK: 0 1 2\n// CHECK-NEXT: Edge, Reachable, edge\n\n//--- program.eclair\n\n@def Edge(u32, u32) input.\n@def Reachable(u32, u32) output.\n\nReachable(x, y) :-\n    Edge(x, y).\n\nReachable(x, y) :-\n    Edge(x, z),\n    Reachable(z, y).\n\n//--- main.c\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n\n\nstruct program;\n\nstruct symbol {\n    uint32_t length;\n    const char* data;\n} __attribute__((packed));\n\nextern struct program* eclair_program_init();\nextern void eclair_program_destroy(struct program*);\nextern uint32_t eclair_encode_string(struct program*, uint32_t length, const char* str);\nextern struct symbol* eclair_decode_string(struct program*, uint32_t index);\n\nstatic const char* str1 = \"Edge\";\nstatic const char* str2 = \"Reachable\";\nstatic const char* str3 = \"edge\";\n\nint main(int argc, char** argv)\n{\n    struct program* prog = eclair_program_init();\n\n    uint32_t str_index1 = eclair_encode_string(prog, strlen(str1), str1);\n    uint32_t str_index2 = eclair_encode_string(prog, strlen(str2), str2);\n    uint32_t str_index3 = eclair_encode_string(prog, strlen(str3), str3);\n    printf(\"%d %d %d\\n\", str_index1, str_index2, str_index3);\n\n    struct symbol* symbol0 = eclair_decode_string(prog, 0);\n    struct symbol* symbol1 = eclair_decode_string(prog, 1);\n    struct symbol* symbol2 = eclair_decode_string(prog, str_index3);\n    printf(\"%s, %s, %s\\n\", symbol0->data, symbol1->data, symbol2->data);\n\n    eclair_program_destroy(prog);\n    return 0;\n}\n"
  },
  {
    "path": "tests/string_support/encode_decode_string_native.eclair",
    "content": "// This checks if strings can be encoded / decoded between C <=> Eclair\n\n// RUN: split-file %s %t\n\n// RUN: %eclair compile %t/program.eclair > %t/program.ll\n// RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll\n\n// RUN: %t/program | FileCheck %s\n\n// NOTE: fact is inserted at place 0, Test at place 1!\n// CHECK: 2 2 3 1\n// CHECK-NEXT: fact, Test, some example text, some more text\n\n//--- program.eclair\n\n@def fact(u32) input output.\n@def Test(u32) input output.\n\n//--- main.c\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n\n\nstruct program;\n\nstruct symbol {\n    uint32_t length;\n    const char* data;\n} __attribute__((packed));\n\nextern struct program* eclair_program_init();\nextern void eclair_program_destroy(struct program*);\nextern uint32_t eclair_encode_string(struct program*, uint32_t length, const char* str);\nextern struct symbol* eclair_decode_string(struct program*, uint32_t index);\n\nstatic const char* str1 = \"some example text\";\nstatic const char* str2 = \"some more text\";\nstatic const char* str3 = \"Test\";\n\nint main(int argc, char** argv)\n{\n    struct program* prog = eclair_program_init();\n\n    uint32_t str_index1 = eclair_encode_string(prog, strlen(str1), str1);\n    uint32_t str_index2 = eclair_encode_string(prog, strlen(str1), str1);\n    uint32_t str_index3 = eclair_encode_string(prog, strlen(str2), str2);\n    uint32_t str_index4 = eclair_encode_string(prog, strlen(str3), str3);\n    printf(\"%d %d %d %d\\n\", str_index1, str_index2, str_index3, str_index4);\n\n    struct symbol* symbol0 = eclair_decode_string(prog, 0);\n    struct symbol* symbol1 = eclair_decode_string(prog, 1);\n    struct symbol* symbol2 = eclair_decode_string(prog, str_index1);\n    struct symbol* symbol3 = eclair_decode_string(prog, str_index3);\n    printf(\"%s, %s, %s, %s\\n\", symbol0->data, symbol1->data, symbol2->data, symbol3->data);\n\n    eclair_program_destroy(prog);\n    return 0;\n}\n"
  },
  {
    "path": "tests/string_support/encode_decode_string_wasm.eclair",
    "content": "// This checks if strings can be encoded / decoded between JS <=> Eclair\n\n// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// TODO: we really need to have our own WASM allocator..\n// RUN: test -e %t/walloc.c || wget https://raw.githubusercontent.com/wingo/walloc/master/walloc.c -O %t/walloc.c\n// RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/walloc.o %t/walloc.c\n\n// RUN: %eclair compile --target wasm32 %t/program.eclair > %t/program.ll\n// RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/program.o %t/program.ll\n// RUN: %wasm-ld --no-entry --import-memory --import-undefined -o %t/program.wasm %t/program.o %t/walloc.o\n\n// RUN: node %t/program.js | FileCheck %s\n\n// CHECK: 1 1 2\n// CHECK-NEXT: fact some example text some more text\n\n//--- program.eclair\n\n@def fact(u32) input output.\n\n//--- program.js\n\nconst fs = require(\"fs\");\n\nconst encodeString = (memory, instance, program, string) => {\n  const address = instance.exports.eclair_malloc(string.length * 3);\n\n  const array = new Uint8Array(memory.buffer, address, string.length * 3);\n  const { written } = new TextEncoder().encodeInto(string, array);\n  const index = instance.exports.eclair_encode_string(\n    program,\n    written,\n    address\n  );\n\n  instance.exports.eclair_free(address);\n  return index;\n};\n\nconst decodeString = (memory, instance, program, index) => {\n  const address = instance.exports.eclair_decode_string(program, index);\n  const symbolMemory = new Uint32Array(memory.buffer, address, 2);\n  const [stringLength, byteArrayAddress] = symbolMemory;\n  const bytes = new Uint8Array(memory.buffer, byteArrayAddress, stringLength);\n  return new TextDecoder().decode(bytes);\n};\n\nconst main = () => {\n  const bytes = fs.readFileSync(\"TEST_DIR/program.wasm\");\n\n  const mod = new WebAssembly.Module(bytes);\n  const memory = new WebAssembly.Memory({ initial: 10, maximum: 10 });\n  const imports = { env: { memory } };\n  const instance = new WebAssembly.Instance(mod, imports);\n\n  const program = instance.exports.eclair_program_init();\n\n  const str = \"some example text\";\n  const str2 = \"some more text\";\n  const strIndex1 = encodeString(memory, instance, program, str);\n  const strIndex2 = encodeString(memory, instance, program, str);\n  const strIndex3 = encodeString(memory, instance, program, str2);\n  console.log(strIndex1, strIndex2, strIndex3);\n  console.log(\n    decodeString(memory, instance, program, 0),\n    decodeString(memory, instance, program, strIndex1),\n    decodeString(memory, instance, program, strIndex3)\n  );\n\n  instance.exports.eclair_program_destroy(program);\n};\n\nmain();\n"
  },
  {
    "path": "tests/transpilation/souffle.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile --emit souffle %t/program.eclair > %t/actual_souffle.out\n// RUN: diff %t/expected_souffle.out %t/actual_souffle.out\n// RUN: %eclair compile --emit souffle %t/unsupported.eclair 2> %t/error.out\n// RUN: diff -w %t/expected_error.out %t/error.out\n// RUN: %eclair compile --emit souffle %t/unsupported2.eclair 2> %t/error.out\n// RUN: diff  %t/expected_error2.out %t/error.out\n\n//--- program.eclair\n@def fact1(u32) input.\n@def fact2(u32) output.\n\nfact2(y) :-\n  fact1(x),\n  y = x + 3.\n\nfact2(x + 3) :-\n  fact1(x).\n\nfact2((x + 1) + 2 * x) :-\n  fact1(x).\n\nfact2(x) :-\n  !fact1(x),\n  fact1(x + 4),\n  fact1(x + 4).\n\nfact2((8 - x) / x) :-\n  fact1(x).\n\n@def fact3(u32, string) output.\n\nfact3(123, \"abcd\").\n\n//--- expected_souffle.out\n.decl fact1(arg_0: unsigned)\n\n.input fact1\n\n.decl fact2(arg_0: unsigned)\n\n.output fact2\n\nfact2(y) :-\n  fact1(x),\n  y = (x + 3).\n\nfact2((x + 3)) :-\n  fact1(x).\n\nfact2(((x + 1) + (2 * x))) :-\n  fact1(x).\n\nfact2(x) :-\n  !fact1(x),\n  fact1((x + 4)),\n  fact1((x + 4)).\n\nfact2(((8 - x) / x)) :-\n  fact1(x).\n\n.decl fact3(arg_0: unsigned, arg_1: symbol)\n\n.output fact3\n\nfact3(123, \"abcd\").\n//--- unsupported.eclair\n@def fact1(u32) input.\n\nfact2(y) :-\n  fact1(?).\n//--- expected_error.out\n[error]: Unsupported feature in Souffle\n     ╭──▶ TEST_DIR/unsupported.eclair@4:9-4:10\n     │\n   4 │   fact1(?).\n     •         ┬\n     •         ╰╸ Souffle has no support for holes.\n     •\n     │ Hint: Replace the hole with a variable or literal.\n─────╯\n//--- unsupported2.eclair\n@extern match(string, string).\n//--- expected_error2.out\n[error]: Unsupported feature in Souffle\n     ╭──▶ TEST_DIR/unsupported2.eclair@1:1-1:31\n     │\n   1 │ @extern match(string, string).\n     • ┬─────────────────────────────\n     • ╰╸ Eclair can't transpile extern definitions yet.\n     •\n     │ Hint: Please open a github issue asking for this feature.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/arg_count_mismatch.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def edge(u32, u32, u32) output.\n@def path(u32) output.\nedge(1, 2).\npath(3, 4).\n\n@def fact1(u32, u32) input.\n@def fact2(u32, u32, u32) output.\n\nfact2(x, y) :-\n  fact1(x, y).\n\n@def fact3(u32, u32) input.\n@def fact4(u32) output.\n\nfact4(x, y) :-\n  fact3(x, y).\n\n@def fact5(u32, u32, u32) input.\n@def fact6(u32, u32) output.\n\nfact6(x, y) :-\n  fact5(x, y).\n\n@def fact7(u32, u32) input.\n@def fact8(u32, u32) output.\n\nfact8(x, y) :-\n  fact7(x, y, 123).\n\n@def a(u32, u32) output.\n@def b(u32) input.\n@def c(u32) input.\n\na(x, y) :-\n  b(x, 123),\n  c(y, 456).\n\n//--- expected.out\n[error]: Found an unexpected amount of arguments for 'edge'\n     ╭──▶ TEST_DIR/program.eclair@3:1-3:12\n     │\n   1 │ @def edge(u32, u32, u32) output.\n     • ┬───────────────────────────────\n     • ╰╸ 'edge' is defined with 3 arguments.\n     •\n   3 │ edge(1, 2).\n     • ┬──────────\n     • ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 3 arguments to 'edge'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'path'\n     ╭──▶ TEST_DIR/program.eclair@4:1-4:12\n     │\n   2 │ @def path(u32) output.\n     • ┬─────────────────────\n     • ╰╸ 'path' is defined with 1 argument.\n     •\n   4 │ path(3, 4).\n     • ┬──────────\n     • ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 1 argument to 'path'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'fact2'\n     ╭──▶ TEST_DIR/program.eclair@9:1-10:15\n     │\n   7 │    @def fact2(u32, u32, u32) output.\n     •    ┬────────────────────────────────\n     •    ╰╸ 'fact2' is defined with 3 arguments.\n     •\n   9 │ ╭┤ fact2(x, y) :-\n  10 │ ├┤   fact1(x, y).\n     • │\n     • ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 3 arguments to 'fact2'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'fact4'\n     ╭──▶ TEST_DIR/program.eclair@15:1-16:15\n     │\n  13 │    @def fact4(u32) output.\n     •    ┬──────────────────────\n     •    ╰╸ 'fact4' is defined with 1 argument.\n     •\n  15 │ ╭┤ fact4(x, y) :-\n  16 │ ├┤   fact3(x, y).\n     • │\n     • ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 1 argument to 'fact4'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'fact5'\n     ╭──▶ TEST_DIR/program.eclair@22:3-22:14\n     │\n  18 │ @def fact5(u32, u32, u32) input.\n     • ┬───────────────────────────────\n     • ╰╸ 'fact5' is defined with 3 arguments.\n     •\n  22 │   fact5(x, y).\n     •   ┬──────────\n     •   ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 3 arguments to 'fact5'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'fact7'\n     ╭──▶ TEST_DIR/program.eclair@28:3-28:19\n     │\n  24 │ @def fact7(u32, u32) input.\n     • ┬──────────────────────────\n     • ╰╸ 'fact7' is defined with 2 arguments.\n     •\n  28 │   fact7(x, y, 123).\n     •   ┬───────────────\n     •   ╰╸ 3 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 2 arguments to 'fact7'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'b'\n     ╭──▶ TEST_DIR/program.eclair@35:3-35:12\n     │\n  31 │ @def b(u32) input.\n     • ┬─────────────────\n     • ╰╸ 'b' is defined with 1 argument.\n     •\n  35 │   b(x, 123),\n     •   ┬────────\n     •   ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 1 argument to 'b'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'c'\n     ╭──▶ TEST_DIR/program.eclair@36:3-36:12\n     │\n  32 │ @def c(u32) input.\n     • ┬─────────────────\n     • ╰╸ 'c' is defined with 1 argument.\n     •\n  36 │   c(y, 456).\n     •   ┬────────\n     •   ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 1 argument to 'c'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/arithmetic.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n//--- program1.eclair\n// RUN: %eclair compile --emit ra-transformed %t/program1.eclair | FileCheck %t/program1.eclair\n// CHECK: project\n\n@def number(u32) input.\n@def arithmetic(u32) output.\n\narithmetic(123 + 456).\narithmetic(456 - 123).\narithmetic(123 * 456).\narithmetic(123 / 456).\narithmetic(123 * 456 + 789).\n\narithmetic(x) :-\n  number(x),\n  x = 123 + (456 * 789).\n\n//--- program2.eclair\n// RUN: %eclair compile --emit ra-transformed %t/program2.eclair 2> %t/program2.out\n// RUN: diff -w %t/expected.out %t/program2.out\n\n@def number(u32) input.\n@def arithmetic(u32) output.\n\narithmetic(\"abc\" + 456).\narithmetic(123 - \"abc\").\n\narithmetic(x) :-\n  number(x),\n  x = \"abc\" + (456 * \"def\"),\n  \"abc\" = 1 + 2.\n\n//--- expected.out\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@7:12-7:17\n     │\n   7 │ arithmetic(\"abc\" + 456).\n     • ┬───────────────────────\n     • ╰───────────╸ 1) While checking the type of this..\n     •            ├╸ 2) While checking the type of this..\n     •            ├╸ 3) While checking the type of this..\n     •            ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@8:18-8:23\n     │\n   8 │ arithmetic(123 - \"abc\").\n     • ┬───────────────────────\n     • ╰─────────────────╸ 1) While checking the type of this..\n     •            ╰──────╸ 2) While checking the type of this..\n     •                  ├╸ 3) While checking the type of this..\n     •                  ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@12:7-12:12\n     │\n  10 │ ╭┤ arithmetic(x) :-\n  11 │ │    number(x),\n  12 │ │    x = \"abc\" + (456 * \"def\"),\n     • │    ┬────────────────────────\n     • │    ╰────╸ 2) While checking the type of this..\n     • │        ├╸ 3) While inferring the type of this..\n     • │        ├╸ 4) While checking the type of this..\n     • │        ╰╸ 5) Expected this to be of type 'u32', but it actually has type 'string'.\n  13 │ ├┤   \"abc\" = 1 + 2.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@12:22-12:27\n     │\n  10 │ ╭┤ arithmetic(x) :-\n  11 │ │    number(x),\n  12 │ │    x = \"abc\" + (456 * \"def\"),\n     • │    ┬────────────────────────\n     • │    ╰───────────────────╸ 2) While checking the type of this..\n     • │        ╰───────────────╸ 3) While inferring the type of this..\n     • │                 ╰──────╸ 4) While checking the type of this..\n     • │                       ├╸ 5) While checking the type of this..\n     • │                       ╰╸ 6) Expected this to be of type 'u32', but it actually has type 'string'.\n  13 │ ├┤   \"abc\" = 1 + 2.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program2.eclair@13:3-13:16\n     │\n  10 │ ╭┤ arithmetic(x) :-\n  11 │ │    number(x),\n  12 │ │    x = \"abc\" + (456 * \"def\"),\n  13 │ ├┤   \"abc\" = 1 + 2.\n     • │    ┬────────────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n"
  },
  {
    "path": "tests/typesystem/comparisons.eclair",
    "content": "// NOTE: happy path already tested in lowering tests.\n\n// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32, u32) input.\n@def fact2(u32).\n\nfact2(x) :-\n  fact1(x, _),\n  x != \"abc\",\n  \"abc\" != x.\n\nfact2(x) :-\n  fact1(x, _),\n  x < \"abc\",\n  \"abc\" < x.\n\nfact2(x) :-\n  fact1(x, _),\n  x <= \"abc\",\n  x >= \"abc\",\n  x > \"abc\".\n\n//--- expected.out\n[error]: No output relations found\n     ╭──▶ TEST_DIR/program.eclair@1:1-1:2\n     │\n   1 │ @def fact1(u32, u32) input.\n     • ┬\n     • ╰╸ This module does not produce any results\n     •\n     │ Hint: Add an 'output' qualifier to one of the relations defined in this module.\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@6:3-6:13\n     │\n   4 │ ╭┤ fact2(x) :-\n   5 │ │    fact1(x, _),\n   6 │ │    x != \"abc\",\n     • │    ┬─────────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n   7 │ ├┤   \"abc\" != x.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@7:3-7:13\n     │\n   4 │ ╭┤ fact2(x) :-\n   5 │ │    fact1(x, _),\n   6 │ │    x != \"abc\",\n   7 │ ├┤   \"abc\" != x.\n     • │    ┬─────────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@11:7-11:12\n     │\n   9 │ ╭┤ fact2(x) :-\n  10 │ │    fact1(x, _),\n  11 │ │    x < \"abc\",\n     • │    ┬────────\n     • │    ╰────╸ 2) While checking the type of this..\n     • │        ├╸ 3) While checking the type of this..\n     • │        ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n  12 │ ├┤   \"abc\" < x.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@12:3-12:8\n     │\n   9 │ ╭┤ fact2(x) :-\n  10 │ │    fact1(x, _),\n  11 │ │    x < \"abc\",\n  12 │ ├┤   \"abc\" < x.\n     • │    ┬────────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ├╸ 3) While checking the type of this..\n     • │    ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@16:8-16:13\n     │\n  14 │ ╭┤ fact2(x) :-\n  15 │ │    fact1(x, _),\n  16 │ │    x <= \"abc\",\n     • │    ┬─────────\n     • │    ╰─────╸ 2) While checking the type of this..\n     • │         ├╸ 3) While checking the type of this..\n     • │         ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n  17 │ │    x >= \"abc\",\n  18 │ ├┤   x > \"abc\".\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@17:8-17:13\n     │\n  14 │ ╭┤ fact2(x) :-\n  15 │ │    fact1(x, _),\n  16 │ │    x <= \"abc\",\n  17 │ │    x >= \"abc\",\n     • │    ┬─────────\n     • │    ╰─────╸ 2) While checking the type of this..\n     • │         ├╸ 3) While checking the type of this..\n     • │         ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n  18 │ ├┤   x > \"abc\".\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@18:7-18:12\n     │\n  14 │ ╭┤ fact2(x) :-\n  15 │ │    fact1(x, _),\n  16 │ │    x <= \"abc\",\n  17 │ │    x >= \"abc\",\n  18 │ ├┤   x > \"abc\".\n     • │    ┬────────\n     • │    ╰────╸ 2) While checking the type of this..\n     • │        ├╸ 3) While checking the type of this..\n     • │        ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n"
  },
  {
    "path": "tests/typesystem/duplicate_type_declarations.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def edge(u32, u32) output.\n@def edge(u32, u32) output.\n@def path(u32, u32) output.\n@def edge(u32, u32) output.\n@def path(u32, u32) output.\n\n//--- expected.out\n[error]: Multiple definitions for 'edge'\n     ╭──▶ TEST_DIR/program.eclair@1:1-1:28\n     │\n   1 │ @def edge(u32, u32) output.\n     • ┬──────────────────────────\n     • ╰╸ 'edge' is originally defined here.\n   2 │ @def edge(u32, u32) output.\n     • ┬──────────────────────────\n     • ╰╸ 'edge' is re-defined here.\n     •\n   4 │ @def edge(u32, u32) output.\n     • ┬──────────────────────────\n     • ╰╸ 'edge' is re-defined here.\n     •\n     │ Hint: You can solve this by removing the duplicate definitions for 'edge'.\n─────╯\n\n[error]: Multiple definitions for 'path'\n     ╭──▶ TEST_DIR/program.eclair@3:1-3:28\n     │\n   3 │ @def path(u32, u32) output.\n     • ┬──────────────────────────\n     • ╰╸ 'path' is originally defined here.\n     •\n   5 │ @def path(u32, u32) output.\n     • ┬──────────────────────────\n     • ╰╸ 'path' is re-defined here.\n     •\n     │ Hint: You can solve this by removing the duplicate definitions for 'path'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/extern_definitions.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n//--- program1.eclair\n// RUN: %eclair compile --emit ra-transformed %t/program1.eclair | FileCheck %t/program1.eclair\n// CHECK: project\n\n@def edge(u32, u32) input.\n@def test_externs(u32) output.\n\n@extern constraint1(string).\n@extern func1(u32) u32.\n@extern func2(u32, u32) string.\n\ntest_externs(func1(123)).\ntest_externs(x) :-\n  edge(x, y),\n  constraint1(\"abc\"),\n  constraint1(func2(123, 456)),\n  func1(y) + 1 = x,\n  x = func1(y) + y,\n  func1(x) = func1(y),\n  func1(func1(x)) = y.\n\n//--- program2.eclair\n// RUN: %eclair compile --emit ra-transformed %t/program2.eclair 2> %t/program2.out\n// RUN: diff -w %t/expected.out %t/program2.out\n\n@extern constraint2(string).\n@extern func3(u32) u32.\n@extern func4(u32, u32) string.\n\n@def edge2(u32, u32) input.\n@def test_externs2(u32) output.\n\n// Type errors in functions\ntest_externs2(func3(\"abc\")).\ntest_externs2(func3(123, 123)).\ntest_externs2(func4(123, 123)).\ntest_externs2(func4(123, 123) + 123).\n\n// Type errors in constraints\ntest_externs2(x) :-\n  edge2(x, 123),\n  constraint2(x),\n  constraint2(\"abc\", \"def\").\n\n// Using in wrong context\ntest_externs2(constraint2(\"abc\")).\ntest_externs2(edge2(123, 456)).\ntest_externs2(x) :-\n  edge2(x, 123),\n  func3(x).\n\n// Unknown extern definition\ntest_externs2(x) :-\n  edge2(x, 123),\n  unknown_constraint(x),\n  x = unknown_function(123).\n\n//--- expected.out\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@12:21-12:26\n     │\n  12 │ test_externs2(func3(\"abc\")).\n     • ┬───────────────────────────\n     • ╰────────────────────╸ 1) While checking the type of this..\n     •               ╰──────╸ 2) While checking the type of this..\n     •                     ├╸ 3) While checking the type of this..\n     •                     ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'func3'\n     ╭──▶ TEST_DIR/program2.eclair@13:15-13:30\n     │\n   5 │ @extern func3(u32) u32.\n     • ┬──────────────────────\n     • ╰╸ 'func3' is defined with 1 argument.\n     •\n  13 │ test_externs2(func3(123, 123)).\n     •               ┬──────────────\n     •               ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 1 argument to 'func3'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@14:15-14:30\n     │\n  14 │ test_externs2(func4(123, 123)).\n     • ┬──────────────────────────────\n     • ╰──────────────╸ 1) While checking the type of this..\n     •               ├╸ 2) While checking the type of this..\n     •               ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@15:15-15:30\n     │\n  15 │ test_externs2(func4(123, 123) + 123).\n     • ┬────────────────────────────────────\n     • ╰──────────────╸ 1) While checking the type of this..\n     •               ├╸ 2) While checking the type of this..\n     •               ├╸ 3) While checking the type of this..\n     •               ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program2.eclair@20:15-20:16\n     │\n  18 │ ╭┤ test_externs2(x) :-\n  19 │ │    edge2(x, 123),\n  20 │ │    constraint2(x),\n     • │                ┬\n     • │                ├╸ 2) While checking the type of this..\n     • │                ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n  21 │ ├┤   constraint2(\"abc\", \"def\").\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Found an unexpected amount of arguments for 'constraint2'\n     ╭──▶ TEST_DIR/program2.eclair@21:3-21:28\n     │\n   4 │ @extern constraint2(string).\n     • ┬───────────────────────────\n     • ╰╸ 'constraint2' is defined with 1 argument.\n     •\n  21 │   constraint2(\"abc\", \"def\").\n     •   ┬────────────────────────\n     •   ╰╸ 2 arguments are provided here.\n     •\n     │ Hint: You can solve this by passing exactly 1 argument to 'constraint2'.\n─────╯\n\n[error]: Invalid use of constraint\n     ╭──▶ TEST_DIR/program2.eclair@24:15-24:33\n     │\n   4 │ @extern constraint2(string).\n     • ┬───────────────────────────\n     • ╰╸ Previously defined as a constraint here.\n     •\n  24 │ test_externs2(constraint2(\"abc\")).\n     •               ┬─────────────────\n     •               ╰╸ Expected a function.\n     •\n     │ Hint: Maybe you meant to declare this as a function instead?\n     │ Hint: Remove the invalid constraint.\n─────╯\n\n[error]: Invalid use of constraint\n     ╭──▶ TEST_DIR/program2.eclair@25:15-25:30\n     │\n   8 │ @def edge2(u32, u32) input.\n     • ┬──────────────────────────\n     • ╰╸ Previously defined as a constraint here.\n     •\n  25 │ test_externs2(edge2(123, 456)).\n     •               ┬──────────────\n     •               ╰╸ Expected a function.\n     •\n     │ Hint: Maybe you meant to declare this as a function instead?\n     │ Hint: Remove the invalid constraint.\n─────╯\n\n[error]: Invalid use of function\n     ╭──▶ TEST_DIR/program2.eclair@28:3-28:11\n     │\n   5 │ @extern func3(u32) u32.\n     • ┬──────────────────────\n     • ╰╸ Previously defined as a function here.\n     •\n  28 │   func3(x).\n     •   ┬───────\n     •   ╰╸ Expected a constraint here.\n     •\n     │ Hint: Maybe you meant to declare this an external constraint instead?\n     │ Hint: Remove the invalid function.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program2.eclair@33:3-33:24\n     │\n  33 │   unknown_constraint(x),\n     •   ┬────────────────────\n     •   ╰╸ Could not find a type definition for 'unknown_constraint'.\n     •\n     │ Hint: Add a type definition for 'unknown_constraint'.\n     │ Hint: Add an extern definition for 'unknown_constraint'.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program2.eclair@34:7-34:28\n     │\n  34 │   x = unknown_function(123).\n     •       ┬────────────────────\n     •       ╰╸ Could not find a type definition for 'unknown_function'.\n     •\n     │ Hint: Add an extern definition for 'unknown_function'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/negation.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n//--- program1.eclair\n// RUN: %eclair compile %t/program1.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n@def fact1(u32) input.\n@def fact2(u32) output.\n\nfact2(x) :-\n  fact1(x),\n  !fact1(2),\n  !fact1(\"abc\"),\n  !fact1(x + 1).\n\n//--- expected.out\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program1.eclair@10:10-10:15\n     │\n   7 │ ╭┤ fact2(x) :-\n   8 │ │    fact1(x),\n   9 │ │    !fact1(2),\n  10 │ │    !fact1(\"abc\"),\n     • │    ┬────────────\n     • │    ╰───────╸ 2) While checking the type of this..\n     • │     ╰──────╸ 3) While checking the type of this..\n     • │           ├╸ 4) While checking the type of this..\n     • │           ╰╸ 5) Expected this to be of type 'u32', but it actually has type 'string'.\n  11 │ ├┤   !fact1(x + 1).\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n"
  },
  {
    "path": "tests/typesystem/no_rules_for_type.eclair",
    "content": "// RUN: %eclair compile %s | FileCheck %s\n\n@def edge(u32, u32) input.\n@def chain(u32, u32, u32) input output.\n\n// CHECK: chain\n"
  },
  {
    "path": "tests/typesystem/type_mismatch_in_rule.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32) input.\n@def fact2(u32, string) output.\nfact2(123, x) :-\n  fact1(x).\n\n//--- expected.out\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@4:9-4:10\n     │\n   3 │ ╭┤ fact2(123, x) :-\n   4 │ ├┤   fact1(x).\n     • │          ┬\n     • │          ├╸ 2) While checking the type of this..\n     • │          ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n"
  },
  {
    "path": "tests/typesystem/type_mismatch_in_rule_body.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32, u32) input.\n@def fact2(u32) output.\nfact2(123) :-\n  fact1(\"abc\", 123),\n  fact1(456, \"def\"),\n  fact1(\"abc\", \"def\").\n\n@def fact3(u32) output.\n@def fact4(u32, string) input.\nfact3(123) :-\n  fact4(x, x).\n\n//--- expected.out\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@4:9-4:14\n     │\n   3 │ ╭┤ fact2(123) :-\n   4 │ │    fact1(\"abc\", 123),\n     • │          ┬────\n     • │          ├╸ 2) While checking the type of this..\n     • │          ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n   5 │ │    fact1(456, \"def\"),\n   6 │ ├┤   fact1(\"abc\", \"def\").\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@5:14-5:19\n     │\n   3 │ ╭┤ fact2(123) :-\n   4 │ │    fact1(\"abc\", 123),\n   5 │ │    fact1(456, \"def\"),\n     • │               ┬────\n     • │               ├╸ 2) While checking the type of this..\n     • │               ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n   6 │ ├┤   fact1(\"abc\", \"def\").\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@6:9-6:14\n     │\n   3 │ ╭┤ fact2(123) :-\n   4 │ │    fact1(\"abc\", 123),\n   5 │ │    fact1(456, \"def\"),\n   6 │ ├┤   fact1(\"abc\", \"def\").\n     • │          ┬────\n     • │          ├╸ 2) While checking the type of this..\n     • │          ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@6:16-6:21\n     │\n   3 │ ╭┤ fact2(123) :-\n   4 │ │    fact1(\"abc\", 123),\n   5 │ │    fact1(456, \"def\"),\n   6 │ ├┤   fact1(\"abc\", \"def\").\n     • │                 ┬────\n     • │                 ├╸ 2) While checking the type of this..\n     • │                 ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@11:12-11:13\n     │\n  10 │ ╭┤ fact3(123) :-\n  11 │ ├┤   fact4(x, x).\n     • │             ┬\n     • │             ├╸ 2) While checking the type of this..\n     • │             ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n"
  },
  {
    "path": "tests/typesystem/type_mismatch_in_rule_head.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\nreachable(x, \"abc\") :-\n  edge(x, x).\nreachable(\"abc\", x) :-\n  edge(x, x).\nreachable(\"abc\", \"abc\") :-\n  edge(x, x).\n\n@def fact1(u32) input.\n@def fact2(u32, string) output.\nfact2(x, x) :-\n  fact1(x).\n\n//--- expected.out\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@3:14-3:19\n     │\n   3 │ ╭┤ reachable(x, \"abc\") :-\n     • │               ┬────\n     • │               ├╸ 2) While checking the type of this..\n     • │               ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n   4 │ ├┤   edge(x, x).\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@5:11-5:16\n     │\n   5 │ ╭┤ reachable(\"abc\", x) :-\n     • │            ┬────\n     • │            ├╸ 2) While checking the type of this..\n     • │            ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n   6 │ ├┤   edge(x, x).\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@7:11-7:16\n     │\n   7 │ ╭┤ reachable(\"abc\", \"abc\") :-\n     • │            ┬────\n     • │            ├╸ 2) While checking the type of this..\n     • │            ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n   8 │ ├┤   edge(x, x).\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@7:18-7:23\n     │\n   7 │ ╭┤ reachable(\"abc\", \"abc\") :-\n     • │                   ┬────\n     • │                   ├╸ 2) While checking the type of this..\n     • │                   ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n   8 │ ├┤   edge(x, x).\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@12:10-12:11\n     │\n  12 │ ╭┤ fact2(x, x) :-\n     • │           ┬\n     • │           ├╸ 2) While checking the type of this..\n     • │           ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n  13 │ ├┤   fact1(x).\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n"
  },
  {
    "path": "tests/typesystem/type_mismatch_top_level_atoms.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32, string) output.\n@def fact2(string, string) output.\nfact1(1, 2).\nfact1(\"abc\", \"def\").\nfact1(\"abc\", 2).\nfact2(1, 2).\n\n//--- expected.out\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@3:10-3:11\n     │\n   3 │ fact1(1, 2).\n     • ┬───────────\n     • ╰─────────╸ 1) While checking the type of this..\n     •          ├╸ 2) While checking the type of this..\n     •          ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@4:7-4:12\n     │\n   4 │ fact1(\"abc\", \"def\").\n     • ┬───────────────────\n     • ╰──────╸ 1) While checking the type of this..\n     •       ├╸ 2) While checking the type of this..\n     •       ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@5:7-5:12\n     │\n   5 │ fact1(\"abc\", 2).\n     • ┬───────────────\n     • ╰──────╸ 1) While checking the type of this..\n     •       ├╸ 2) While checking the type of this..\n     •       ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@5:14-5:15\n     │\n   5 │ fact1(\"abc\", 2).\n     • ┬───────────────\n     • ╰─────────────╸ 1) While checking the type of this..\n     •              ├╸ 2) While checking the type of this..\n     •              ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@6:7-6:8\n     │\n   6 │ fact2(1, 2).\n     • ┬───────────\n     • ╰──────╸ 1) While checking the type of this..\n     •       ├╸ 2) While checking the type of this..\n     •       ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@6:10-6:11\n     │\n   6 │ fact2(1, 2).\n     • ┬───────────\n     • ╰─────────╸ 1) While checking the type of this..\n     •          ├╸ 2) While checking the type of this..\n     •          ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/typed_holes.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n//--- program1.eclair\n// RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out\n// RUN: diff -w %t/expected1.out %t/actual1.out\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n\nreachable(x, y) :-\n  edge(x, y),\n  edge(x, ?),\n  x = ?.\n\n//--- expected1.out\n[error]: Found hole\n     ╭──▶ TEST_DIR/program1.eclair@9:11-9:12\n     │\n   7 │ ╭┤ reachable(x, y) :-\n   8 │ │    edge(x, y),\n   9 │ │    edge(x, ?),\n     • │            ┬\n     • │            ├╸ 2) While checking the type of this..\n     • │            ╰╸ 3) Found hole with type 'u32'.\n  10 │ ├┤   x = ?.\n     • │\n     • ╰╸ 1) While checking the type of this..\n     •\n     │ Hint: Possible candidate: x :: 'u32'\n     │ Hint: Possible candidate: y :: 'u32'\n─────╯\n\n[error]: Found hole\n     ╭──▶ TEST_DIR/program1.eclair@10:7-10:8\n     │\n   7 │ ╭┤ reachable(x, y) :-\n   8 │ │    edge(x, y),\n   9 │ │    edge(x, ?),\n  10 │ ├┤   x = ?.\n     • │    ┬────\n     • │    ╰────╸ 2) While checking the type of this..\n     • │        ├╸ 3) While inferring the type of this..\n     • │        ╰╸ 4) Found hole with type 'u32'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n     •\n     │ Hint: Possible candidate: x :: 'u32'\n     │ Hint: Possible candidate: y :: 'u32'\n─────╯\n//--- program2.eclair\n// RUN: %eclair compile %t/program2.eclair 2> %t/actual2.out\n// RUN: diff %t/expected2.out %t/actual2.out\n\n@def fact(string, u32, u32) output.\n\nfact(?, 42, ?).\n\n//--- expected2.out\n[error]: Found hole\n     ╭──▶ TEST_DIR/program2.eclair@6:6-6:7\n     │\n   6 │ fact(?, 42, ?).\n     • ┬──────────────\n     • ╰─────╸ 1) While checking the type of this..\n     •      ├╸ 2) While checking the type of this..\n     •      ╰╸ 3) Found hole with type 'string'.\n─────╯\n\n[error]: Found hole\n     ╭──▶ TEST_DIR/program2.eclair@6:13-6:14\n     │\n   6 │ fact(?, 42, ?).\n     • ┬──────────────\n     • ╰────────────╸ 1) While checking the type of this..\n     •             ├╸ 2) While checking the type of this..\n     •             ╰╸ 3) Found hole with type 'u32'.\n─────╯\n//--- program3.eclair\n// RUN: %eclair compile %t/program3.eclair 2> %t/actual3.out\n// RUN: diff -w %t/expected3.out %t/actual3.out\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\n\nreachable(x, z) :-\n  edge(x, ?),\n  reachable(y, z).\n\n//--- expected3.out\n[error]: Found unconstrained variable\n     ╭──▶ TEST_DIR/program3.eclair@9:13-9:14\n     │\n   7 │ ╭┤ reachable(x, z) :-\n   8 │ │    edge(x, ?),\n   9 │ ├┤   reachable(y, z).\n     • │              ┬\n     • │              ╰╸ The variable 'y' only occurs once.\n     • │\n     • ╰╸ This rule contains no other references to 'y'.\n     •\n     │ Hint: Replace the variable with a wildcard ('_').\n     │ Hint: Use the variable in another rule clause.\n─────╯\n\n[error]: Found hole\n     ╭──▶ TEST_DIR/program3.eclair@8:11-8:12\n     │\n   7 │ ╭┤ reachable(x, z) :-\n   8 │ │    edge(x, ?),\n     • │            ┬\n     • │            ├╸ 2) While checking the type of this..\n     • │            ╰╸ 3) Found hole with type 'u32'.\n   9 │ ├┤   reachable(y, z).\n     • │\n     • ╰╸ 1) While checking the type of this..\n     •\n     │ Hint: Possible candidate: x :: 'u32'\n     │ Hint: Possible candidate: y :: 'u32'\n     │ Hint: Possible candidate: z :: 'u32'\n─────╯\n"
  },
  {
    "path": "tests/typesystem/unification_failure.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def fact1(u32, u32) input.\n@def fact2(u32).\nfact2(x) :-\n  fact1(x, _),\n  x = \"abc\".\n\n@def fact3(u32, u32) input.\n@def fact4(u32).\nfact4(x) :-\n  fact3(x, _),\n  \"abc\" = x.\n\n@def fact5(string, string) input.\n@def fact6(string).\nfact6(x) :-\n  fact5(x, _),\n  x = 1.\n\n@def fact7(string, string) input.\n@def fact8(string).\nfact8(x) :-\n  fact7(x, _),\n  1 = x.\n\n@def fact9(string, u32) input.\n@def fact10(string).\nfact10(x) :-\n  fact9(x, y),\n  x = y.\n\n@def fact11(u32, string) input.\n@def fact12(u32).\nfact12(x) :-\n  x = y,\n  fact11(x, z),\n  y = z.\n\n@def fact13(u32, string) input.\n@def fact14(u32).\nfact14(x) :-\n  x = y,\n  y = z,\n  fact13(x, z).\n\n@def fact15(u32, string) input.\n@def fact16(u32).\nfact16(x) :-\n  x = y,\n  y = z,\n  z = a,\n  fact15(x, a).\n\n@def fact17(u32, string) input.\n@def fact18(u32) output.\nfact18(x) :-\n  x = y,\n  y = z,\n  fact17(x, a),\n  z = a.\n\n//--- expected.out\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@5:3-5:12\n     │\n   3 │ ╭┤ fact2(x) :-\n   4 │ │    fact1(x, _),\n   5 │ ├┤   x = \"abc\".\n     • │    ┬────────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@11:3-11:12\n     │\n   9 │ ╭┤ fact4(x) :-\n  10 │ │    fact3(x, _),\n  11 │ ├┤   \"abc\" = x.\n     • │    ┬────────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@17:3-17:8\n     │\n  15 │ ╭┤ fact6(x) :-\n  16 │ │    fact5(x, _),\n  17 │ ├┤   x = 1.\n     • │    ┬────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@23:3-23:8\n     │\n  21 │ ╭┤ fact8(x) :-\n  22 │ │    fact7(x, _),\n  23 │ ├┤   1 = x.\n     • │    ┬────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@29:3-29:8\n     │\n  27 │ ╭┤ fact10(x) :-\n  28 │ │    fact9(x, y),\n  29 │ ├┤   x = y.\n     • │    ┬────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@36:3-36:8\n     │\n  33 │ ╭┤ fact12(x) :-\n  34 │ │    x = y,\n  35 │ │    fact11(x, z),\n  36 │ ├┤   y = z.\n     • │    ┬────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@43:13-43:14\n     │\n  40 │ ╭┤ fact14(x) :-\n  41 │ │    x = y,\n  42 │ │    y = z,\n  43 │ ├┤   fact13(x, z).\n     • │              ┬\n     • │              ├╸ 2) While checking the type of this..\n     • │              ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type mismatch\n     ╭──▶ TEST_DIR/program.eclair@51:13-51:14\n     │\n  47 │ ╭┤ fact16(x) :-\n  48 │ │    x = y,\n  49 │ │    y = z,\n  50 │ │    z = a,\n  51 │ ├┤   fact15(x, a).\n     • │              ┬\n     • │              ├╸ 2) While checking the type of this..\n     • │              ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'.\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n\n[error]: Type unification failure\n     ╭──▶ TEST_DIR/program.eclair@59:3-59:8\n     │\n  55 │ ╭┤ fact18(x) :-\n  56 │ │    x = y,\n  57 │ │    y = z,\n  58 │ │    fact17(x, a),\n  59 │ ├┤   z = a.\n     • │    ┬────\n     • │    ├╸ 2) While checking the type of this..\n     • │    ╰╸ 3) While unifying these types..\n     • │\n     • ╰╸ 1) While checking the type of this..\n─────╯\n"
  },
  {
    "path": "tests/typesystem/unknown_atom_in_rule_body.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def path(u32, u32) output.\n\npath(x, y) :-\n  edge(x, y).\n\n//--- expected.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@3:6-3:7\n     │\n   3 │ ╭┤ path(x, y) :-\n     • │       ┬\n     • │       ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation.\n   4 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'x'.\n     •\n     │ Hint: Use the variable 'x' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@3:9-3:10\n     │\n   3 │ ╭┤ path(x, y) :-\n     • │          ┬\n     • │          ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation.\n   4 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'y'.\n     •\n     │ Hint: Use the variable 'y' as an argument in a relation.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@4:3-4:13\n     │\n   4 │   edge(x, y).\n     •   ┬─────────\n     •   ╰╸ Could not find a type definition for 'edge'.\n     •\n     │ Hint: Add a type definition for 'edge'.\n     │ Hint: Add an extern definition for 'edge'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/unknown_atom_in_rule_head.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\n@def edge(u32, u32) input.\n\npath(x, y) :-\n  edge(x, y).\n\n@def out(u32) output.\n\n//--- expected.out\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@3:1-4:14\n     │\n   3 │ ╭┤ path(x, y) :-\n   4 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ Could not find a type definition for 'path'.\n     •\n     │ Hint: Add a type definition for 'path'.\n     │ Hint: Add an extern definition for 'path'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/unknown_atoms.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff -w %t/expected.out %t/actual.out\n\n//--- program.eclair\ntop_level_atom(1).\n\npath(x, y) :-\n  edge(x, y).\n\n@def result(u32) output.\n\n//--- expected.out\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@3:6-3:7\n     │\n   3 │ ╭┤ path(x, y) :-\n     • │       ┬\n     • │       ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation.\n   4 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'x'.\n     •\n     │ Hint: Use the variable 'x' as an argument in a relation.\n─────╯\n\n[error]: Ungrounded variable\n     ╭──▶ TEST_DIR/program.eclair@3:9-3:10\n     │\n   3 │ ╭┤ path(x, y) :-\n     • │          ┬\n     • │          ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation.\n   4 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ This contains no clauses that refer to 'y'.\n     •\n     │ Hint: Use the variable 'y' as an argument in a relation.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@1:1-1:19\n     │\n   1 │ top_level_atom(1).\n     • ┬─────────────────\n     • ╰╸ Could not find a type definition for 'top_level_atom'.\n     •\n     │ Hint: Add a type definition for 'top_level_atom'.\n     │ Hint: Add an extern definition for 'top_level_atom'.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@3:1-4:14\n     │\n   3 │ ╭┤ path(x, y) :-\n   4 │ ├┤   edge(x, y).\n     • │\n     • ╰╸ Could not find a type definition for 'path'.\n     •\n     │ Hint: Add a type definition for 'path'.\n     │ Hint: Add an extern definition for 'path'.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@4:3-4:13\n     │\n   4 │   edge(x, y).\n     •   ┬─────────\n     •   ╰╸ Could not find a type definition for 'edge'.\n     •\n     │ Hint: Add a type definition for 'edge'.\n     │ Hint: Add an extern definition for 'edge'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/unknown_top_level_atoms.eclair",
    "content": "// RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test\n// RUN: split-file %t/input.test %t\n\n// RUN: %eclair compile %t/program.eclair 2> %t/actual.out\n// RUN: diff %t/expected.out %t/actual.out\n\n//--- program.eclair\nedge(1, 2).\npath(3, 4).\n\n@def result(u32) output.\n\n//--- expected.out\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@1:1-1:12\n     │\n   1 │ edge(1, 2).\n     • ┬──────────\n     • ╰╸ Could not find a type definition for 'edge'.\n     •\n     │ Hint: Add a type definition for 'edge'.\n     │ Hint: Add an extern definition for 'edge'.\n─────╯\n\n[error]: Missing type definition\n     ╭──▶ TEST_DIR/program.eclair@2:1-2:12\n     │\n   2 │ path(3, 4).\n     • ┬──────────\n     • ╰╸ Could not find a type definition for 'path'.\n     •\n     │ Hint: Add a type definition for 'path'.\n     │ Hint: Add an extern definition for 'path'.\n─────╯\n"
  },
  {
    "path": "tests/typesystem/valid.eclair",
    "content": "// RUN: %eclair compile %s --emit ra-transformed | FileCheck %s\n// CHECK: project\n\n@def fact1(u32, string).\n@def fact2(string, string).\nfact1(1, \"a\").\nfact2(\"abc\", \"def\").\n\n@def edge(u32, u32) input.\n@def reachable(u32, u32) output.\nreachable(x, y) :- edge(x, y).\nreachable(1, 2) :- edge(x, x).\n\n@def fact3(u32, u32) input.\n@def fact4(u32).\nfact4(123) :-\n  fact3(123, 456).\n\n@def fact5(u32).\n@def fact6(u32, u32) input.\nfact5(123) :-\n  fact6(x, x).\n\n@def fact7(u32) input.\n@def fact8(u32, string).\n@def fact9(string) input.\nfact8(x, y) :-\n  fact7(x),\n  fact9(y).\n\n@def fact10(u32, u32, string) input.\n@def fact11(u32).\nfact11(x) :-\n  fact10(x, _, _),\n  fact10(_, x, _).\n\n@def fact12(u32, u32) input.\n@def fact13(u32).\nfact13(x) :-\n  fact12(x, _),\n  x = 123.\n\nfact13(x) :-\n  fact12(x, _),\n  123 = x.\n\nfact13(x) :-\n  fact12(x, y),\n  x = y.\n\nfact13(x) :-\n  fact12(x, _),\n  123 = 456.\n\nfact13(x) :-\n  fact12(x, _),\n  \"abc\" = \"def\".\n\n@def fact14(string, string) input.\n@def fact15(string).\nfact15(x) :-\n  fact14(x, _),\n  \"abc\" = x.\n\nfact15(x) :-\n  fact14(x, _),\n  x = \"abc\".\n\n@def fact16(u32, u32) input.\n@def fact17(u32) output.\nfact17(x) :-\n  x = y,\n  fact16(x, z),\n  y = z.\n\nfact17(x) :-\n  x = y,\n  y = z,\n  fact16(x, z).\n\n@def fact18(u32) input.\n@def fact19(u32) output.\n\nfact19(x) :-\n  fact18(x),\n  !fact18(2),\n  !fact18(x + 1).\n"
  },
  {
    "path": "tests/utils/extract_snippet",
    "content": "#!/bin/bash\n# Print lines starting from a match on a pattern ($2), up to first empty line.\n# Use sed again to trim the trailing newline\nsed -n \"/^.*$2.*/,/^\\$/p\" $1 | sed '/^$/d'\n"
  }
]