[
  {
    "path": ".formatter.exs",
    "content": "# Used by \"mix format\"\nlocals_without_parens = [allow: 2]\n\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"],\n  locals_without_parens: locals_without_parens,\n  export: [locals_without_parens: locals_without_parens]\n]\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    name: OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}\n    env:\n      MIX_ENV: test\n    strategy:\n      matrix:\n        include:\n          - elixir: \"1.14.5\"\n            otp: \"26.0\"\n            testArgs: \"--exclude=lts_only\"\n          - elixir: \"1.15.4\"\n            otp: \"26.0\"\n            testArgs: \"--exclude=lts_only\"\n          - elixir: \"1.16.0\"\n            otp: \"26.0\"\n            testArgs: \"--exclude=lts_only\"\n          - elixir: \"1.17.3\"\n            otp: \"27.0\"\n            testArgs: \"--exclude=lts_only\"\n          - elixir: \"1.18.0\"\n            otp: \"27.0\"\n            testArgs: \"--exclude=lts_only\"\n          - elixir: \"1.18.4\"\n            otp: \"27.0\"\n            testArgs: \"--exclude=lts_only\"\n          - elixir: \"1.19.0\"\n            otp: \"28.1\"\n            testArgs: \"\"\n    steps:\n      - uses: actions/checkout@v2\n      - uses: erlef/setup-beam@v1\n        with:\n          otp-version: ${{matrix.otp}}\n          elixir-version: ${{matrix.elixir}}\n      - name: Install Dependencies\n        run: mix deps.get\n      - name: Check compile warnings\n        run: mix compile --warnings-as-errors\n      - name: Check format\n        run: mix format --check-formatted\n      # TODO add dialyzer?\n      - name: Unit tests\n        run: mix test ${{ matrix.testArgs }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\ndune-*.tar\n\n# Temporary files, for example, from tests.\n/tmp/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## Dev\n\n## v0.3.15 (2025-10-19)\n\n- Support Elixir 1.19\n\n## v0.3.14 (2025-07-29)\n\n### Security fixes\n\n- Use safe shims for chardata -> string conversions (`List.to_string/1`, ...) in\n  `Dune.Allowlist.Default`\n- Restrict more unsafe modules and functions in `Dune.Allowlist.Default`:\n  - `:unicode`\n  - `:erts_debug.flat_size`\n\n## v0.3.13 (2025-07-27)\n\n- Fix older versions pre-1.18 that don't have `JSON`\n\n## v0.3.12 (2025-07-27)\n\n- `Dune.Allowlist.Default` exposes a safe shim for the `JSON` module\n\n## v0.3.11 (2024-12-21)\n\n- Enable support for Elixir 1.18\n\n## v0.3.10 (2024-07-14)\n\n- Enable support for Elixir 1.17\n\n## v0.3.9 (2024-06-25)\n\n- `Dune.Allowlist.Default` allows the `Version` module\n\n## v0.3.8 (2024-05-26)\n\n### Bug fixes\n\n- Make sure the `Duration` atom is available\n\n## v0.3.7 (2024-05-26)\n\n### Bug fixes\n\n- Fix incorrect type definitions, remove unused ones\n\n### Enhancements\n\n- `Dune.Allowlist.Default` allows the new `Duration` module and new kernel\n  functions from Elixir 1.17\n- Add an `:inspect_sort_maps` option for deterministic outputs\n- Capture and return parser warnings in `stdio`\n\n## v0.3.6 (2023-12-23)\n\n- Support Elixir 1.16\n- `Dune.Allowlist.Default` allows `**/2`\n\n## v0.3.5 (2023-11-10)\n\n### Enhancements\n\n- Prepare Elixir 1.16 support(handle line-column positions in diagnostics)\n\n## v0.3.4 (2023-09-14)\n\n### Bug fixes\n\n- Fix `UndefinedFunctionError` when using external modules in a custom allowlist\n\n## v0.3.3 (2023-08-13)\n\n### Bug fixes\n\n- Fix vulnerability allowing an attacker to crash the VM using bitstrings\n\n## v0.3.2 (2023-08-12)\n\n### Enhancements\n\n- `dbg/1` uses pretty printing\n\n### Bug fixes\n\n- Fix error message on restricted `dbg/0`\n\n## v0.3.1 (2023-08-12)\n\n### Enhancements\n\n- Add support for `dbg/1`\n\n### Bug fixes\n\n- Properly distinguish user code `throw/1` from internal ones\n\n## v0.3.0 (2023-08-09)\n\n### Breaking changes\n\n- Drop support for Elixir 1.13\n- Compile errors are now returned as a separate type `:compile_error`\n\n### Enhancements\n\n- Support Elixir 1.15\n- Capture compile diagnostics (Elixir >= 1.15)\n\n### Bug fixes\n\n- Better handle `UndefinedFunctionError` for dynamic module names\n\n## v0.2.6 (2022-10-17)\n\n### Enhancements\n\n- Support Elixir 1.14\n\n## v0.2.5 (2022-08-25)\n\n### Bug fixes\n\n- Restrict the use of `:counters` in `Dune.Allowlist.Default`, since it can leak\n  memory\n\n## v0.2.4 (2022-07-13)\n\n### Bug fixes\n\n- Validate module names in `defmodule`, reject `nil` or booleans\n\n## v0.2.3 (2022-04-13)\n\n### Bug fixes\n\n- `Dune.string_to_quoted/2` quotes modules with `.` correctly\n- OTP 25 regression: keep a clean stacktrace for exceptions\n\n## v0.2.2 (2022-04-05)\n\n### Enhancements\n\n- Add `Dune.string_to_quoted/2` to make it possible to visualize AST\n- Merged parsing and eval options in a single `Dune.Opts` for simplicity\n- Add a `pretty` option to inspect result\n- Better error message when `def/2` and `defp/2` called outside a module\n\n### Breaking changes\n\n- Removed Dune.Parser.Opts and Dune.Eval.Opts\n\n## v0.2.1 (2022-03-19)\n\n### Bug fixes\n\n- Handle default arguments in functions\n- Handle conflicting `def` and `defp` with same name/arity\n\n## v0.2.0 (2022-01-02)\n\n### Breaking changes\n\n- Support Elixir 1.13, drop support for 1.12\n- This fixes a [bug in atoms](https://github.com/elixir-lang/elixir/pull/11313)\n  was due to the Elixir parser\n\n## v0.1.2 (2021-10-17)\n\n### Enhancements\n\n- Allow safe functions from the `:erlang` module\n\n### Bug fixes\n\n- Fix bug when calling custom function in nested AST\n\n## v0.1.1 (2021-10-16)\n\n### Bug fixes\n\n- Prevent atom leaks due to `Code.string_to_quoted/2` not respecting\n  `static_atoms_encoder`\n- Handle Elixir 1.12 bug on single atom ASTs\n- Handle atoms prefixed with `Elixir.` properly\n- Fix inspect for quoted atoms\n\n## v0.1.0 (2021-09-19)\n\n- Initial release\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2021 Sabiwara\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Dune\n\n[![Hex Version](https://img.shields.io/hexpm/v/dune.svg)](https://hex.pm/packages/dune)\n[![docs](https://img.shields.io/badge/docs-hexpm-blue.svg)](https://hexdocs.pm/dune/)\n[![CI](https://github.com/functional-rewire/dune/workflows/CI/badge.svg)](https://github.com/functional-rewire/dune/actions?query=workflow%3ACI)\n\nA sandbox for Elixir to safely evaluate untrusted code from user input.\n\n[**Try it out on our online playground!**](https://playground.functional-rewire.com/)\n\n`Dune` can be useful to develop playgrounds, online REPL, coding games, or\ncustomizable business logic.\n\n**Warning:** `Dune` cannot offer strong security guarantees (see the\n[Security guarantees](#security-guarantees) section below). Besides, it is still\nearly stage: expect bugs and vulnerabilities.\n\n## Features\n\n- allowlist mechanism (customizable) to restrict execution to safe modules and\n  functions: no access to environment variables, file system, network...\n- code executed in an isolated process\n- execution within configurable limits: timeout, maximum reductions and memory\n  (inspired by [Luerl](https://github.com/rvirding/luerl))\n- captured standard output\n- atoms, without atom leaks: parsing and runtime do not\n  [leak atoms](https://hexdocs.pm/elixir/String.html#to_atom/1) (i.e. does not\n  keep\n  [filling the atom table](https://learnyousomeerlang.com/starting-out-for-real#atoms)\n  until the VM crashes)\n- modules, without actual module creation: Dune does not let users define any\n  actual module (would leak memory and modify the state of the VM globally), but\n  `defmodule` simulates the basic behavior of a module, including private and\n  recursive functions\n\n```elixir\niex> Dune.eval_string(\"IO.puts(\\\"Hello world!\\\")\")\n%Dune.Success{inspected: \":ok\", stdio: \"Hello world!\\n\", value: :ok}\n\niex> Dune.eval_string(\"File.cwd!()\")\n%Dune.Failure{message: \"** (DuneRestrictedError) function File.cwd!/0 is restricted\", type: :restricted}\n\niex> Dune.eval_string(\"List.duplicate(:spam, 100_000)\")\n%Dune.Failure{message: \"Execution stopped - memory limit exceeded\", stdio: \"\", type: :memory}\n\niex> Dune.eval_string(\"Enum.product(1..100_000)\")\n%Dune.Failure{message: \"Execution stopped - reductions limit exceeded\", stdio: \"\", type: :reductions}\n```\n\nThe list of modules and functions authorized by default is defined by the\n[`Dune.Allowlist.Default`](https://hexdocs.pm/dune/Dune.Allowlist.Default.html#module-allowed-modules-functions)\nmodule, but this list can be extended and customized (at your own risk!) using\n[`Dune.Allowlist`](https://hexdocs.pm/dune/Dune.Allowlist.html).\n\nIf you need to keep the state between evaluations, you might consider\n[`Dune.Session`](https://hexdocs.pm/dune/Dune.Session.html):\n\n```elixir\niex> Dune.Session.new()\n...> |> Dune.Session.eval_string(\"x = 1\")\n...> |> Dune.Session.eval_string(\"x + 2\")\n#Dune.Session<last_result: %Dune.Success{inspected: \"3\", stdio: \"\", value: 3}, ...>\n```\n\n`Dune.string_to_quoted/2` returns the AST corresponding to the provided\n`string`, without leaking atoms:\n\n```elixir\niex> Dune.string_to_quoted(\"foo(:bar)\").inspected\n\"{:foo, [line: 1], [:bar]}\"\n```\n\n## Limitations\n\n`Dune` supports a fair subset of the base language, but it cannot safely support\nadvanced features (at least at this stage) such as:\n\n- custom structs / behaviours / protocols\n- concurrency / processes / OTP\n- metaprogramming\n\n## Security guarantees\n\nBecause of the approch being used, Dune cannot offer strong security guarantees,\nand should not be considered a sufficient security layer by itself.\n\nA best-effort approach is made to prevent attackers from getting outside of the\noriginal process and from calling any function/macro outside of the allowlist.\nHowever, it is impossible to prove that all escape paths have been completely\nblocked. Due to how the Erlang VM works, an attacker able to escape the sandbox\ncould get full access to the VM without restriction.\n\nSee the\n[EEF guidelines about sandboxing](https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/sandboxing)\nfor more information.\n\nUse at your own risk and avoid running it directly on a server with any\nsensitive access, e.g. to a database.\n\n## Installation\n\nThe package can be installed by adding `dune` to your list of dependencies in\n`mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:dune, \"~> 0.3.15\"}\n  ]\nend\n```\n\nDocumentation can be found at\n[https://hexdocs.pm/dune](https://hexdocs.pm/dune).\n\n## FAQ\n\n### Why can I still create atoms?\n\nAtoms are converted by the parser and mapped to always use a given set of atoms.\nSo when you type `foo = bar(:baz)`, the atoms `:foo`, `:bar` and `:baz` won't\nactually be created, but other atoms like `:atom1`, `:atom2` and `:atom3` are\ngoing to be used instead.\n\nSo even if many user run various codes using many different variable and\nfunction names, they will all pull from the same pool of atoms.\n\n### Why can I still create modules?\n\nModules are being defined globally within the VM, making it both an isolation\nconcern and a memory concern. But modules are an important part of the Elixir\nlanguage, and are especially important for learning platforms.\n\nDune implements an alternative `defmodule`, relying on maps of anonymous\nfunctions at runtime and should reproduce the basic behavior of actual modules,\nwith support for recursive and private functions.\n\n### Why is the behavior different than Elixir when doing X?\n\nAs explained above, some parts of the language are actually being completely\nreimplemented because the original version could not be safely sandboxed: atoms,\nmodules... While these alternative implementation aim to be as close as possible\nto the original ones, they might differ in some cases, because the code being\nexecuted is actually different.\n\n### Why can't I do X?\n\nSome parts of the language are being restricted because they present a direct\nsecurity risk, while some other would need to be reimplemented in an alternative\nway and therefore need a consequent amount of work that hasn't been done yet.\n"
  },
  {
    "path": "lib/dune/allowlist/default.ex",
    "content": "defmodule Dune.Allowlist.Default do\n  @moduledoc \"\"\"\n  The default `Dune.Allowlist` module to be used to allow or restrict\n  functions and macros that can be safely executed.\n\n  ## Examples\n\n      iex> Dune.Allowlist.Default.fun_status(Kernel, :+, 2)\n      :allowed\n\n      iex> Dune.Allowlist.Default.fun_status(String, :to_atom, 1)\n      :restricted\n\n      iex> Dune.Allowlist.Default.fun_status(Atom, :to_string, 1)\n      {:shimmed, Dune.Shims.Atom, :to_string}\n\n      iex> Dune.Allowlist.Default.fun_status(Kernel, :foo, 1)\n      :undefined_function\n\n      iex> Dune.Allowlist.Default.fun_status(Bar, :foo, 1)\n      :undefined_module\n\n      iex> Dune.Allowlist.Default.fun_status(Kernel.SpecialForms, :quote, 2)\n      :restricted\n\n  ## Allowed modules / functions\n\n  __DUNE_ALLOWLIST_FUNCTIONS__\n\n  \"\"\"\n\n  use Dune.Allowlist\n\n  alias Dune.Shims\n\n  @special_forms_allowed ~w[\n    {}\n    %{}\n    <<>>\n    =\n    ^\n    case\n    cond\n    fn\n    for\n    with\n    ::\n    __aliases__\n  ]a\n\n  @kernel_operators ~w[\n    |>\n    +\n    ++\n    -\n    --\n    *\n    **\n    /\n    <>\n    ==\n    ===\n    !=\n    !==\n    =~\n    >\n    >=\n    <\n    <=\n    and\n    or\n    &&\n    ||\n    !\n    ..\n    ..//\n  ]a\n\n  @kernel_guards ~w[\n    is_integer\n    is_binary\n    is_bitstring\n    is_atom\n    is_boolean\n    is_integer\n    is_float\n    is_number\n    is_list\n    is_map\n    is_map_key\n    is_nil\n    is_reference\n    is_tuple\n    is_exception\n    is_struct\n    is_function\n  ]a\n\n  @kernel_macros ~w[\n    if\n    unless\n    in\n    match?\n    then\n    tap\n    raise\n  ]a\n\n  @kernel_sigils ~w[\n    sigil_C\n    sigil_D\n    sigil_N\n    sigil_R\n    sigil_S\n    sigil_T\n    sigil_U\n    sigil_c\n    sigil_r\n    sigil_s\n    sigil_w\n  ]a\n\n  @kernel_functions ~w[\n    abs\n    binary_part\n    bit_size\n    byte_size\n    ceil\n    div\n    elem\n    floor\n    get_and_update_in\n    get_in\n    hd\n    length\n    make_ref\n    map_size\n    max\n    min\n    not\n    pop_in\n    put_elem\n    put_in\n    rem\n    round\n    self\n    tl\n    trunc\n    tuple_size\n    update_in\n  ]a\n\n  # TODO Remove when dropping support for Elixir 1.16\n  extra_kernel_functions =\n    if System.version() |> Version.compare(\"1.17.0-rc.0\") != :lt,\n      do: [:to_timeout, :is_non_struct_map],\n      else: []\n\n  @kernel_allowed extra_kernel_functions ++\n                    @kernel_operators ++\n                    @kernel_guards ++ @kernel_macros ++ @kernel_sigils ++ @kernel_functions\n\n  @kernel_shims [\n    apply: {Shims.Kernel, :safe_apply},\n    inspect: {Shims.Kernel, :safe_inspect},\n    to_string: {Shims.Kernel, :safe_to_string},\n    to_charlist: {Shims.Kernel, :safe_to_charlist},\n    sigil_w: {Shims.Kernel, :safe_sigil_w},\n    sigil_W: {Shims.Kernel, :safe_sigil_W},\n    throw: {Shims.Kernel, :safe_throw},\n    dbg: {Shims.Kernel, :safe_dbg}\n  ]\n\n  @erlang_allowed [\n    :*,\n    :+,\n    :++,\n    :-,\n    :--,\n    :/,\n    :\"/=\",\n    :<,\n    :\"=/=\",\n    :\"=:=\",\n    :\"=<\",\n    :==,\n    :>,\n    :>=,\n    :abs,\n    :adler32,\n    :adler32_combine,\n    :and,\n    :append_element,\n    :band,\n    :binary_part,\n    :binary_to_float,\n    :binary_to_integer,\n    :binary_to_list,\n    :bit_size,\n    :bitstring_to_list,\n    :bnot,\n    :bor,\n    :bsl,\n    :bsr,\n    :bxor,\n    :byte_size,\n    :ceil,\n    :convert_time_unit,\n    :crc32,\n    :crc32_combine,\n    :date,\n    :delete_element,\n    :div,\n    :element,\n    :float,\n    :float_to_binary,\n    :float_to_list,\n    :floor,\n    :hd,\n    :insert_element,\n    :integer_to_binary,\n    :integer_to_list,\n    :iolist_size,\n    :iolist_to_binary,\n    :iolist_to_iovec,\n    :is_atom,\n    :is_binary,\n    :is_bitstring,\n    :is_boolean,\n    :is_float,\n    :is_function,\n    :is_integer,\n    :is_list,\n    :is_map,\n    :is_map_key,\n    :is_number,\n    :is_pid,\n    :is_port,\n    :is_record,\n    :is_reference,\n    :is_tuple,\n    :length,\n    :list_to_binary,\n    :list_to_bitstring,\n    :list_to_float,\n    :list_to_integer,\n    :localtime,\n    :localtime_to_universaltime,\n    :make_ref,\n    :make_tuple,\n    :map_get,\n    :map_size,\n    :max,\n    :md5,\n    :md5_final,\n    :md5_init,\n    :md5_update,\n    :min,\n    :monotonic_time,\n    :not,\n    :or,\n    :phash2,\n    :ref_to_list,\n    :rem,\n    :round,\n    :setelement,\n    :size,\n    :split_binary,\n    :system_time,\n    :time,\n    :time_offset,\n    :timestamp,\n    :tl,\n    :trunc,\n    :tuple_size,\n    :tuple_to_list,\n    :unique_integer,\n    :universaltime,\n    :universaltime_to_localtime,\n    :xor\n  ]\n\n  @erlang_shims [\n    apply: {Shims.Kernel, :safe_apply}\n  ]\n\n  @io_shims [\n    puts: {Shims.IO, :puts},\n    inspect: {Shims.IO, :inspect},\n    chardata_to_string: {Shims.List, :to_string}\n  ]\n\n  allow Kernel.SpecialForms, only: @special_forms_allowed\n\n  allow Kernel, only: @kernel_allowed, shims: @kernel_shims\n  allow Access, :all\n  allow String, except: ~w[to_atom to_existing_atom]a\n  allow Regex, :all\n  allow Map, :all\n  allow MapSet, :all\n  allow Keyword, :all\n  allow Tuple, :all\n  allow List, shims: [to_string: {Shims.List, :to_string}], except: ~w[to_atom to_existing_atom]a\n  allow Enum, shims: [join: {Shims.Enum, :join}, map_join: {Shims.Enum, :map_join}]\n  # TODO double check\n  allow Stream, :all\n  allow Range, :all\n  allow Integer, :all\n  allow Float, :all\n\n  allow Atom,\n    except: ~w[to_char_list]a,\n    shims: [to_string: {Shims.Atom, :to_string}, to_charlist: {Shims.Atom, :to_charlist}]\n\n  if Code.ensure_loaded?(JSON) do\n    allow JSON,\n      only: ~w[decode decode!]a,\n      shims: Enum.map(~w[protocol_encode encode! encode_to_iodata!]a, &{&1, {Shims.JSON, &1}})\n  end\n\n  allow Date, :all\n  allow DateTime, :all\n  allow NaiveDateTime, :all\n\n  # TODO Remove when dropping support for Elixir 1.16\n  if System.version() |> Version.compare(\"1.17.0-rc.0\") != :lt do\n    allow Duration, :all\n  end\n\n  allow Calendar, except: ~w[put_time_zone_database]a\n  allow Calendar.ISO, :all\n  allow Time, :all\n  allow Base, :all\n  allow URI, :all\n  allow Version, :all\n  allow Bitwise, :all\n  allow Function, only: ~w[identity]a\n  allow IO, only: ~w[iodata_length iodata_to_binary]a, shims: @io_shims\n  allow Process, only: [:sleep]\n\n  allow :erlang, only: @erlang_allowed, shims: @erlang_shims\n  allow :math, :all\n  allow :binary, :all\n  allow :lists, :all\n  allow :array, :all\n  allow :maps, :all\n  allow :gb_sets, :all\n  allow :gb_trees, :all\n  allow :ordsets, :all\n  allow :orddict, :all\n  allow :proplists, :all\n  allow :queue, :all\n  allow :string, :all\n  allow :rand, :all\n  # note: :unicode is not safe and should be shimmed due to \"structural sharing bombs\"\n\n  # note: flat_size is unsafe due to \"structural sharing bombs\"\n  allow :erts_debug, only: ~w[same size size_shared]a\n  allow :zlib, only: ~w[zip unzip gzip gunzip compress uncompress]a\nend\n"
  },
  {
    "path": "lib/dune/allowlist/docs.ex",
    "content": "defmodule Dune.Allowlist.Docs do\n  @moduledoc false\n\n  def document_allowlist(spec) do\n    spec\n    |> Dune.Allowlist.Spec.list_ordered_modules()\n    |> Enum.map_join(\"\\n\", &do_doc_funs/1)\n  end\n\n  def public_functions(module) when is_atom(module) do\n    case Code.fetch_docs(module) do\n      {:docs_v1, _, _, _, _, _, list} ->\n        for {{:function, function_name, _}, _, _, %{}, %{}} <- list, into: MapSet.new() do\n          function_name\n        end\n\n      _ ->\n        []\n    end\n  end\n\n  defp do_doc_funs({module, grouped_funs}) do\n    public_funs = public_functions(module)\n\n    head = [\"- `\", inspect(module), \"`\"]\n\n    tail =\n      Enum.map(grouped_funs, fn {status, funs} ->\n        [\n          \"**\",\n          format_status(status),\n          \"**: \" | Enum.map_intersperse(funs, \", \", &format_fun(module, &1, status, public_funs))\n        ]\n      end)\n\n    Enum.intersperse([head | tail], \"\\n  - \") |> to_string()\n  end\n\n  defp format_fun(module, {fun, arity}, status, public_funs) do\n    if fun in public_funs or module in [Kernel, Kernel.SpecialForms] do\n      [\n        ?[,\n        maybe_strike(status),\n        ?`,\n        Atom.to_string(fun),\n        ?`,\n        maybe_strike(status),\n        \"](`\",\n        inspect(module),\n        ?.,\n        to_string(fun),\n        ?/,\n        to_string(arity),\n        \"`)\"\n      ]\n    else\n      [\n        maybe_strike(status),\n        ?`,\n        Atom.to_string(fun),\n        ?`,\n        maybe_strike(status)\n      ]\n    end\n  end\n\n  defp maybe_strike(:restricted), do: \"~~\"\n  defp maybe_strike(_status), do: []\n\n  defp format_status(:allowed), do: \"Allowed\"\n  defp format_status(:shimmed), do: \"Alernative implementation\"\n  defp format_status(:restricted), do: \"Restricted\"\nend\n\nDune.Allowlist.Docs.public_functions(:rand)\n"
  },
  {
    "path": "lib/dune/allowlist/spec.ex",
    "content": "defmodule Dune.Allowlist.Spec do\n  @moduledoc false\n\n  alias Dune.Allowlist\n  alias Dune.Parser.RealModule\n\n  @type t :: %__MODULE__{\n          modules: %{optional(module) => [{atom, non_neg_integer, Allowlist.status()}]}\n        }\n  @enforce_keys [:modules]\n  defstruct @enforce_keys\n\n  def new do\n    %__MODULE__{modules: %{}}\n  end\n\n  @spec list_fun_statuses(t) :: list({module, atom, Allowlist.status()})\n  def list_fun_statuses(%__MODULE__{modules: modules}) do\n    for {module, funs} <- modules, {fun_name, _arity, status} <- funs do\n      {module, fun_name, status}\n    end\n    |> Enum.sort()\n    |> Enum.dedup()\n  end\n\n  @spec list_ordered_modules(t) :: list({module, {atom, Allowlist.status()}})\n  def list_ordered_modules(%__MODULE__{modules: modules}) do\n    modules\n    |> Enum.map(fn {module, funs} ->\n      {module, group_funs_by_status(funs)}\n    end)\n    |> Enum.sort()\n  end\n\n  defp group_funs_by_status(funs) do\n    Enum.group_by(\n      funs,\n      fn {_fun, _arity, status} -> extract_status_atom(status) end,\n      fn {fun, arity, _status} -> {fun, arity} end\n    )\n    |> Enum.map(fn {status, funs} -> {status, Enum.sort(funs) |> Enum.dedup_by(&elem(&1, 0))} end)\n    |> Enum.sort_by(fn {status, _} -> status_sort(status) end)\n  end\n\n  defp extract_status_atom(:restricted), do: :restricted\n  defp extract_status_atom(:allowed), do: :allowed\n  defp extract_status_atom({:shimmed, _, _}), do: :shimmed\n\n  defp status_sort(:allowed), do: 1\n  defp status_sort(:shimmed), do: 2\n  defp status_sort(:restricted), do: 3\n\n  @spec add_new_module(t, module, :all) :: t\n  def add_new_module(%__MODULE__{modules: modules}, module, _status)\n      when :erlang.map_get(module, modules) != nil do\n    # TODO proper error type\n    raise \"ModuleConflict: module #{inspect(module)} already defined\"\n  end\n\n  def add_new_module(spec = %__MODULE__{modules: modules}, module, status) when is_atom(module) do\n    Code.ensure_compiled!(module)\n\n    functions =\n      RealModule.list_functions(module)\n      |> classify_functions(status)\n\n    %{spec | modules: Map.put(modules, module, functions)}\n  end\n\n  defp classify_functions(functions, :all) do\n    Enum.map(functions, fn {fun_name, arity} -> {fun_name, arity, :allowed} end)\n  end\n\n  defp classify_functions(functions, only: only) when is_list(only) do\n    do_classify(functions, only, :allowed, :restricted)\n  end\n\n  defp classify_functions(functions, except: except) when is_list(except) do\n    do_classify(functions, except, :restricted, :allowed)\n  end\n\n  defp classify_functions(functions, opts) do\n    case Keyword.pop(opts, :shims) do\n      {shims, remaining_opts} when is_list(shims) ->\n        new_opts =\n          case remaining_opts do\n            [] -> :all\n            other -> other\n          end\n\n        classify_functions(functions, new_opts) |> shim_functions(shims)\n\n      {nil, _} ->\n        raise \"Invalid opts #{inspect(opts)}\"\n    end\n  end\n\n  defp do_classify(functions, set_list, member_atom, non_member_atom) do\n    set = to_atom_set(set_list)\n\n    functions\n    |> Enum.map_reduce(set, fn {fun_name, arity}, acc ->\n      case set do\n        %{^fun_name => _} -> {{fun_name, arity, member_atom}, Map.delete(acc, fun_name)}\n        _ -> {{fun_name, arity, non_member_atom}, acc}\n      end\n    end)\n    |> unwrap_classify()\n  end\n\n  defp to_atom_set(list) do\n    Enum.each(list, fn atom when is_atom(atom) -> :ok end)\n    :maps.from_keys(list, nil)\n  end\n\n  defp unwrap_classify({result, remaining}) when remaining == %{}, do: result\n\n  defp unwrap_classify({_, remaining}) do\n    [{key, _}] = Enum.take(remaining, 1)\n    raise \"Unknown function #{key}\"\n  end\n\n  defp shim_functions(functions, shims) do\n    # TODO validate shims\n    Enum.map(functions, fn fun = {fun_name, arity, _status} ->\n      case Keyword.get(shims, fun_name) do\n        nil ->\n          fun\n\n        {shim_module, shim_fun_name} ->\n          validate_shim!(shim_module, shim_fun_name, arity + 1)\n          {fun_name, arity, {:shimmed, shim_module, shim_fun_name}}\n      end\n    end)\n  end\n\n  defp validate_shim!(module, fun_name, arity) do\n    Code.ensure_compiled!(module)\n\n    unless function_exported?(module, fun_name, arity) or macro_exported?(module, fun_name, arity) do\n      raise \"Invalid shim: function #{inspect(module)}.#{fun_name}/#{arity} doesn't exist!\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/allowlist.ex",
    "content": "defmodule Dune.Allowlist do\n  @moduledoc \"\"\"\n  Behaviour to customize the modules and functions that are allowed or restricted.\n\n  ## Warning: security considerations\n\n  The default implementation is `Dune.Allowlist.Default`, and should only allow safe\n  functions: no atom leaks, no execution of arbitrary code, no access to the filesystem / network...\n  Defining or extending a custom `Dune.Allowlist` module can introduce security risks or bugs.\n\n  Please also note that using custom allowlists is still **experimental** and the API for it\n  might change faster than the rest of the library.\n\n  ## Defining a new allowlist\n\n  In order to define a custom allowlist from scratch, `use Dune.Allowlist` can be used:\n\n      defmodule CustomAllowlist do\n        use Dune.Allowlist\n\n        allow Kernel, only: [:+, :*, :-, :/, :div, :rem]\n      end\n\n      Dune.eval_string(\"4 + 9\", allowlist: CustomAllowlist)\n\n  ## Extending an existing allowlist\n\n  Defining an allowlist from scratch can be both daunting and risky.\n  It is possible to extend an exisiting allowlist instead using the `extend` option:\n\n      defmodule ExtendedAllowlist do\n        use Dune.Allowlist, extend: Dune.Allowlist.Default\n\n        allow SomeModule, only: [:authorized]\n      end\n\n      Dune.eval_string(\"SomeModule.authorized(123)\", allowlist: ExtendedAllowlist)\n\n  Note: currently, it is not possible to add or restrict functions from modules\n  that have already been specified.\n\n  ## Documentation generation\n\n  The list of modules and functions with their status can be generated in the `@moduledoc`.\n  An example can be found in the  `Dune.Allowlist.Default` documentation.\n\n  If the `__DUNE_ALLOWLIST_FUNCTIONS__` string is found in the `@moduledoc` string,\n  it will be replaced.\n\n      defmodule CustomAllowlist do\n        @moduledoc \\\"\\\"\\\"\n        Only allows simple arithmetic\n\n        ## Allowlist functions\n\n        __DUNE_ALLOWLIST_FUNCTIONS__\n        \\\"\\\"\\\"\n\n        use Dune.Allowlist\n\n        allow Kernel, only: [:+, :*, :-, :/, :div, :rem]\n      end\n\n  \"\"\"\n\n  @type status :: :allowed | :restricted | {:shimmed, module, atom}\n\n  @doc \"\"\"\n  Returns the trust status of a function or macro, specified as a `module`, `fun_name` and `arity` (`mfa`):\n  - `:allowed` if can be safely use\n  - `:restricted` if its usage should be forbidden\n  - a `{:shimmed, module, function_name}` if the function call should be replaced with an alternative implementation\n  \"\"\"\n  @callback fun_status(module, atom, non_neg_integer) :: Dune.Allowlist.status()\n\n  @doc \"\"\"\n  Validates the fact that a module implements the `Dune.Allowlist` behaviour.\n\n  Raises if not the case.\n\n  ## Examples\n\n      iex> Dune.Allowlist.ensure_implements_behaviour!(DoesNotExists)\n      ** (ArgumentError) could not load module DoesNotExists due to reason :nofile\n\n      iex> Dune.Allowlist.ensure_implements_behaviour!(List)\n      ** (ArgumentError) List does not implement the Dune.Allowlist behaviour\n\n  \"\"\"\n  @spec ensure_implements_behaviour!(module) :: module\n  def ensure_implements_behaviour!(module) when is_atom(module) do\n    Code.ensure_compiled!(module)\n\n    implemented? =\n      module.module_info(:attributes)\n      |> Keyword.get(:behaviour, [])\n      |> Enum.member?(Dune.Allowlist)\n\n    unless implemented? do\n      raise ArgumentError,\n        message: \"#{inspect(module)} does not implement the Dune.Allowlist behaviour\"\n    end\n\n    module\n  end\n\n  defmacro __using__(opts) do\n    extend = extract_extend_opt(opts, __CALLER__)\n\n    quote do\n      import Dune.Allowlist, only: [allow: 2]\n\n      @behaviour Dune.Allowlist\n\n      Module.register_attribute(__MODULE__, :allowlist, accumulate: true)\n\n      Module.put_attribute(__MODULE__, :extend_allowlist, unquote(extend))\n\n      @before_compile Dune.Allowlist\n    end\n  end\n\n  @doc \"\"\"\n  Adds a new module to the allowlist and specifices which functions to use.\n\n  The module must not be already specified in the allowlist.\n\n  Must be called after `use Dune.Allowlist`.\n\n  ## Examples\n\n      # allow all functions in a module\n      allow Time, :all\n\n      # only allow specific functions\n      allow Function, only: [:identity]\n\n      # exclude specific functions\n      allow Calendar, except: [:put_time_zone_database]\n\n  Note: `only` and `except` will cover all arities if several functions\n  share a name.\n\n  \"\"\"\n  defmacro allow(module, status) do\n    quote do\n      Module.put_attribute(__MODULE__, :allowlist, {unquote(module), unquote(status)})\n    end\n  end\n\n  defmacro __before_compile__(env) do\n    Dune.Allowlist.__postprocess__(env.module)\n  end\n\n  defp extract_extend_opt(opts, caller) do\n    case Keyword.fetch(opts, :extend) do\n      {:ok, module_ast} ->\n        Macro.expand(module_ast, caller) |> ensure_implements_behaviour!()\n\n      _ ->\n        nil\n    end\n  end\n\n  @doc false\n  def __postprocess__(module) do\n    extend = Module.get_attribute(module, :extend_allowlist)\n    spec = generate_spec(module, extend)\n    update_module_doc(module, spec)\n\n    quote do\n      unquote(def_spec(spec))\n      unquote(def_fun_status(spec))\n\n      @on_load :ensure_alias_atoms\n\n      # Aliases like Foo.Bar are represented on the AST level as {:alias, _, [:Foo, :Bar]}\n      # We need to force the creation of these atoms, which might otherwise not be\n      # available when we parse due to atom encoding logic.\n      def ensure_alias_atoms do\n        # do nothing and returns :ok\n        Enum.each(unquote(alias_atoms(spec)), & &1)\n      end\n    end\n  end\n\n  defp generate_spec(module, extend) do\n    base_spec =\n      case extend do\n        nil -> Dune.Allowlist.Spec.new()\n        allowlist when is_atom(allowlist) -> allowlist.spec()\n      end\n\n    Module.get_attribute(module, :allowlist)\n    |> Enum.reduce(base_spec, fn {module, status}, acc ->\n      Dune.Allowlist.Spec.add_new_module(acc, module, status)\n    end)\n  end\n\n  defp def_spec(spec) do\n    quote do\n      @doc false\n      @spec spec :: Dune.Allowlist.Spec.t()\n      def spec do\n        unquote(Macro.escape(spec))\n      end\n    end\n  end\n\n  defp def_fun_status(spec) do\n    defps =\n      for {m, f, status} = _ <- Dune.Allowlist.Spec.list_fun_statuses(spec) do\n        quote do\n          defp do_fun_status(unquote(m), unquote(f)),\n            do: unquote(Macro.escape(status))\n        end\n      end\n\n    quote do\n      @impl Dune.Allowlist\n      @doc \"Implements `c:Dune.Allowlist.fun_status/3`\"\n      def fun_status(module, fun_name, arity)\n          when is_atom(module) and is_atom(fun_name) and is_integer(arity) and arity >= 0 do\n        with :defined <- Dune.Parser.RealModule.fun_status(module, fun_name, arity) do\n          do_fun_status(module, fun_name)\n        end\n      end\n\n      unquote(defps)\n\n      defp do_fun_status(_module, _fun_name), do: :restricted\n    end\n  end\n\n  defp update_module_doc(module, spec) do\n    case Module.get_attribute(module, :moduledoc) do\n      {line, doc} when is_binary(doc) ->\n        doc =\n          String.replace(doc, \"__DUNE_ALLOWLIST_FUNCTIONS__\", fn _ ->\n            Dune.Allowlist.Docs.document_allowlist(spec)\n          end)\n\n        Module.put_attribute(module, :moduledoc, {line, doc})\n\n      _other ->\n        :ok\n    end\n  end\n\n  defp alias_atoms(spec) do\n    spec.modules\n    |> Enum.flat_map(fn {mod, _} ->\n      try do\n        Module.split(mod)\n      rescue\n        ArgumentError ->\n          # \"expected an Elixir module\" error -> erlang module\n          []\n      end\n    end)\n    |> Enum.map(&String.to_atom/1)\n    |> Enum.uniq()\n  end\nend\n"
  },
  {
    "path": "lib/dune/atom_mapping.ex",
    "content": "defmodule Dune.AtomMapping do\n  @moduledoc false\n\n  alias Dune.{Success, Failure}\n\n  @type substitute_atom :: atom\n  @type original_string :: String.t()\n  @type sub_mapping :: %{optional(substitute_atom) => original_string}\n  @type extra_info :: %{optional(substitute_atom) => :wrapped}\n\n  @typedoc \"\"\"\n  Should be considered opaque\n  \"\"\"\n  @type t :: %__MODULE__{atoms: sub_mapping, modules: sub_mapping, extra_info: extra_info}\n  @enforce_keys [:atoms, :modules, :extra_info]\n  defstruct @enforce_keys\n\n  @spec new :: t()\n  def new do\n    %__MODULE__{atoms: %{}, modules: %{}, extra_info: %{}}\n  end\n\n  @spec from_atoms([{substitute_atom, original_string}], [{substitute_atom, :wrapped}]) :: t\n  def from_atoms(list, extra_info) when is_list(list) do\n    atoms = build_mapping(list)\n    extra_info = Map.new(extra_info)\n    %__MODULE__{atoms: atoms, modules: %{}, extra_info: extra_info}\n  end\n\n  @spec add_modules(t, [{substitute_atom, original_string}]) :: t\n  def add_modules(mapping = %__MODULE__{}, list) do\n    %{mapping | modules: build_mapping(list)}\n  end\n\n  defp build_mapping(list) do\n    Map.new(list, fn {substitute_atom, original_string}\n                     when is_atom(substitute_atom) and is_binary(original_string) ->\n      {substitute_atom, original_string}\n    end)\n  end\n\n  @spec to_string(t, atom) :: String.t()\n  def to_string(mapping = %__MODULE__{}, atom) when is_atom(atom) do\n    case lookup_original_string(mapping, atom) do\n      {:atom, string} -> string\n      {:wrapped_atom, string} -> string\n      {:module, string} -> \"Elixir.#{string}\"\n      :error -> Atom.to_string(atom)\n    end\n  end\n\n  @spec inspect(t, atom) :: String.t()\n  def inspect(mapping = %__MODULE__{}, atom) when is_atom(atom) do\n    case lookup_original_string(mapping, atom) do\n      {:atom, string} -> \":#{string}\"\n      {:wrapped_atom, string} -> ~s(:\"#{string}\")\n      {:module, string} -> string\n      :error -> inspect(atom)\n    end\n  end\n\n  @spec lookup_original_string(t, atom) :: {:atom | :wrapped_atom | :module, String.t()} | :error\n  def lookup_original_string(mapping = %__MODULE__{}, atom) when is_atom(atom) do\n    case mapping.modules do\n      %{^atom => string} ->\n        {:module, string}\n\n      _ ->\n        case mapping.atoms do\n          %{^atom => string} ->\n            case mapping.extra_info do\n              %{^atom => :wrapped} -> {:wrapped_atom, string}\n              _ -> {:atom, string}\n            end\n\n          _ ->\n            :error\n        end\n    end\n  end\n\n  @spec replace_in_string(t, String.t()) :: String.t()\n  def replace_in_string(mapping, string) when is_binary(string) do\n    if string =~ \"Dune\" do\n      do_replace_in_string(mapping, string)\n    else\n      string\n    end\n  end\n\n  @dune_atom_regex ~r/(Dune_Atom_\\d+__|a?__Dune_atom_\\d+__|Dune_Module_\\d+__)/\n  defp do_replace_in_string(mapping = %__MODULE__{}, string) do\n    string_replace_map =\n      %{}\n      |> build_replace_map(mapping.modules, nil, &inspect/1)\n      |> build_replace_map(mapping.atoms, mapping.extra_info, &Atom.to_string/1)\n\n    String.replace(string, @dune_atom_regex, &Map.get(string_replace_map, &1, &1))\n  end\n\n  defp build_replace_map(map, sub_mapping, extra_info, to_string_fun) do\n    for {subsitute_atom, original_string} <- sub_mapping, into: map do\n      replace_by =\n        case extra_info do\n          %{^subsitute_atom => :wrapped} -> ~s(\"#{original_string}\")\n          _ -> original_string\n        end\n\n      {to_string_fun.(subsitute_atom), replace_by}\n    end\n  end\n\n  @spec replace_in_result(t, Success.t() | Failure.t()) ::\n          Success.t() | Failure.t()\n  def replace_in_result(mapping, result)\n\n  def replace_in_result(mapping, %Success{} = success) do\n    %Success{\n      success\n      | inspected: replace_in_string(mapping, success.inspected),\n        stdio: replace_in_string(mapping, success.stdio)\n    }\n  end\n\n  def replace_in_result(mapping, %Failure{} = error) do\n    %Failure{\n      error\n      | message: replace_in_string(mapping, error.message),\n        stdio: replace_in_string(mapping, error.stdio)\n    }\n  end\n\n  @spec to_existing_atom(t, String.t()) :: atom\n  def to_existing_atom(mapping = %__MODULE__{}, string) when is_binary(string) do\n    case fetch_existing_atom(mapping, string) do\n      nil -> String.to_existing_atom(string)\n      atom -> atom\n    end\n  end\n\n  defp fetch_existing_atom(mapping, \"Elixir.\" <> module_name) do\n    Enum.find_value(mapping.modules, fn {subsitute_atom, original_string} ->\n      if original_string == module_name do\n        subsitute_atom\n      end\n    end)\n  end\n\n  defp fetch_existing_atom(mapping, atom_name) do\n    Enum.find_value(mapping.atoms, fn {subsitute_atom, original_string} ->\n      if original_string == atom_name do\n        subsitute_atom\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/dune/eval/env.ex",
    "content": "defmodule Dune.Eval.Env do\n  @moduledoc false\n\n  alias Dune.AtomMapping\n  alias Dune.Eval.FakeModule\n\n  @type t :: %__MODULE__{\n          atom_mapping: AtomMapping.t(),\n          allowlist: module,\n          fake_modules: %{optional(atom) => FakeModule.t()}\n        }\n  @enforce_keys [:atom_mapping, :allowlist, :fake_modules]\n  defstruct @enforce_keys\n\n  def new(atom_mapping = %AtomMapping{}, allowlist) when is_atom(allowlist) do\n    %__MODULE__{atom_mapping: atom_mapping, allowlist: allowlist, fake_modules: %{}}\n  end\n\n  def add_module(env = %__MODULE__{fake_modules: modules}, module_name, module = %FakeModule{})\n      when is_atom(module_name) do\n    # TODO check a bunch of things here:\n    # - warn if module redefined\n    # - fail if overriding existing module\n    # - fail if overriding Kernel/Special forms\n    %{env | fake_modules: Map.put(modules, module_name, module)}\n  end\n\n  def apply_fake(env = %__MODULE__{}, module, fun_name, args)\n      when is_atom(module) and is_atom(fun_name) and is_list(args) do\n    arity = length(args)\n\n    case fetch_fake_function(env, module, fun_name, arity) do\n      {:def, fun} -> fun.(env, args)\n      other -> throw({other, module, fun_name, arity})\n    end\n  end\n\n  defp fetch_fake_function(%{fake_modules: modules}, module, fun_name, arity) do\n    case modules do\n      %{^module => fake_module} ->\n        case FakeModule.get_function(fake_module, fun_name, arity) do\n          nil -> :undefined_function\n          {:def, fun} -> {:def, fun}\n        end\n\n      _ ->\n        :undefined_module\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/eval/fake_module.ex",
    "content": "defmodule Dune.Eval.FakeModule do\n  @moduledoc false\n\n  @type t :: %__MODULE__{\n          public_funs: %{optional(atom) => %{required(non_neg_integer) => function}}\n        }\n  @enforce_keys [:public_funs]\n  defstruct @enforce_keys\n\n  def get_function(%__MODULE__{public_funs: funs}, fun_name, arity)\n      when is_atom(fun_name) and is_integer(arity) do\n    case funs do\n      %{^fun_name => %{^arity => fun}} -> {:def, fun}\n      _ -> nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/eval/function_clause_error.ex",
    "content": "defmodule Dune.Eval.FunctionClauseError do\n  @moduledoc false\n\n  defexception [:module, :function, :args]\n\n  def message(err = %__MODULE__{function: function, args: args}) do\n    module = inspect(err.module)\n    arity = length(args)\n    args = inspect(args) |> String.slice(1..-2//1)\n\n    \"no function clause matching in #{module}.#{function}/#{arity}: #{module}.#{function}(#{args})\"\n  end\nend\n"
  },
  {
    "path": "lib/dune/eval/macro_env.ex",
    "content": "defmodule Dune.Eval.MacroEnv do\n  @moduledoc false\n\n  # Recommended way to generate a Macro.Env struct\n  # https://hexdocs.pm/elixir/main/Macro.Env.html\n  def make_env do\n    import Dune.Shims.Kernel, only: [safe_sigil_w: 3, safe_sigil_W: 3], warn: false\n\n    %Macro.Env{__ENV__ | file: \"nofile\", module: nil, function: nil, line: 1}\n  end\nend\n"
  },
  {
    "path": "lib/dune/eval/process.ex",
    "content": "defmodule Dune.Eval.Process do\n  @moduledoc false\n\n  alias Dune.Failure\n  alias Dune.Helpers.Diagnostics\n\n  def run(fun, opts = %Dune.Opts{}) when is_function(fun, 0) do\n    with_string_io(fn string_io ->\n      do_run(fun, opts, string_io)\n    end)\n  end\n\n  defp do_run(fun, opts, string_io) do\n    task =\n      Task.async(fn ->\n        # spawn within a task to avoid trapping exits in the caller\n        spawn_trapped_process(\n          fun,\n          opts.max_heap_size,\n          opts.max_reductions,\n          string_io\n        )\n      end)\n\n    result =\n      case Task.yield(task, opts.timeout) || Task.shutdown(task) do\n        {:ok, result} ->\n          result\n\n        nil ->\n          %Failure{type: :timeout, message: \"Execution timeout - #{opts.timeout}ms\"}\n      end\n\n    case result do\n      %Failure{type: :compile_error} -> result\n      _ -> %{result | stdio: result.stdio <> StringIO.flush(string_io)}\n    end\n  end\n\n  defp with_string_io(fun) do\n    {:ok, string_io} = StringIO.open(\"\")\n\n    try do\n      fun.(string_io)\n    after\n      StringIO.close(string_io)\n    end\n  end\n\n  # returns a Dune.Failure struct or exits\n  defp spawn_trapped_process(fun, max_heap_size, max_reductions, string_io) do\n    report_to = self()\n\n    Process.flag(:trap_exit, true)\n\n    # unlike plain spawn / Process.spawn, proc_lib doesn't trigger the logger:\n    # |  Unlike in \"plain Erlang\", proc_lib processes will not generate error reports,\n    # |  which are written to the terminal by the emulator. All exceptions are converted\n    # |  to exits which are ignored by the default logger handler.\n\n    opts = [\n      :link,\n      priority: :low,\n      max_heap_size: %{size: max_heap_size, kill: true, error_logger: false}\n    ]\n\n    pid =\n      :proc_lib.spawn_opt(\n        fn ->\n          Process.group_leader(self(), string_io)\n\n          fun\n          |> catch_diagnostics()\n          |> then(&send(report_to, &1))\n        end,\n        opts\n      )\n\n    spawn(fn -> check_max_reductions(pid, report_to, max_reductions) end)\n\n    receive do\n      {:ok, result, diagnostics} ->\n        Diagnostics.prepend_diagnostics(result, diagnostics)\n\n      {:compile_error, error, diagnostics, stacktrace} ->\n        format_compile_error(error, diagnostics, stacktrace)\n\n      {:EXIT, ^pid, reason} ->\n        case reason do\n          :normal ->\n            exit(:normal)\n\n          :killed ->\n            %Failure{type: :memory, message: \"Execution stopped - memory limit exceeded\"}\n\n          {error, stacktrace} ->\n            format_error(error, stacktrace)\n        end\n\n      {:EXIT, _other_pid, reason} ->\n        # avoid the process to become immune to parent death\n        exit(reason)\n\n      {:reductions_exceeded, _reductions} ->\n        %Failure{type: :reductions, message: \"Execution stopped - reductions limit exceeded\"}\n    end\n  end\n\n  defp catch_diagnostics(fun) do\n    {result, diagnostics} =\n      Diagnostics.with_diagnostics_polyfill(fn ->\n        try do\n          {:ok, fun.()}\n        rescue\n          err in CompileError ->\n            {err, __STACKTRACE__}\n        end\n      end)\n\n    case result do\n      {:ok, value} ->\n        {:ok, value, diagnostics}\n\n      {%CompileError{} = err, stacktrace} ->\n        {:compile_error, err, diagnostics, stacktrace}\n    end\n  end\n\n  defp check_max_reductions(pid, report_to, max_reductions) when is_integer(max_reductions) do\n    # approach inspired from luerl\n    # https://github.com/rvirding/luerl/blob/develop/src/luerl_sandbox.erl\n    case Process.info(pid, :reductions) do\n      nil ->\n        :ok\n\n      {:reductions, reductions} when reductions > max_reductions ->\n        # if send immediately, might arrive before an EXIT signal\n        Process.send_after(report_to, {:reductions_exceeded, reductions}, 1)\n\n      {:reductions, _reductions} ->\n        check_max_reductions(pid, report_to, max_reductions)\n    end\n  end\n\n  defp format_error(error, stacktrace)\n\n  defp format_error({:nocatch, value}, _stacktrace) do\n    case value do\n      {:undefined_module, module, fun, arity} -> Failure.undefined_module(module, fun, arity)\n      {:undefined_function, module, fun, arity} -> Failure.undefined_function(module, fun, arity)\n      {:safe_throw, thrown} -> %Failure{type: :throw, message: \"** (throw) \" <> inspect(thrown)}\n    end\n  end\n\n  defp format_error(error, stacktrace) do\n    [head | _] = stacktrace\n\n    parts =\n      case head do\n        {:erl_eval, :do_apply, _, _} -> 3\n        {:elixir_eval, :__FILE__, _, _} -> 3\n        _ -> 2\n      end\n\n    message =\n      {error, [head]}\n      |> Exception.format_exit()\n      |> String.split(\"\\n    \", parts: parts)\n      |> Enum.at(1)\n\n    # TODO properly pass stacktrace\n    %Failure{type: :exception, message: message}\n  end\n\n  defp format_compile_error(error, diagnostics, stacktrace) do\n    message =\n      {error, stacktrace}\n      |> Exception.format_exit()\n      |> String.split(\"\\n    \")\n      |> Enum.at(1)\n\n    %Failure{\n      type: :compile_error,\n      message: message,\n      stdio: Diagnostics.format_diagnostics(diagnostics)\n    }\n  end\nend\n"
  },
  {
    "path": "lib/dune/eval.ex",
    "content": "defmodule Dune.Eval do\n  @moduledoc false\n\n  alias Dune.{AtomMapping, Success, Failure, Opts}\n  alias Dune.Eval.Env\n  alias Dune.Eval.MacroEnv\n  alias Dune.Parser.SafeAst\n  alias Dune.Shims\n\n  @typep previous_session :: %{:bindings => keyword, :env => Env.t(), optional(any) => any}\n\n  @spec run(SafeAst.t() | Failure.t(), Opts.t(), previous_session | nil) ::\n          Success.t() | Failure.t()\n  def run(parsed, opts, previous_session \\\\ nil)\n\n  def run(\n        %SafeAst{\n          ast: ast,\n          atom_mapping: atom_mapping,\n          compile_env: %{allowlist: allowlist},\n          stdio: parser_stdio\n        },\n        opts = %Opts{},\n        previous_session\n      ) do\n    case previous_session do\n      nil ->\n        env = Env.new(atom_mapping, allowlist)\n        do_run(ast, atom_mapping, opts, env, nil)\n\n      %{bindings: bindings, env: env} ->\n        env = %{env | atom_mapping: atom_mapping, allowlist: allowlist}\n        do_run(ast, atom_mapping, opts, env, bindings)\n    end\n    |> prepend_parser_stdio(parser_stdio)\n  end\n\n  def run(%Failure{} = failure, _opts, _bindings), do: failure\n\n  defp do_run(ast, atom_mapping, opts, env, bindings) do\n    result =\n      Dune.Eval.Process.run(\n        fn ->\n          safe_eval(ast, env, bindings, opts.pretty, opts.inspect_sort_maps)\n        end,\n        opts\n      )\n\n    AtomMapping.replace_in_result(atom_mapping, result)\n  end\n\n  defp prepend_parser_stdio(result, \"\"), do: result\n\n  defp prepend_parser_stdio(result, parser_stdio) do\n    Map.update!(result, :stdio, &(parser_stdio <> &1))\n  end\n\n  defp safe_eval(safe_ast, env, bindings, pretty, sort_maps) do\n    try do\n      inspect_opts = [pretty: pretty, custom_options: [sort_maps: sort_maps]]\n      do_safe_eval(safe_ast, env, bindings, inspect_opts)\n    catch\n      failure = %Failure{} ->\n        failure\n    end\n  end\n\n  defp do_safe_eval(safe_ast, env, nil, inspect_opts) do\n    binding = [env__Dune__: env]\n    {value, new_env, _new_bindings} = eval_quoted(safe_ast, binding)\n\n    %Success{\n      value: value,\n      # another important thing about inspect is that it force-evaluates\n      # potentially huge shared structs => OOM before sending\n      inspected: Shims.Kernel.safe_inspect(new_env, value, inspect_opts),\n      stdio: \"\"\n    }\n  end\n\n  defp do_safe_eval(safe_ast, env, bindings, inspect_opts) when is_list(bindings) do\n    binding = [env__Dune__: env] ++ bindings\n    {value, new_env, new_bindings} = eval_quoted(safe_ast, binding)\n\n    %Success{\n      value: {value, new_env, new_bindings},\n      inspected: Shims.Kernel.safe_inspect(new_env, value, inspect_opts),\n      stdio: \"\"\n    }\n  end\n\n  defp eval_quoted(safe_ast, binding) do\n    {value, bindings, _env} = Code.eval_quoted_with_env(safe_ast, binding, MacroEnv.make_env())\n\n    {new_env, new_bindings} = Keyword.pop!(bindings, :env__Dune__)\n\n    {value, new_env, new_bindings}\n  end\nend\n"
  },
  {
    "path": "lib/dune/failure.ex",
    "content": "defmodule Dune.Failure do\n  @moduledoc \"\"\"\n  A struct returned when `Dune` parsing or evaluation fails.\n\n  Fields:\n  - `message` (string): the error message to display to the user\n  - `type` (atom): the nature of the error\n  - `stdio` (string): captured standard output\n\n  \"\"\"\n\n  @type error_type ::\n          :restricted\n          | :module_restricted\n          | :module_conflict\n          | :timeout\n          | :exception\n          | :compile_error\n          | :parsing\n          | :memory\n          | :reductions\n\n  @type t :: %__MODULE__{type: error_type, message: String.t(), stdio: binary}\n  @enforce_keys [:type, :message]\n  defstruct @enforce_keys ++ [stdio: \"\"]\n\n  @doc false\n  def restricted_function(module, fun, arity) do\n    formatted_fun = format_function(module, fun, arity)\n    message = \"** (DuneRestrictedError) function #{formatted_fun} is restricted\"\n\n    %__MODULE__{type: :restricted, message: message}\n  end\n\n  @doc false\n  def undefined_module(module, function, arity) do\n    base_message = base_undefined_message(module, function, arity)\n\n    message =\n      IO.iodata_to_binary([base_message, \"(module \", inspect(module), \" is not available)\"])\n\n    %__MODULE__{type: :exception, message: message}\n  end\n\n  @doc false\n  def undefined_function(module, function, arity) do\n    base_message = base_undefined_message(module, function, arity)\n    message = IO.iodata_to_binary([base_message, \"or private\"])\n\n    %__MODULE__{type: :exception, message: message}\n  end\n\n  defp base_undefined_message(module, function, arity) do\n    formatted_fun = format_function(module, function, arity)\n    [\"** (UndefinedFunctionError) function \", formatted_fun, \" is undefined \"]\n  end\n\n  defp format_function(kernel, fun, arity) when kernel in [nil, Kernel, Kernel.SpecialForms] do\n    \"#{fun}/#{arity}\"\n  end\n\n  defp format_function(module, fun, arity) do\n    \"#{inspect(module)}.#{fun}/#{arity}\"\n  end\nend\n"
  },
  {
    "path": "lib/dune/helpers/diagnostics.ex",
    "content": "defmodule Dune.Helpers.Diagnostics do\n  @moduledoc false\n\n  # used for formatting errors and warnings consistently\n\n  @type result_with_stdio :: %{stdio: binary()}\n\n  @spec prepend_diagnostics(\n          result_with_stdio(),\n          [Code.diagnostic(:warning | :error)]\n        ) :: result_with_stdio()\n  def prepend_diagnostics(result, []), do: result\n\n  def prepend_diagnostics(result, diagnostics) do\n    %{result | stdio: format_diagnostics(diagnostics) <> \"\\n\\n\"}\n  end\n\n  @spec format_diagnostics([Code.diagnostic(:warning | :error)]) :: String.t()\n  def format_diagnostics(diagnostics) do\n    Enum.map_join(\n      diagnostics,\n      \"\\n\",\n      &\"#{&1.severity}: #{&1.message}\\n  #{&1.file}:#{format_pos(&1.position)}\"\n    )\n  end\n\n  defp format_pos(integer) when is_integer(integer), do: Integer.to_string(integer)\n  defp format_pos({line, col}), do: [Integer.to_string(line), ?:, Integer.to_string(col)]\n\n  @doc \"\"\"\n  A polyfill for `Code.with_diagnostics/1` for older versions of Elixir, which returns\n  an empty list of diagnostics if not available.\n  \"\"\"\n\n  # TODO remove then dropping support for 1.14\n  if System.version() |> Version.compare(\"1.15.0\") != :lt do\n    defdelegate with_diagnostics_polyfill(fun), to: Code, as: :with_diagnostics\n  else\n    def with_diagnostics_polyfill(fun) do\n      {fun.(), []}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/helpers/term_checker.ex",
    "content": "defmodule Dune.Helpers.TermChecker do\n  @moduledoc false\n\n  defguardp is_simple_term(term)\n            when is_atom(term) or is_bitstring(term) or is_number(term) or is_reference(term) or\n                   is_function(term) or is_pid(term) or is_port(term) or term == []\n\n  @doc \"\"\"\n  Walks the term recursively to make sure it is not a humongous tree built using structural sharing\n  \"\"\"\n  def check(term), do: do_check(term)\n\n  defp do_check(term) when is_simple_term(term), do: :ok\n\n  defp do_check([left | right]) when is_simple_term(left) do\n    do_check(right)\n  end\n\n  defp do_check([left | right]) do\n    do_check(left)\n    do_check(right)\n  end\n\n  defp do_check(map) when is_map(map) do\n    Map.to_list(map) |> do_check()\n  end\n\n  defp do_check(tuple) when is_tuple(tuple) do\n    Tuple.to_list(tuple) |> do_check()\n  end\nend\n"
  },
  {
    "path": "lib/dune/opts.ex",
    "content": "defmodule Dune.Opts do\n  @moduledoc \"\"\"\n  Defines and validates the options for `Dune`.\n\n  The available options are explained below:\n\n  ### Parsing restriction options\n\n  - `atom_pool_size`:\n    Defines the maximum total number of atoms that can be created.\n    Must be an integer `>= 0`. Defaults to `5000`.\n    See the [section below](#module-extra-note-about-atom_pool_size) for more information.\n  - `max_length`:\n    Defines the maximum length of code strings that can be parsed.\n    Defaults to `5000`.\n\n  ### Execution restriction options\n\n  - `allowlist`:\n    Defines which module and functions are considered safe or restricted.\n    Should be a module implementing the `Dune.Allowlist` behaviour.\n    Defaults to `Dune.Allowlist.Default`.\n  - `max_heap_size`:\n    Limits the memory usage of the evaluation process using the\n    [`max_heap_size` flag](https://erlang.org/doc/man/erlang.html#process_flag_max_heap_size).\n    Should be an integer `> 0`. Defaults to `30_000`.\n  - `max_reductions`:\n    Limits the number of CPU cycles of the evaluation process.\n    The erlang pre-emptive scheduler is using reductions to measure work being done by processes,\n    which is useful to prevent users to run CPU intensive code such as infinite loops.\n    Should be an integer `> 0`. Defaults to `30_000`.\n  - `timeout`:\n    Limits the time the evaluation process is authorized to run (in milliseconds).\n    Should be an integer `> 0`. Defaults to `50`.\n\n  The evaluation process will still need to parse and execute the sanitized AST, so using\n  too low limits here would leave only a small margin to actually run user code.\n\n  ### Other options\n\n  - `pretty`:\n    Use pretty printing when inspecting the result.\n    Should be a boolean. Defaults to `false`.\n\n  - `inspect_sort_maps`:\n    Sort maps when inspecting the result, useful to keep the output deterministic.\n    Should be a boolean. Defaults to `false`. Only works since Elixir >= 1.14.4.\n\n  ### Extra note about `atom_pool_size`\n\n  Atoms are reused from one evaluation to the other so the total is not\n  expected to grow. Atoms will not be leaked.\n\n  Also, the atom pool is actually split into several pools: regular atoms, module names,\n  unused variable names, ...\n  So defining a value of `100` does not mean that `100` atoms will be available, but\n  rather `25` of each type.\n\n  Atoms being very lightweight, there is no need to use a low value, as long\n  as there is an upper bound preventing atom leaks.\n\n  \"\"\"\n\n  alias Dune.Allowlist\n\n  @type t :: %__MODULE__{\n          atom_pool_size: non_neg_integer,\n          max_length: pos_integer,\n          allowlist: module,\n          max_heap_size: pos_integer,\n          max_reductions: pos_integer,\n          timeout: pos_integer,\n          pretty: boolean,\n          inspect_sort_maps: boolean\n        }\n\n  defstruct atom_pool_size: 5000,\n            max_length: 5000,\n            allowlist: Dune.Allowlist.Default,\n            max_heap_size: 50_000,\n            max_reductions: 30_000,\n            timeout: 50,\n            pretty: false,\n            inspect_sort_maps: false\n\n  @doc \"\"\"\n  Validates untrusted options from a keyword or a map and returns a `Dune.Opts` struct.\n\n  ## Examples\n\n      iex> Dune.Opts.validate!([])\n      %Dune.Opts{\n        allowlist: Dune.Allowlist.Default,\n        atom_pool_size: 5000,\n        max_heap_size: 50000,\n        max_length: 5000,\n        max_reductions: 30000,\n        pretty: false,\n        timeout: 50\n      }\n\n      iex> Dune.Opts.validate!(atom_pool_size: 10)\n      %Dune.Opts{atom_pool_size: 10, allowlist: Dune.Allowlist.Default}\n\n      iex> Dune.Opts.validate!(atom_pool_size: -10)\n      ** (ArgumentError) atom_pool_size should be an integer >= 0\n\n      iex> Dune.Opts.validate!(max_length: 0)\n      ** (ArgumentError) atom_pool_size should be an integer > 0\n\n      iex> Dune.Opts.validate!(allowlist: DoesNotExists)\n      ** (ArgumentError) could not load module DoesNotExists due to reason :nofile\n\n      iex> Dune.Opts.validate!(allowlist: List)\n      ** (ArgumentError) List does not implement the Dune.Allowlist behaviour\n\n      iex> Dune.Opts.validate!(max_reductions: 10_000, max_heap_size: 10_000, timeout: 20)\n      %Dune.Opts{max_heap_size: 10_000, max_reductions: 10_000, timeout: 20}\n\n      iex> Dune.Opts.validate!(max_heap_size: 0)\n      ** (ArgumentError) max_heap_size should be an integer > 0\n\n      iex> Dune.Opts.validate!(max_reductions: 0)\n      ** (ArgumentError) max_reductions should be an integer > 0\n\n      iex> Dune.Opts.validate!(timeout: \"55\")\n      ** (ArgumentError) timeout should be an integer > 0\n\n      iex> Dune.Opts.validate!(pretty: :maybe)\n      ** (ArgumentError) pretty should be a boolean\n\n  \"\"\"\n  @spec validate!(Keyword.t() | map) :: t\n  def validate!(opts) do\n    struct(__MODULE__, opts) |> do_validate()\n  end\n\n  defp do_validate(%{atom_pool_size: atom_pool_size})\n       when not (is_integer(atom_pool_size) and atom_pool_size >= 0) do\n    raise ArgumentError, message: \"atom_pool_size should be an integer >= 0\"\n  end\n\n  defp do_validate(%{max_length: max_length})\n       when not (is_integer(max_length) and max_length > 0) do\n    raise ArgumentError, message: \"atom_pool_size should be an integer > 0\"\n  end\n\n  defp do_validate(%{allowlist: allowlist}) when not is_atom(allowlist) do\n    raise ArgumentError, message: \"allowlist should be a module\"\n  end\n\n  defp do_validate(%{max_reductions: max_reductions})\n       when not (is_integer(max_reductions) and max_reductions > 0) do\n    raise ArgumentError, message: \"max_reductions should be an integer > 0\"\n  end\n\n  defp do_validate(%{max_heap_size: max_heap_size})\n       when not (is_integer(max_heap_size) and max_heap_size > 0) do\n    raise ArgumentError, message: \"max_heap_size should be an integer > 0\"\n  end\n\n  defp do_validate(%{timeout: timeout}) when not (is_integer(timeout) and timeout > 0) do\n    raise ArgumentError, message: \"timeout should be an integer > 0\"\n  end\n\n  defp do_validate(%{pretty: pretty}) when not is_boolean(pretty) do\n    raise ArgumentError, message: \"pretty should be a boolean\"\n  end\n\n  defp do_validate(%{inspect_sort_maps: sort}) when not is_boolean(sort) do\n    raise ArgumentError, message: \"inspect_sort_maps should be a boolean\"\n  end\n\n  defp do_validate(opts = %{allowlist: allowlist}) do\n    Allowlist.ensure_implements_behaviour!(allowlist)\n\n    opts\n  end\nend\n"
  },
  {
    "path": "lib/dune/parser/atom_encoder.ex",
    "content": "defmodule Dune.Parser.AtomEncoder do\n  @moduledoc false\n\n  alias Dune.AtomMapping\n\n  @type atom_category :: :alias | :private_var | :public_var | :other\n\n  @atom_categories 4\n\n  # TODO Remove when dropping support for Elixir 1.16\n  extra_modules =\n    if System.version() |> Version.compare(\"1.17.0-rc.0\") != :lt, do: [Duration], else: []\n\n  @elixir_modules [\n                    Kernel,\n                    Kernel.SpecialForms,\n                    Atom,\n                    Base,\n                    Bitwise,\n                    Date,\n                    DateTime,\n                    Duration,\n                    Exception,\n                    Float,\n                    Function,\n                    Integer,\n                    Module,\n                    NaiveDateTime,\n                    Record,\n                    Regex,\n                    String,\n                    Time,\n                    Tuple,\n                    URI,\n                    Version,\n                    Version.Requirement,\n                    Access,\n                    Date.Range,\n                    Enum,\n                    Keyword,\n                    List,\n                    Map,\n                    MapSet,\n                    Range,\n                    Stream,\n                    File,\n                    File.Stat,\n                    File.Stream,\n                    IO,\n                    IO.ANSI,\n                    IO.Stream,\n                    OptionParser,\n                    Path,\n                    Port,\n                    StringIO,\n                    System,\n                    Calendar,\n                    Calendar.ISO,\n                    Calendar.TimeZoneDatabase,\n                    Calendar.UTCOnlyTimeZoneDatabase,\n                    Agent,\n                    Application,\n                    Config,\n                    Config.Provider,\n                    Config.Reader,\n                    DynamicSupervisor,\n                    GenServer,\n                    Node,\n                    Process,\n                    Registry,\n                    Supervisor,\n                    Task,\n                    Task.Supervisor,\n                    Collectable,\n                    Enumerable,\n                    Inspect,\n                    Inspect.Algebra,\n                    Inspect.Opts,\n                    List.Chars,\n                    Protocol,\n                    String.Chars,\n                    Code,\n                    Kernel.ParallelCompiler,\n                    Macro,\n                    Macro.Env,\n                    Behaviour,\n                    Dict,\n                    GenEvent,\n                    HashDict,\n                    HashSet,\n                    Set,\n                    Supervisor.Spec,\n                    ArgumentError,\n                    ArithmeticError,\n                    BadArityError,\n                    BadBooleanError,\n                    BadFunctionError,\n                    BadMapError,\n                    BadStructError,\n                    CaseClauseError,\n                    Code.LoadError,\n                    CompileError,\n                    CondClauseError,\n                    Enum.EmptyError,\n                    Enum.OutOfBoundsError,\n                    ErlangError,\n                    File.CopyError,\n                    File.Error,\n                    File.LinkError,\n                    File.RenameError,\n                    FunctionClauseError,\n                    IO.StreamError,\n                    Inspect.Error,\n                    KeyError,\n                    MatchError,\n                    Module.Types.Error,\n                    OptionParser.ParseError,\n                    Protocol.UndefinedError,\n                    Regex.CompileError,\n                    RuntimeError,\n                    SyntaxError,\n                    SystemLimitError,\n                    TokenMissingError,\n                    TryClauseError,\n                    DuneRestrictedError,\n                    UnicodeConversionError,\n                    Version.InvalidRequirementError,\n                    Version.InvalidVersionError,\n                    WithClauseError\n                  ] ++ extra_modules\n\n  @module_reprs @elixir_modules\n                |> Enum.flat_map(&Module.split/1)\n                |> Map.new(&{&1, String.to_existing_atom(&1)})\n\n  @spec load_atom_mapping(AtomMapping.t() | nil) :: :ok\n  def load_atom_mapping(nil), do: :ok\n\n  def load_atom_mapping(%AtomMapping{atoms: atoms}) do\n    count = Enum.count(atoms)\n    Process.put(:__Dune_atom_count__, count)\n\n    Enum.each(atoms, fn {atom, binary} ->\n      Process.put({:__Dune_atom__, binary}, atom)\n    end)\n  end\n\n  @spec static_atoms_encoder(String.t(), non_neg_integer()) :: {:ok, atom} | {:error, String.t()}\n  def static_atoms_encoder(binary, pool_size)\n      when is_binary(binary) and is_integer(pool_size) do\n    case @module_reprs do\n      %{^binary => atom} ->\n        {:ok, atom}\n\n      _ ->\n        if binary =~ \"Dune\" do\n          {:error, \"Atoms containing `Dune` are restricted for safety\"}\n        else\n          atom_category = categorize_atom_binary(binary)\n          do_static_atoms_encoder(binary, atom_category, pool_size)\n        end\n    end\n  end\n\n  @spec categorize_atom_binary(binary) :: atom_category\n  def categorize_atom_binary(atom_binary) do\n    charlist = String.to_charlist(atom_binary)\n\n    case {Code.Fragment.cursor_context(charlist), atom_binary} do\n      {{:alias, ^charlist}, _} -> :alias\n      {{:local_or_var, ^charlist}, \"_\" <> _} -> :private_var\n      {{:local_or_var, ^charlist}, _} -> :public_var\n      _ -> :other\n    end\n  end\n\n  defp do_static_atoms_encoder(\"Elixir.\" <> rest, :alias, pool_size) do\n    rest\n    |> String.split(\".\")\n    |> encode_many_atoms(pool_size, [])\n  end\n\n  defp do_static_atoms_encoder(binary, atom_category, pool_size) do\n    process_key = {:__Dune_atom__, binary}\n\n    case Process.get(process_key, nil) do\n      nil -> do_static_atoms_encoder(binary, atom_category, process_key, pool_size)\n      atom when is_atom(atom) -> {:ok, atom}\n    end\n  end\n\n  defp do_static_atoms_encoder(binary, atom_category, process_key, pool_size) do\n    {:ok, String.to_existing_atom(binary)}\n  rescue\n    ArgumentError ->\n      case new_atom(atom_category, pool_size) do\n        {:ok, atom} ->\n          Process.put(process_key, atom)\n\n          if atom_category == :other do\n            Process.put({:__Dune_atom_extra_info__, atom}, :wrapped)\n          end\n\n          {:ok, atom}\n\n        {:error, error} ->\n          {:error, error}\n      end\n  end\n\n  defp encode_many_atoms([], _pool_size, acc) do\n    {:ok, {:__aliases__, [], [Elixir | Enum.reverse(acc)]}}\n  end\n\n  defp encode_many_atoms([head | tail], pool_size, acc) do\n    case do_static_atoms_encoder(head, :alias, pool_size) do\n      {:ok, atom} -> encode_many_atoms(tail, pool_size, [atom | acc])\n      {:error, error} -> {:error, error}\n    end\n  end\n\n  @spec plain_atom_mapping :: AtomMapping.t()\n  def plain_atom_mapping() do\n    atoms =\n      for {{:__Dune_atom__, binary}, atom} <- Process.get() do\n        {atom, binary}\n      end\n\n    extra_info =\n      for {{:__Dune_atom_extra_info__, atom}, info} <- Process.get() do\n        {atom, info}\n      end\n\n    AtomMapping.from_atoms(atoms, extra_info)\n  end\n\n  defp new_atom(atom_category, pool_size) do\n    count = Process.get(:__Dune_atom_count__, 0) + 1\n\n    if count * @atom_categories > pool_size do\n      {:error, \"atom_pool_size exceeded, failed to parse atom\"}\n    else\n      Process.put(:__Dune_atom_count__, count)\n      atom = do_new_atom(atom_category, count)\n      {:ok, atom}\n    end\n  end\n\n  defp do_new_atom(:alias, count) do\n    :\"Dune_Atom_#{count}__\"\n  end\n\n  defp do_new_atom(:public_var, count) do\n    :\"a__Dune_atom_#{count}__\"\n  end\n\n  defp do_new_atom(category, count) when category in [:private_var, :other] do\n    :\"__Dune_atom_#{count}__\"\n  end\n\n  @spec encode_modules(Macro.t(), AtomMapping.t(), AtomMapping.t() | nil) ::\n          {Macro.t(), AtomMapping.t()}\n  def encode_modules(ast, plain_atom_mapping, existing_mapping) do\n    initial_acc = get_module_acc(existing_mapping)\n\n    {new_ast, acc} =\n      Macro.postwalk(ast, initial_acc, fn\n        {:__aliases__, ctx, atoms}, acc ->\n          {modules, new_acc} = remove_elixir_prefix(atoms) |> map_modules_ast(acc)\n          {{:__aliases__, ctx, modules}, new_acc}\n\n        other, acc ->\n          {other, acc}\n      end)\n\n    atom_mapping = build_module_mapping(acc, plain_atom_mapping)\n\n    {new_ast, atom_mapping}\n  end\n\n  defp get_module_acc(nil), do: %{}\n\n  defp get_module_acc(%AtomMapping{atoms: atoms, modules: modules}) do\n    reverse_atoms = Map.new(atoms, fn {atom, string} -> {string, atom} end)\n\n    Map.new(modules, fn {atom, string} ->\n      atoms = String.split(string, \".\") |> Enum.map(&Map.fetch!(reverse_atoms, &1))\n      {atoms, atom}\n    end)\n  end\n\n  defp remove_elixir_prefix(atoms = [Elixir, Elixir | _]), do: atoms\n  defp remove_elixir_prefix([Elixir | atoms]) when atoms != [], do: atoms\n  defp remove_elixir_prefix(atoms), do: atoms\n\n  defp map_modules_ast(atoms, acc) do\n    case acc do\n      %{^atoms => module_name} ->\n        {[module_name], acc}\n\n      _ ->\n        try do\n          atoms |> Enum.join(\".\") |> then(&\"Elixir.#{&1}\") |> String.to_existing_atom()\n        rescue\n          ArgumentError ->\n            module_name = :\"Dune_Module_#{map_size(acc) + 1}__\"\n            {[module_name], Map.put(acc, atoms, module_name)}\n        else\n          _ ->\n            {atoms, acc}\n        end\n    end\n  end\n\n  defp build_module_mapping(acc, plain_atom_mapping) do\n    modules =\n      Enum.map(acc, fn {atoms, module_name} ->\n        string = Enum.map_join(atoms, \".\", &AtomMapping.to_string(plain_atom_mapping, &1))\n        module = Module.concat([module_name])\n        {module, string}\n      end)\n\n    AtomMapping.add_modules(plain_atom_mapping, modules)\n  end\nend\n"
  },
  {
    "path": "lib/dune/parser/compile_env.ex",
    "content": "defmodule Dune.Parser.CompileEnv do\n  @moduledoc false\n\n  @type name_arity :: {atom, non_neg_integer}\n  @type maybe_fake_module :: {:real | :fake, module}\n  @type t :: %__MODULE__{\n          module: module | nil,\n          allowlist: module,\n          fake_modules: %{optional(module) => %{optional(name_arity) => :def | :defp}}\n          # aliases\n          # struct info\n          # requires\n        }\n  @enforce_keys [:module, :allowlist, :fake_modules]\n  defstruct @enforce_keys\n\n  def new(allowlist) do\n    %__MODULE__{\n      allowlist: allowlist,\n      module: nil,\n      fake_modules: %{}\n    }\n  end\n\n  def define_fake_module(env = %__MODULE__{fake_modules: fake_modules}, module, name_arities)\n      when is_atom(module) and is_map(name_arities) do\n    if module_already_exists?(module, fake_modules) do\n      throw({:module_conflict, module})\n    end\n\n    new_modules = Map.put(fake_modules, module, name_arities)\n\n    %{env | fake_modules: new_modules}\n  end\n\n  defp module_already_exists?(module, fake_modules) do\n    case fake_modules do\n      %{^module => _conflict} -> true\n      _ -> Code.ensure_loaded?(module)\n    end\n  end\n\n  def resolve_mfa(%__MODULE__{}, module, fun_name, arity)\n      when module in [Kernel, nil] and fun_name in [:def, :defp] and arity in [1, 2] do\n    :outside_module\n  end\n\n  def resolve_mfa(env = %__MODULE__{}, module, fun_name, arity)\n      when is_atom(module) and is_atom(fun_name) and is_integer(arity) do\n    actual_module = resolve_module(module, fun_name, arity)\n\n    case env.allowlist.fun_status(actual_module, fun_name, arity) do\n      :undefined_module ->\n        resolve_fake_module(env, module, fun_name, arity)\n\n      :undefined_function ->\n        case module do\n          nil -> resolve_fake_module(env, nil, fun_name, arity)\n          _ -> :undefined_function\n        end\n\n      :restricted ->\n        {:restricted, actual_module}\n\n      other ->\n        other\n    end\n  end\n\n  defp resolve_module(nil, fun_name, arity) do\n    if Macro.special_form?(fun_name, arity) do\n      Kernel.SpecialForms\n    else\n      Kernel\n    end\n  end\n\n  defp resolve_module(module, _fun_name, _arity), do: module\n\n  defp resolve_fake_module(%{module: nil}, nil, _fun_name, _arity), do: :undefined_function\n\n  defp resolve_fake_module(env = %{module: module}, nil, fun_name, arity) do\n    resolve_fake_module(env, module, fun_name, arity)\n  end\n\n  defp resolve_fake_module(env, module, fun_name, arity) do\n    # TODO check current module to know if defp OK\n    fun_with_arity = {fun_name, arity}\n\n    case env.fake_modules do\n      %{^module => %{^fun_with_arity => def_or_defp}} -> check_private(env, module, def_or_defp)\n      _ -> :undefined_module\n    end\n  end\n\n  defp check_private(_env, module, :def), do: {:fake, module}\n  defp check_private(%{module: module}, module, :defp), do: {:fake, module}\n  defp check_private(_env, _module, _def), do: :undefined_function\nend\n"
  },
  {
    "path": "lib/dune/parser/debug.ex",
    "content": "defmodule Dune.Parser.Debug do\n  @moduledoc false\n\n  def io_debug(ast) do\n    debug(ast) |> IO.puts()\n    ast\n  end\n\n  def debug(%{ast: ast}) when is_tuple(ast) do\n    ast_to_string(ast)\n  end\n\n  def debug(ast) when is_tuple(ast) do\n    ast_to_string(ast)\n  end\n\n  defp ast_to_string({:__block__, _, list}) do\n    Enum.map_join(list, \"\\n\", &Macro.to_string/1)\n  end\n\n  defp ast_to_string(ast) do\n    Macro.to_string(ast)\n  end\nend\n"
  },
  {
    "path": "lib/dune/parser/real_module.ex",
    "content": "defmodule Dune.Parser.RealModule do\n  @moduledoc false\n\n  @spec elixir_module?(module) :: boolean\n  def elixir_module?(module) do\n    module\n    |> Atom.to_string()\n    |> String.starts_with?(\"Elixir.\")\n  end\n\n  def list_functions(module)\n\n  def list_functions(Kernel.SpecialForms) do\n    [{:%{}, 2}] ++ Kernel.SpecialForms.__info__(:macros)\n  end\n\n  @spec list_functions(module) :: [{atom, non_neg_integer}]\n  def list_functions(module) when is_atom(module) do\n    if elixir_module?(module) do\n      module.__info__(:functions) ++ module.__info__(:macros)\n    else\n      for {f, _a} = fa <- module.module_info(:exports), f != :module_info, do: fa\n    end\n  end\n\n  def fun_exists?(module, fun_name, arity) do\n    # TODO replace with fun_status\n    fun_status(module, fun_name, arity) == :defined\n  end\n\n  def fun_status(module, fun_name, arity)\n\n  def fun_status(Kernel.SpecialForms, fun_name, arity) do\n    if Macro.special_form?(fun_name, arity) do\n      :defined\n    else\n      :undefined_function\n    end\n  end\n\n  def fun_status(module, fun_name, arity) do\n    cond do\n      not Code.ensure_loaded?(module) -> :undefined_module\n      function_exported?(module, fun_name, arity) -> :defined\n      macro_exported?(module, fun_name, arity) -> :defined\n      true -> :undefined_function\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/parser/safe_ast.ex",
    "content": "defmodule Dune.Parser.SafeAst do\n  @moduledoc false\n\n  @type t :: %__MODULE__{\n          ast: Macro.t(),\n          atom_mapping: Dune.AtomMapping.t(),\n          compile_env: Dune.Parser.CompileEnv.t(),\n          stdio: binary()\n        }\n  @enforce_keys [:ast, :atom_mapping, :compile_env]\n  defstruct @enforce_keys ++ [stdio: <<>>]\nend\n"
  },
  {
    "path": "lib/dune/parser/sanitizer.ex",
    "content": "defmodule Dune.Parser.Sanitizer do\n  @moduledoc false\n\n  alias Dune.{Failure, AtomMapping, Opts}\n  alias Dune.Parser.{CompileEnv, RealModule, UnsafeAst, SafeAst}\n\n  @env_variable_name :env__Dune__\n\n  @spec sanitize(UnsafeAst.t() | Failure.t(), Opts.t()) :: SafeAst.t() | Failure.t()\n  def sanitize(unsafe = %UnsafeAst{}, compile_env = %CompileEnv{}) do\n    case try_sanitize(unsafe.ast, compile_env) do\n      {:ok, safe_ast, new_env} ->\n        %SafeAst{\n          ast: safe_ast,\n          atom_mapping: unsafe.atom_mapping,\n          compile_env: new_env,\n          stdio: unsafe.stdio\n        }\n\n      {:restricted, module, fun, arity} ->\n        failure = Failure.restricted_function(module, fun, arity)\n        AtomMapping.replace_in_result(unsafe.atom_mapping, failure)\n\n      {:undefined_module, module, func_name, arity} ->\n        failure = Failure.undefined_module(module, func_name, arity)\n        AtomMapping.replace_in_result(unsafe.atom_mapping, failure)\n\n      {:undefined_function, module, func_name, arity} ->\n        failure = Failure.undefined_function(module, func_name, arity)\n        AtomMapping.replace_in_result(unsafe.atom_mapping, failure)\n\n      {:outside_module, def_or_defp} ->\n        message = \"** (ArgumentError) cannot invoke #{def_or_defp}/2 inside function/macro\"\n        new_failure(:exception, message, unsafe.atom_mapping)\n\n      {:module_invalid, module_ast, error_ctx} ->\n        line = Keyword.get(error_ctx, :line)\n        name = Macro.to_string(module_ast)\n        message = \"** (Dune.Eval.CompileError) nofile:#{line}: invalid module name: #{name}\"\n\n        new_failure(:exception, message, unsafe.atom_mapping)\n\n      {:module_restricted, ast} ->\n        message =\n          \"** (DuneRestrictedError) the following syntax is restricted inside defmodule:\\n         #{Macro.to_string(ast)}\"\n\n        new_failure(:module_restricted, message, unsafe.atom_mapping)\n\n      {:module_conflict, module} ->\n        message =\n          \"** (DuneRestrictedError) Following module cannot be defined/redefined: #{inspect(module)}\"\n\n        new_failure(:module_conflict, message, unsafe.atom_mapping)\n\n      {:definition_conflict, name_arity, previous_def, previous_ctx, conflict_def, conflict_ctx} ->\n        conflict_line = Keyword.get(conflict_ctx, :line)\n        previous_line = Keyword.get(previous_ctx, :line)\n        {name, arity} = name_arity\n\n        message =\n          \"** (Dune.Eval.CompileError) nofile:#{conflict_line}: \" <>\n            \"#{conflict_def} #{name}/#{arity} already defined as #{previous_def} in nofile:#{previous_line}\"\n\n        new_failure(:exception, message, unsafe.atom_mapping)\n\n      {:parsing_error, ast} ->\n        message = \"dune parsing error: failed to safe parse\\n         #{Macro.to_string(ast)}\"\n        new_failure(:parsing, message, unsafe.atom_mapping)\n\n      {:bin_modifier_restricted, ast} ->\n        message =\n          \"** (DuneRestrictedError) bitstring modifier is restricted:\\n         #{Macro.to_string(ast)}\"\n\n        new_failure(:restricted, message, unsafe.atom_mapping)\n\n      {:bin_modifier_size, max_size} ->\n        message = \"** (DuneRestrictedError) size modifiers above #{max_size} are restricted\"\n        new_failure(:restricted, message, unsafe.atom_mapping)\n\n      {:exception, error} ->\n        message = Exception.format(:error, error)\n        new_failure(:exception, message, unsafe.atom_mapping)\n    end\n  end\n\n  def sanitize(%Failure{} = failure, _opts), do: failure\n\n  defp new_failure(type, message, atom_mapping) when is_atom(type) and is_binary(message) do\n    failure = %Failure{type: type, message: message, stdio: \"\"}\n    AtomMapping.replace_in_result(atom_mapping, failure)\n  end\n\n  # XXX this is a bit hacky and brute-force approach!\n  # ideally the AST transformation is robust enough so we don't need it\n  defp try_sanitize(ast, env) do\n    do_sanitize_main(ast, env)\n  rescue\n    error ->\n      error\n      |> then(&Exception.blame(:error, &1, __STACKTRACE__))\n      |> elem(0)\n      |> then(&Exception.format(:error, &1))\n      |> IO.warn()\n\n      {:parsing_error, ast}\n  catch\n    thrown -> thrown\n  end\n\n  defp do_sanitize_main({:__block__, ctx, list}, env) do\n    {list_ast, env} = do_sanitize_main_list(list, env)\n    block_ast = {:__block__, ctx, list_ast}\n    {:ok, block_ast, env}\n  end\n\n  defp do_sanitize_main(single, env) do\n    case do_sanitize_main_list([single], env) do\n      {[safe_single], env} ->\n        {:ok, safe_single, env}\n\n      {list_ast, env} when is_list(list_ast) ->\n        block_ast = {:__block__, [], list_ast}\n        {:ok, block_ast, env}\n    end\n  end\n\n  defp do_sanitize_main_list(list, env) when is_list(list) do\n    {defmodules, instructions} = Enum.split_with(list, &defmodule_block?/1)\n\n    raw_fun_definitions = Enum.map(defmodules, &parse_module_definition/1)\n\n    env =\n      Enum.reduce(raw_fun_definitions, env, fn {module, fun_defs}, acc ->\n        fun_name_arities =\n          Map.new(fun_defs, fn {name_arity, [raw_definition | _]} ->\n            {name_arity, elem(raw_definition, 0)}\n          end)\n\n        CompileEnv.define_fake_module(acc, module, fun_name_arities)\n      end)\n\n    module_definitions = Enum.map(raw_fun_definitions, &sanitize_module_definition(&1, env))\n\n    sanitized_instructions =\n      case {raw_fun_definitions, do_sanitize(instructions, env)} do\n        {[_ | _], []} ->\n          {last_module, _} = List.last(raw_fun_definitions)\n          [quote(do: {:module, unquote(last_module), nil, nil})]\n\n        {_, sanitized_instructions} ->\n          sanitized_instructions\n      end\n\n    {module_definitions ++ sanitized_instructions, env}\n  end\n\n  defp defmodule_block?({:defmodule, _, _}), do: true\n  defp defmodule_block?(_), do: false\n\n  defp parse_module_definition({:defmodule, ctx, [module_name, [do: do_ast]]}) do\n    module_name\n    |> validate_module_name(ctx)\n    |> do_parse_module_definition(do_ast)\n  end\n\n  defp parse_module_definition(ast = {:defmodule, _, _}) do\n    throw({:parsing_error, ast})\n  end\n\n  defp validate_module_name(module, _ctx)\n       when is_atom(module) and module not in [nil, false, true] do\n    module\n  end\n\n  defp validate_module_name(module_def = {:__aliases__, _, [_module_atom]}, _ctx) do\n    Macro.expand_once(module_def, __ENV__)\n  end\n\n  defp validate_module_name(module_ast, ctx) do\n    throw({:module_invalid, module_ast, ctx})\n  end\n\n  defp do_parse_module_definition(module_name, do_ast) do\n    fun_definitions =\n      block_to_list(do_ast)\n      |> Enum.map(&parse_fun_definition/1)\n      |> Enum.filter(& &1)\n      |> Enum.flat_map(&expand_defaults/1)\n      |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))\n      |> tap(&check_definition_conflicts/1)\n\n    {module_name, fun_definitions}\n  end\n\n  defp block_to_list({:__block__, _, list}) when is_list(list), do: list\n  defp block_to_list(single) when is_tuple(single), do: [single]\n\n  defp parse_fun_definition({def_or_defp, ctx, [signature, [do: body]]})\n       when def_or_defp in [:def, :defp] do\n    {header, guards} = parse_fun_signature(signature)\n    {name, args} = Macro.decompose_call(header)\n    {args, defaults} = extract_default_args(args, 0, [], [])\n    name_arity = {name, length(args)}\n    definition = {def_or_defp, ctx, args, body, guards}\n    {name_arity, definition, defaults}\n  end\n\n  defp parse_fun_definition({:@, _, [{doc, _, [value]}]})\n       when doc in ~w[moduledoc doc]a and (value == false or is_binary(value)) do\n    nil\n  end\n\n  defp parse_fun_definition({:@, _, [{typespec, _, [{:\"::\", _, _}]}]})\n       when typespec in ~w[spec type typep opaque]a do\n    nil\n  end\n\n  defp parse_fun_definition(unsupported_ast) do\n    throw({:module_restricted, unsupported_ast})\n  end\n\n  # TODO else raise unsupported!\n\n  defp extract_default_args([], _index, arg_acc, defaults) do\n    {Enum.reverse(arg_acc), defaults}\n  end\n\n  defp extract_default_args([{:\\\\, _, [arg, default]} | args], index, arg_acc, defaults) do\n    extract_default_args(args, index + 1, [arg | arg_acc], [{index, default} | defaults])\n  end\n\n  defp extract_default_args([arg | args], index, arg_acc, defaults) do\n    extract_default_args(args, index + 1, [arg | arg_acc], defaults)\n  end\n\n  defp expand_defaults({name_arity, definition, _defaults = []}) do\n    [{name_arity, definition}]\n  end\n\n  defp expand_defaults({name_arity = {name, arity}, definition, defaults}) do\n    def_or_defp = elem(definition, 0)\n    do_expand_defaults(name, arity, def_or_defp, defaults, [{name_arity, definition}])\n  end\n\n  defp do_expand_defaults(_name, _arity, _def_or_defp, [], acc) do\n    acc\n  end\n\n  defp do_expand_defaults(\n         name,\n         arity,\n         def_or_defp,\n         [{default_index, default_value} | defaults],\n         acc\n       ) do\n    args = Macro.generate_arguments(arity, nil)\n    arity = arity - 1\n\n    args_without_default = List.delete_at(args, default_index)\n\n    args_in_expr =\n      Enum.with_index(args, fn\n        _arg, ^default_index -> default_value\n        arg, _index -> arg\n      end)\n\n    definition = {def_or_defp, [], args_without_default, {name, [], args_in_expr}, nil}\n\n    acc = [{{name, arity}, definition} | acc]\n\n    do_expand_defaults(name, arity, def_or_defp, defaults, acc)\n  end\n\n  defp check_definition_conflicts(grouped_definitions) do\n    Enum.each(grouped_definitions, fn {name_arity, [head | tail]} ->\n      check_definition_conflict(name_arity, head, tail)\n    end)\n  end\n\n  defp check_definition_conflict(_name_arity, _, []), do: :ok\n\n  defp check_definition_conflict(\n         name_arity,\n         head = {def_or_defp, _, _, _, _},\n         [\n           {def_or_defp, _, _, _, _} | rest\n         ]\n       ) do\n    check_definition_conflict(name_arity, head, rest)\n  end\n\n  defp check_definition_conflict(name_arity, {previous_def, previous_ctx, _, _, _}, [\n         {conflict_def, conflict_ctx, _, _, _} | _\n       ]) do\n    throw(\n      {:definition_conflict, name_arity, previous_def, previous_ctx, conflict_def, conflict_ctx}\n    )\n  end\n\n  defp parse_fun_signature({:when, _, [header, guards]}) do\n    {header, guards}\n  end\n\n  defp parse_fun_signature(header) do\n    {header, nil}\n  end\n\n  defp sanitize_module_definition({module, fun_defs}, env) do\n    env = %{env | module: module}\n\n    public_funs_ast =\n      fun_defs\n      |> Enum.map(&sanitize_fun(&1, env))\n      |> Enum.group_by(&elem(&1, 0), fn {_fun, arity, ast} -> {arity, ast} end)\n      |> Enum.map(fn {fun_name, list} ->\n        {fun_name, to_map_ast(list)}\n      end)\n      |> to_map_ast()\n\n    quote do\n      unquote(env_variable()) =\n        Dune.Eval.Env.add_module(\n          unquote(env_variable()),\n          unquote(module),\n          %Dune.Eval.FakeModule{public_funs: unquote(public_funs_ast)}\n        )\n    end\n  end\n\n  defp to_map_ast(list_ast) when is_list(list_ast), do: {:%{}, [], list_ast}\n\n  defp sanitize_fun({{fun_name, arity}, definitions}, env) do\n    args = Macro.var(:args, nil)\n\n    bottom_clause =\n      quote do\n        args ->\n          raise %Dune.Eval.FunctionClauseError{\n            module: unquote(env.module),\n            function: unquote(fun_name),\n            args: args\n          }\n      end\n\n    clauses = Enum.map(definitions, &sanitize_fun_clause(&1, env)) ++ bottom_clause\n    env_var = env_variable_if_used(clauses)\n\n    anonymous_ast =\n      {:fn, [],\n       [\n         {:->, [],\n          [\n            [env_var, args],\n            {:case, [],\n             [\n               args,\n               [do: clauses]\n             ]}\n          ]}\n       ]}\n\n    {fun_name, arity, anonymous_ast}\n  end\n\n  defp sanitize_fun_clause({_def_or_defp, ctx, args, body, guards}, env) do\n    safe_args = do_sanitize(args, env)\n    safe_body = do_sanitize(body, env)\n    safe_guards = do_sanitize(guards, env)\n    definition_to_clause(ctx, safe_args, safe_body, safe_guards)\n  end\n\n  defp definition_to_clause(ctx, args, body, _guards = nil) do\n    {:->, ctx, [[args], body]}\n  end\n\n  defp definition_to_clause(ctx, args, body, guards) when is_tuple(guards) do\n    args_and_guards = [{:when, [], [args, guards]}]\n    {:->, ctx, [args_and_guards, body]}\n  end\n\n  defp env_variable_if_used(asts) do\n    uses_variable?(asts, @env_variable_name)\n\n    case uses_variable?(asts, @env_variable_name) do\n      true -> env_variable()\n      false -> underscore_env_variable()\n    end\n  end\n\n  defp uses_variable?([], _variable_name), do: false\n\n  defp uses_variable?([head | tail], variable_name) do\n    case uses_variable?(head, variable_name) do\n      true -> true\n      false -> uses_variable?(tail, variable_name)\n    end\n  end\n\n  defp uses_variable?({variable_name, _, nil}, variable_name), do: true\n\n  defp uses_variable?({_, _, list}, variable_name) when is_list(list) do\n    uses_variable?(list, variable_name)\n  end\n\n  defp uses_variable?({x, y}, variable_name) do\n    uses_variable?(x, variable_name) or uses_variable?(y, variable_name)\n  end\n\n  defp uses_variable?(_ast, _variable_name), do: false\n\n  defp do_sanitize(ast, env)\n\n  defp do_sanitize(raw_value, _env)\n       when is_atom(raw_value) or is_number(raw_value) or is_binary(raw_value) do\n    raw_value\n  end\n\n  defp do_sanitize(list, env)\n       when is_list(list) do\n    sanitize_args(list, env)\n  end\n\n  defp do_sanitize({arg1, arg2}, env) do\n    [safe_arg1, safe_arg2] = sanitize_args([arg1, arg2], env)\n    {safe_arg1, safe_arg2}\n  end\n\n  defp do_sanitize({atom, _, _} = raw, env) when atom in [:__block__, :when, :<-, :->, :|] do\n    sanitize_args_in_node(raw, env)\n  end\n\n  defp do_sanitize({name, _, atom} = variable, _env)\n       when is_atom(name) and atom in [nil, Elixir] do\n    unless authorized_var_name?(name) do\n      throw({:restricted, Kernel, name, 0})\n    end\n\n    variable\n  end\n\n  defp do_sanitize({:&, _, args}, env) do\n    [ast] = args\n\n    sanitize_capture(ast, env)\n  end\n\n  defp do_sanitize({:<<>>, meta, args}, env) do\n    sanitized_args =\n      Enum.map(args, fn\n        {:\"::\", meta, [expr, modifier]} ->\n          {:\"::\", meta, [do_sanitize(expr, env), check_bin_modifier(modifier)]}\n\n        arg ->\n          do_sanitize(arg, env)\n      end)\n\n    {:<<>>, meta, sanitized_args}\n  end\n\n  defp do_sanitize({{:., _, [left, right]}, ctx, args} = raw, env)\n       when is_atom(right) and is_list(args) do\n    case left do\n      atom when is_atom(atom) ->\n        do_sanitize_function(raw, env)\n\n      {:__aliases__, _, list} when is_list(list) ->\n        do_sanitize_function(raw, env)\n\n      _ ->\n        do_sanitize_dot(left, right, args, ctx, env)\n    end\n  end\n\n  defp do_sanitize({{:., dot_ctx, [{fn_or_ampersand, _, _} = anonymous]}, ctx, args}, env)\n       when fn_or_ampersand in [:fn, :&] do\n    safe_anonymous = do_sanitize(anonymous, env)\n    safe_args = sanitize_args(args, env)\n    {{:., dot_ctx, [safe_anonymous]}, ctx, safe_args}\n  end\n\n  defp do_sanitize({:dbg, meta, [expr]}, env) do\n    quote do\n      value = unquote(do_sanitize(expr, env))\n\n      IO.puts([\n        \"[nofile:\",\n        to_string(unquote(meta[:line])),\n        \": (file)]\\n\",\n        unquote(Macro.to_string(expr)),\n        \" #=> \",\n        inspect(value, pretty: true),\n        ?\\n\n      ])\n\n      value\n    end\n  end\n\n  defp do_sanitize({:|>, _, [expr, {:dbg, meta, []}]}, env) do\n    header =\n      quote do\n        [\"[nofile:\", to_string(unquote(meta[:line])), \": (file)]\\n\"]\n      end\n\n    quote do\n      value = unquote(dbg_pipeline(expr, env, header))\n      IO.write(\"\\n\")\n      value\n    end\n  end\n\n  defp do_sanitize({:|>, _, _} = ast, env) do\n    case try_expand_once(ast) do\n      {atom, _, _} = expanded when atom != :|> ->\n        do_sanitize(expanded, env)\n    end\n  end\n\n  defp do_sanitize({_, _, args} = raw, env) when is_list(args) do\n    do_sanitize_function(raw, env)\n  end\n\n  defp try_expand_once(ast) do\n    Macro.expand_once(ast, __ENV__)\n  rescue\n    error ->\n      throw({:exception, error})\n  end\n\n  defp do_sanitize_dot(left, key, args, ctx, env) do\n    safe_left = do_sanitize(left, env)\n    safe_args = sanitize_args(args, env)\n\n    if args == [] and {:no_parens, true} in ctx do\n      quote do\n        Dune.Shims.Kernel.safe_dot(\n          unquote(env_variable()),\n          unquote(safe_left),\n          unquote(key)\n        )\n      end\n    else\n      quote do\n        Dune.Shims.Kernel.safe_apply(\n          unquote(env_variable()),\n          unquote(safe_left),\n          unquote(key),\n          unquote(safe_args)\n        )\n      end\n    end\n  end\n\n  defp do_sanitize_function({func, ctx, atom}, env) when atom in [nil, Elixir] do\n    do_sanitize_function({func, ctx, []}, env)\n  end\n\n  defp do_sanitize_function({{:., _, [{_variable_function, _, atom}]}, _, args} = raw, env)\n       when atom in [nil, Elixir] and is_list(args) do\n    sanitize_args_in_node(raw, env)\n  end\n\n  defp do_sanitize_function({func, _, args} = raw, env)\n       when is_list(args) do\n    {module, func_name} = extract_module_and_fun(func)\n\n    arity = length(args)\n\n    case CompileEnv.resolve_mfa(env, module, func_name, arity) do\n      {:restricted, resolved_module} ->\n        throw({:restricted, resolved_module, func_name, arity})\n\n      {:shimmed, shim_module, shim_func} ->\n        safe_args = sanitize_args(args, env)\n\n        quote do\n          unquote(shim_module).unquote(shim_func)(\n            unquote(env_variable()),\n            unquote_splicing(safe_args)\n          )\n        end\n\n      {:fake, fake_module} ->\n        safe_args = sanitize_args(args, env)\n\n        quote do\n          Dune.Eval.Env.apply_fake(\n            unquote(env_variable()),\n            unquote(fake_module),\n            unquote(func_name),\n            unquote(safe_args)\n          )\n        end\n\n      :allowed ->\n        sanitize_args_in_node(raw, env)\n\n      error ->\n        handle_mfa_error(error, module, func_name, arity)\n    end\n  end\n\n  defp extract_module_and_fun({:., _, [{:__aliases__, _, modules}, func_name]}) do\n    {modules |> Module.concat(), func_name}\n  end\n\n  defp extract_module_and_fun({:., _, [erlang_module, func_name]}) when is_atom(erlang_module) do\n    {erlang_module, func_name}\n  end\n\n  defp extract_module_and_fun(func_name) when is_atom(func_name) do\n    {nil, func_name}\n  end\n\n  defp sanitize_capture({:/, _, [{func, _, _}, arity]} = raw, env) when is_integer(arity) do\n    {module, func_name} = extract_module_and_fun(func)\n\n    case CompileEnv.resolve_mfa(env, module, func_name, arity) do\n      {:restricted, resolved_module} ->\n        throw({:restricted, resolved_module, func_name, arity})\n\n      {:fake, fake_module} ->\n        args = Macro.generate_unique_arguments(arity, nil)\n\n        quote do\n          fn unquote_splicing(args) ->\n            Dune.Eval.Env.apply_fake(\n              unquote(env_variable()),\n              unquote(fake_module),\n              unquote(func_name),\n              unquote(args)\n            )\n          end\n        end\n\n      {:shimmed, shim_module, shim_func} ->\n        args = Macro.generate_unique_arguments(arity, nil)\n\n        quote do\n          # FIXME pass env here!\n          fn unquote_splicing(args) ->\n            unquote(shim_module).unquote(shim_func)(\n              unquote(env_variable()),\n              unquote_splicing(args)\n            )\n          end\n        end\n\n      :allowed ->\n        {:&, [], [raw]}\n\n      error ->\n        handle_mfa_error(error, module, func_name, arity)\n    end\n  end\n\n  defp handle_mfa_error(:undefined_module, module, func_name, arity) do\n    throw({:undefined_module, module, func_name, arity})\n  end\n\n  defp handle_mfa_error(:undefined_function, module, func_name, arity) do\n    throw({:undefined_function, module, func_name, arity})\n  end\n\n  defp handle_mfa_error(:outside_module, _module, func_name, _arity)\n       when func_name in [:def, :defp] do\n    throw({:outside_module, func_name})\n  end\n\n  defp sanitize_capture(capture_arg, env) do\n    safe_capture_arg = do_sanitize(capture_arg, env)\n    {:&, [], [safe_capture_arg]}\n  end\n\n  defp sanitize_args(args, env) when is_list(args) do\n    Enum.map(args, &do_sanitize(&1, env))\n  end\n\n  defp sanitize_args_in_node({_, _, args} = raw, env) when is_list(args) do\n    safe_args = sanitize_args(args, env)\n    put_elem(raw, 2, safe_args)\n  end\n\n  defp dbg_pipeline({:|>, _, [left, {fun, meta, args} = right]}, env, header)\n       when is_list(args) and fun != :dbg do\n    ast_with_placeholder = do_sanitize({fun, meta, [:__DUNE_RESERVED__ | args]}, env)\n\n    ast =\n      Macro.prewalk(ast_with_placeholder, fn\n        :__DUNE_RESERVED__ -> dbg_pipeline(left, env, header)\n        other -> other\n      end)\n\n    quote do\n      value = unquote(ast)\n\n      IO.puts([\n        \"|> \",\n        unquote(Macro.to_string(right)),\n        \" #=> \",\n        inspect(value, pretty: true)\n      ])\n\n      value\n    end\n  end\n\n  defp dbg_pipeline(expr, env, header) do\n    quote do\n      value = unquote(do_sanitize(expr, env))\n\n      IO.puts([\n        unquote_splicing(header),\n        unquote(Macro.to_string(expr)),\n        \" #=> \",\n        inspect(value, pretty: true)\n      ])\n\n      value\n    end\n  end\n\n  @max_segment_size 256\n  @binary_modifiers [:binary, :bytes]\n  @allowed_modifiers [:integer, :float, :bits, :bitstring, :utf8, :utf16, :utf32] ++\n                       [:signed, :unsigned, :little, :big, :native]\n\n  defp check_bin_modifier(modifier) do\n    {size, unit} = check_bin_modifier_size(modifier, 8, nil)\n    unit = unit || 1\n\n    if size * unit > @max_segment_size do\n      throw({:bin_modifier_size, @max_segment_size})\n    end\n\n    modifier\n  end\n\n  defp check_bin_modifier_size({:-, _, [left, right]}, size, unit) do\n    {size, unit} = check_bin_modifier_size(left, size, unit)\n    check_bin_modifier_size(right, size, unit)\n  end\n\n  defp check_bin_modifier_size(modifier, size, unit) do\n    case modifier do\n      new_size when is_integer(new_size) ->\n        {new_size, unit}\n\n      {:size, _, [new_size]} when is_integer(new_size) ->\n        {new_size, unit}\n\n      {:unit, _, [new_unit]} when is_integer(new_unit) ->\n        {size, new_unit}\n\n      {:*, _, [new_size, new_unit]} when is_integer(new_size) and is_integer(new_unit) ->\n        {new_size, new_unit}\n\n      {:size, _, [{:^, _, [{var, _, ctx}]}]} when is_atom(var) and is_atom(ctx) ->\n        {size, unit}\n\n      {atom, _, ctx} when atom in @binary_modifiers and is_atom(ctx) ->\n        {size, unit || 8}\n\n      {atom, _, ctx} when atom in @allowed_modifiers and is_atom(ctx) ->\n        {size, unit}\n\n      other ->\n        throw({:bin_modifier_restricted, other})\n    end\n  end\n\n  defp env_variable do\n    Macro.var(@env_variable_name, nil)\n  end\n\n  defp underscore_env_variable do\n    Macro.var(:_env__Dune__, nil)\n  end\n\n  defp authorized_var_name?(name) do\n    # e.g. recompile could be interpreted as recompile/0\n    not (RealModule.fun_exists?(Kernel, name, 0) or Macro.special_form?(name, 0))\n  end\nend\n"
  },
  {
    "path": "lib/dune/parser/string_parser.ex",
    "content": "defmodule Dune.Parser.StringParser do\n  @moduledoc false\n\n  alias Dune.{AtomMapping, Failure, Opts}\n  alias Dune.Helpers.Diagnostics\n  alias Dune.Parser.{AtomEncoder, UnsafeAst}\n\n  @typep previous_session :: %{atom_mapping: AtomMapping.t()}\n\n  # TODO options: parse timeout & max atoms\n  @spec parse_string(String.t(), Opts.t(), previous_session | nil, boolean) ::\n          UnsafeAst.t() | Failure.t()\n  def parse_string(string, opts, previous_session, encode_modules? \\\\ true)\n      when is_binary(string) and is_boolean(encode_modules?) do\n    # import: do in a different process because the AtomEncoder pollutes the Process dict\n    fn -> do_parse_string(string, opts, previous_session, encode_modules?) end\n    |> Task.async()\n    |> Task.await()\n  end\n\n  defp do_parse_string(\n         string,\n         %Opts{atom_pool_size: pool_size},\n         previous_session,\n         encode_modules?\n       ) do\n    maybe_load_atom_mapping(previous_session)\n    encoder = fn binary, _ctx -> AtomEncoder.static_atoms_encoder(binary, pool_size) end\n\n    {result, diagnostics} =\n      Diagnostics.with_diagnostics_polyfill(fn ->\n        Code.string_to_quoted(string, static_atoms_encoder: encoder, existing_atoms_only: true)\n      end)\n\n    case result do\n      {:ok, ast} ->\n        maybe_encode_modules(ast, previous_session, encode_modules?)\n        |> Diagnostics.prepend_diagnostics(diagnostics)\n\n      {:error, {_ctx, error, token}} ->\n        handle_failure(error, token)\n    end\n  end\n\n  defp maybe_load_atom_mapping(nil), do: :ok\n\n  defp maybe_load_atom_mapping(%{atom_mapping: atom_mapping}) do\n    AtomEncoder.load_atom_mapping(atom_mapping)\n  end\n\n  defp maybe_encode_modules(ast, previous_session, encode_modules?) do\n    plain_atom_mapping = AtomEncoder.plain_atom_mapping()\n\n    {new_ast, atom_mapping} =\n      if encode_modules? do\n        existing_mapping = previous_session[:atom_mapping]\n        AtomEncoder.encode_modules(ast, plain_atom_mapping, existing_mapping)\n      else\n        {ast, plain_atom_mapping}\n      end\n\n    %UnsafeAst{ast: new_ast, atom_mapping: atom_mapping}\n  end\n\n  defp handle_failure(\"Atoms containing\" <> _ = error, token) do\n    %Failure{message: error <> token, type: :restricted}\n  end\n\n  defp handle_failure(error, token) do\n    failure = do_handle_failure(error, token)\n\n    AtomEncoder.plain_atom_mapping()\n    |> AtomMapping.replace_in_result(failure)\n  end\n\n  defp do_handle_failure({error, explanation}, token)\n       when is_binary(error) and is_binary(explanation) do\n    message = IO.iodata_to_binary([error, token, explanation])\n    %Failure{message: message, type: :parsing}\n  end\n\n  defp do_handle_failure(error, token) when is_binary(error) do\n    %Failure{message: error <> token, type: :parsing}\n  end\nend\n"
  },
  {
    "path": "lib/dune/parser/unsafe_ast.ex",
    "content": "defmodule Dune.Parser.UnsafeAst do\n  @moduledoc false\n\n  @type t :: %__MODULE__{\n          ast: Macro.t(),\n          atom_mapping: Dune.AtomMapping.t(),\n          stdio: binary()\n        }\n  @enforce_keys [:ast, :atom_mapping]\n  defstruct @enforce_keys ++ [stdio: <<>>]\nend\n"
  },
  {
    "path": "lib/dune/parser.ex",
    "content": "defmodule Dune.Parser do\n  @moduledoc false\n\n  alias Dune.{AtomMapping, Success, Failure, Opts}\n  alias Dune.Parser.{CompileEnv, StringParser, Sanitizer, SafeAst, UnsafeAst}\n\n  @typep previous_session :: %{\n           atom_mapping: AtomMapping.t(),\n           compile_env: Dune.Parser.CompileEnv.t()\n         }\n\n  @spec parse_string(String.t(), Opts.t(), previous_session | nil) :: SafeAst.t() | Failure.t()\n  def parse_string(string, opts = %Opts{}, previous_session \\\\ nil) when is_binary(string) do\n    compile_env = get_compile_env(opts, previous_session)\n\n    string\n    |> do_parse_string(opts, previous_session)\n    |> Sanitizer.sanitize(compile_env)\n  end\n\n  defp do_parse_string(string, opts = %{max_length: max_length}, previous_session) do\n    case String.length(string) do\n      length when length > max_length ->\n        %Failure{type: :parsing, message: \"max code length exceeded: #{length} > #{max_length}\"}\n\n      _ ->\n        StringParser.parse_string(string, opts, previous_session)\n    end\n  end\n\n  @spec parse_quoted(Macro.t(), Opts.t(), previous_session | nil) :: SafeAst.t()\n  def parse_quoted(quoted, opts = %Opts{}, previous_session \\\\ nil) do\n    compile_env = get_compile_env(opts, previous_session)\n\n    quoted\n    |> unsafe_quoted()\n    |> Sanitizer.sanitize(compile_env)\n  end\n\n  def unsafe_quoted(ast) do\n    %UnsafeAst{ast: ast, atom_mapping: AtomMapping.new()}\n  end\n\n  defp get_compile_env(opts, nil) do\n    CompileEnv.new(opts.allowlist)\n  end\n\n  defp get_compile_env(opts, %{compile_env: compile_env}) do\n    %{compile_env | allowlist: opts.allowlist}\n  end\n\n  @spec string_to_quoted(String.t(), Opts.t()) :: Success.t() | Failure.t()\n  def string_to_quoted(string, opts) do\n    with unsafe = %UnsafeAst{} <- StringParser.parse_string(string, opts, nil, false) do\n      inspected = inspect(unsafe.ast, pretty: opts.pretty)\n      inspected = AtomMapping.replace_in_string(unsafe.atom_mapping, inspected)\n\n      %Success{\n        value: unsafe.ast,\n        inspected: inspected,\n        stdio: unsafe.stdio\n      }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/session.ex",
    "content": "defmodule Dune.Session do\n  @moduledoc \"\"\"\n  Sessions provide a way to evaluate code and keep state (bindings, modules...) between evaluations.\n\n  - Use `Dune.eval_string/2` to execute code as a one-off\n  - Use `Dune.Session.eval_string/3` to execute consecutive code blocks\n\n  `Dune.Session` could be used to implement something like a safe IEx shell, or to compile a module\n  once and call it several times without the overhead of parsing.\n\n  `Dune.Session` is also a struct that is used to store the state of an evaluation.\n\n  Only the following fields are public:\n  - `last_result`: contains the result of the last evaluation, or `nil` for empty sessions\n\n  Other fields are private and shouldn't be accessed directly.\n\n  \"\"\"\n\n  alias Dune.{Allowlist, Eval, Parser, Success, Failure, Opts}\n\n  @opaque private_env :: Eval.Env.t()\n  @opaque private_compile_env :: Parser.CompileEnv.t()\n\n  @typedoc \"\"\"\n  The type of a `Dune.Session`.\n  \"\"\"\n  @type t :: %__MODULE__{\n          last_result: nil | Success.t() | Failure.t(),\n          env: private_env,\n          compile_env: private_compile_env,\n          bindings: keyword\n        }\n  @enforce_keys [:env, :compile_env, :bindings, :last_result]\n  defstruct @enforce_keys\n\n  @default_env Eval.Env.new(Dune.AtomMapping.new(), Allowlist.Default)\n  @default_compile_env Parser.CompileEnv.new(Allowlist.Default)\n\n  @doc \"\"\"\n  Returns a new empty session.\n\n  ## Examples\n\n      iex> Dune.Session.new()\n      #Dune.Session<last_result: nil, ...>\n\n  \"\"\"\n  @spec new :: t\n  def new do\n    %__MODULE__{\n      env: @default_env,\n      compile_env: @default_compile_env,\n      bindings: [],\n      last_result: nil\n    }\n  end\n\n  @doc \"\"\"\n  Evaluates the provided `string` in the context of the `session` and returns a new session.\n\n  The result will be available in the `last_result` key.\n  In case of a success, the variable bindings or created modules will be saved in the session.\n  In case of a failure, the rest of the session state won't be updated, so it is possible to\n  keep executing instructions after a failure\n\n  ## Examples\n\n      iex> Dune.Session.new()\n      ...> |> Dune.Session.eval_string(\"x = 1\")\n      ...> |> Dune.Session.eval_string(\"x + 2\")\n      #Dune.Session<last_result: %Dune.Success{value: 3, inspected: \"3\", stdio: \"\"}, ...>\n\n      iex> Dune.Session.new()\n      ...> |> Dune.Session.eval_string(\"x = 1\")\n      ...> |> Dune.Session.eval_string(\"x = x / 0\")  # will fail, but the previous state is kept\n      ...> |> Dune.Session.eval_string(\"x + 2\")\n      #Dune.Session<last_result: %Dune.Success{value: 3, inspected: \"3\", stdio: \"\"}, ...>\n\n  \"\"\"\n  @spec eval_string(t, String.t(), keyword) :: t\n  def eval_string(session = %__MODULE__{}, string, opts \\\\ []) do\n    opts = Opts.validate!(opts)\n\n    parse_state = %{atom_mapping: session.env.atom_mapping, compile_env: session.compile_env}\n    parsed = Parser.parse_string(string, opts, parse_state)\n\n    parsed\n    |> Eval.run(opts, session)\n    |> add_result_to_session(session, parsed)\n  end\n\n  defp add_result_to_session(result = %Success{value: {value, env, bindings}}, session, %{\n         compile_env: compile_env\n       }) do\n    result = %{result | value: value}\n    %{session | env: env, compile_env: compile_env, last_result: result, bindings: bindings}\n  end\n\n  defp add_result_to_session(result = %Failure{}, session, _) do\n    %{session | last_result: result}\n  end\n\n  defimpl Inspect do\n    import Inspect.Algebra\n\n    def inspect(session, opts) do\n      container_doc(\n        \"#Dune.Session<\",\n        [last_result: session.last_result],\n        \", ...>\",\n        opts,\n        &do_inspect/2,\n        break: :strict\n      )\n    end\n\n    defp do_inspect({key, value}, opts) do\n      key = inspect_as_key(key) |> color(:atom, opts)\n      concat(key, concat(\" \", to_doc(value, opts)))\n    end\n\n    if Code.ensure_loaded?(Macro) and function_exported?(Macro, :inspect_atom, 2) do\n      defp inspect_as_key(key), do: Macro.inspect_atom(:key, key)\n    else\n      defp inspect_as_key(key), do: Code.Identifier.inspect_as_key(key)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/shims/atom.ex",
    "content": "defmodule Dune.Shims.Atom do\n  @moduledoc false\n\n  alias Dune.AtomMapping\n\n  def to_string(env, atom) when is_atom(atom) do\n    AtomMapping.to_string(env.atom_mapping, atom)\n  end\n\n  def to_charlist(env, atom) when is_atom(atom) do\n    __MODULE__.to_string(env, atom) |> String.to_charlist()\n  end\nend\n"
  },
  {
    "path": "lib/dune/shims/enum.ex",
    "content": "defmodule Dune.Shims.Enum do\n  @moduledoc false\n\n  def join(env, enumerable, joiner \\\\ \"\") when is_binary(joiner) do\n    enumerable\n    |> Enum.map_intersperse(joiner, &Dune.Shims.Kernel.safe_to_string(env, &1))\n    |> IO.iodata_to_binary()\n  end\n\n  def map_join(env, enumerable, joiner \\\\ \"\", mapper)\n      when is_binary(joiner) and is_function(mapper, 1) do\n    enumerable\n    |> Enum.map_intersperse(joiner, &Dune.Shims.Kernel.safe_to_string(env, mapper.(&1)))\n    |> IO.iodata_to_binary()\n  end\nend\n"
  },
  {
    "path": "lib/dune/shims/io.ex",
    "content": "defmodule Dune.Shims.IO do\n  @moduledoc false\n\n  alias Dune.{Failure, Shims}\n\n  def puts(env, device \\\\ :stdio, item)\n\n  def puts(env, :stdio, item) do\n    env\n    |> Shims.Kernel.safe_to_string(item)\n    |> then(&IO.puts(:stdio, &1))\n  end\n\n  def puts(_env, _device, _item) do\n    error = Failure.restricted_function(IO, :puts, 2)\n    throw(error)\n  end\n\n  def inspect(env, item, opts \\\\ []) do\n    inspect(env, :stdio, item, opts)\n  end\n\n  def inspect(env, :stdio, item, opts) when is_list(opts) do\n    inspected = Shims.Kernel.safe_inspect(env, item, opts)\n\n    chardata =\n      if label_opt = opts[:label] do\n        [Shims.Kernel.safe_to_string(env, label_opt), \": \", inspected]\n      else\n        inspected\n      end\n\n    IO.puts(:stdio, chardata)\n\n    item\n  end\n\n  def inspect(_env, _device, _item, opts) when is_list(opts) do\n    error = Failure.restricted_function(IO, :inspect, 3)\n    throw(error)\n  end\nend\n"
  },
  {
    "path": "lib/dune/shims/json.ex",
    "content": "if Code.ensure_loaded?(JSON) do\n  defmodule Dune.Shims.JSON do\n    @moduledoc false\n\n    alias Dune.AtomMapping\n    alias Dune.Shims\n\n    def protocol_encode(env, value, encoder) when is_non_struct_map(value) do\n      case :maps.next(:maps.iterator(value)) do\n        :none ->\n          \"{}\"\n\n        {key, value, iterator} ->\n          [\n            ?{,\n            key(env, key, encoder),\n            ?:,\n            encoder.(value, encoder) | next(env, iterator, encoder)\n          ]\n      end\n    end\n\n    def protocol_encode(env, value, encoder)\n        when is_atom(value) and value not in [nil, true, false] do\n      encoder.(AtomMapping.to_string(env.atom_mapping, value), encoder)\n    end\n\n    def protocol_encode(_env, value, encoder) do\n      JSON.protocol_encode(value, encoder)\n    end\n\n    defp next(env, iterator, encoder) do\n      case :maps.next(iterator) do\n        :none ->\n          \"}\"\n\n        {key, value, iterator} ->\n          [\n            ?,,\n            key(env, key, encoder),\n            ?:,\n            encoder.(value, encoder) | next(env, iterator, encoder)\n          ]\n      end\n    end\n\n    defp key(_env, key, encoder) when is_binary(key), do: encoder.(key, encoder)\n    defp key(env, key, encoder), do: encoder.(Shims.Kernel.safe_to_string(env, key), encoder)\n\n    def encode!(env, term) do\n      encode!(env, term, &protocol_encode(env, &1, &2))\n    end\n\n    def encode!(_env, term, encoder) do\n      IO.iodata_to_binary(encoder.(term, encoder))\n    end\n\n    def encode_to_iodata!(env, term) do\n      encode_to_iodata!(env, term, &protocol_encode(env, &1, &2))\n    end\n\n    def encode_to_iodata!(_env, term, encoder) do\n      encoder.(term, encoder)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/dune/shims/kernel.ex",
    "content": "defmodule Dune.Shims.Kernel do\n  @moduledoc false\n\n  alias Dune.{AtomMapping, Failure, Shims}\n  alias Dune.Helpers.TermChecker\n\n  defmacro safe_sigil_w(_env, _, ~c\"a\") do\n    error = Failure.restricted_function(Kernel, :sigil_w, 2)\n    throw(error)\n  end\n\n  defmacro safe_sigil_w(_env, term, modifiers) do\n    quote do\n      sigil_w(unquote(term), unquote(modifiers))\n    end\n  end\n\n  defmacro safe_sigil_W(_env, _, ~c\"a\") do\n    error = Failure.restricted_function(Kernel, :sigil_W, 2)\n    throw(error)\n  end\n\n  defmacro safe_sigil_W(_env, term, modifiers) do\n    quote do\n      sigil_W(unquote(term), unquote(modifiers))\n    end\n  end\n\n  def safe_throw(_env, value) do\n    throw({:safe_throw, value})\n  end\n\n  defmacro safe_dbg(_env) do\n    error = Failure.restricted_function(Kernel, :dbg, 0)\n    throw(error)\n  end\n\n  defmacro safe_dbg(_env, _term) do\n    # should never be called because the sanitizer handles it\n    raise \"unexpected call safe_dbg/2\"\n  end\n\n  defmacro safe_dbg(_env, _term, _opts) do\n    error = Failure.restricted_function(Kernel, :dbg, 2)\n    throw(error)\n  end\n\n  def safe_dot(_env, %{} = map, key) do\n    # TODO test key error\n    Map.fetch!(map, key)\n  end\n\n  def safe_dot(env, module, fun) when is_atom(module) do\n    safe_apply(env, module, fun, [])\n  end\n\n  def safe_apply(_env, fun, args) when is_function(fun, 1) do\n    # TODO check if there is a risk / why it is here\n    apply(fun, args)\n  end\n\n  def safe_apply(env, module, fun, args) when is_atom(module) do\n    arity = length(args)\n\n    case env.allowlist.fun_status(module, fun, arity) do\n      :restricted ->\n        error = Failure.restricted_function(module, fun, arity)\n        throw(error)\n\n      {:shimmed, shim_module, shim_fun} ->\n        apply(shim_module, shim_fun, [env | args])\n\n      :allowed ->\n        apply(module, fun, args)\n\n      :undefined_module ->\n        Dune.Eval.Env.apply_fake(env, module, fun, args)\n\n      :undefined_function ->\n        throw({:undefined_function, module, fun, arity})\n\n      other when other in [:undefined_module, :undefined_function] ->\n        Dune.Eval.Env.apply_fake(env, module, fun, args)\n    end\n  end\n\n  def safe_inspect(env, term, opts \\\\ [])\n\n  def safe_inspect(_env, term, opts)\n      when is_number(term) or is_binary(term) or is_boolean(term) do\n    inspect(term, opts)\n  end\n\n  def safe_inspect(env, atom, _opts) when is_atom(atom) do\n    AtomMapping.inspect(env.atom_mapping, atom)\n  end\n\n  def safe_inspect(env, term, opts) do\n    TermChecker.check(term)\n    inspected = inspect(term, opts)\n    AtomMapping.replace_in_string(env.atom_mapping, inspected)\n  end\n\n  def safe_to_string(env, atom) when is_atom(atom) do\n    Shims.Atom.to_string(env, atom)\n  end\n\n  def safe_to_string(env, atom) when is_list(atom) do\n    Shims.List.to_string(env, atom)\n  end\n\n  def safe_to_string(_env, other), do: to_string(other)\n\n  def safe_to_charlist(env, atom) when is_atom(atom) do\n    Shims.Atom.to_charlist(env, atom)\n  end\n\n  def safe_to_charlist(_env, other), do: to_charlist(other)\nend\n"
  },
  {
    "path": "lib/dune/shims/list.ex",
    "content": "defmodule Dune.Shims.List do\n  @moduledoc false\n\n  alias Dune.Shims\n\n  def to_string(_env, list) when is_list(list) do\n    do_to_string(list)\n  end\n\n  defp do_to_string(list) when is_list(list) do\n    if Enum.any?(list, &is_list/1) do\n      # eagerly convert lists to binary to prevent OOM on\n      # structural sharing bombs\n      Enum.map(list, &do_to_string/1)\n    else\n      list\n    end\n    |> List.to_string()\n  end\n\n  defp do_to_string(elem), do: elem\n\n  # note: this is probably not safe so not actually used\n  def to_existing_atom(env, list) when is_list(list) do\n    string = to_string(list)\n    # make sure it was actually a flat charlist and not an IO-list\n    case to_charlist(string) do\n      ^list -> Shims.String.to_existing_atom(env, string)\n      _ -> List.to_existing_atom(list)\n    end\n  end\n\n  def to_existing_atom(_env, list) do\n    List.to_existing_atom(list)\n  end\nend\n"
  },
  {
    "path": "lib/dune/shims/string.ex",
    "content": "defmodule Dune.Shims.String do\n  @moduledoc false\n\n  alias Dune.AtomMapping\n\n  # note: this is probably not safe so not actually used\n\n  def to_existing_atom(env, string) when is_binary(string) do\n    AtomMapping.to_existing_atom(env, string)\n  end\n\n  def to_existing_atom(_env, string) do\n    String.to_existing_atom(string)\n  end\nend\n"
  },
  {
    "path": "lib/dune/success.ex",
    "content": "defmodule Dune.Success do\n  @moduledoc \"\"\"\n  A struct returned when `Dune` evaluation succeeds.\n\n  Fields:\n  - `value` (term): the value which was actually returned at runtime.\n    Should not be displayed to the user, might be different from what the user expects.\n  - `inspected` (string): safely inspected `value` to be displayed to the user\n  - `stdio` (string): captured standard output\n\n  `value` contains the actual value used at runtime, so atoms will be different from the ones\n  displayed to the user (see `Dune.eval_string/2`).\n  \"\"\"\n\n  @type t :: %__MODULE__{\n          value: term,\n          inspected: String.t(),\n          stdio: binary\n        }\n  @enforce_keys [:value, :inspected, :stdio]\n  defstruct @enforce_keys\nend\n"
  },
  {
    "path": "lib/dune.ex",
    "content": "defmodule Dune do\n  @moduledoc \"\"\"\n  A sandbox for Elixir to safely evaluate untrusted code from user input.\n\n  ## Features\n\n  - only authorized modules and functions can be executed (see\n  `Dune.Allowlist.Default`)\n  - no access to environment variables, file system, network...\n  - code executed in an isolated process\n  - execution within configurable limits: timeout, maximum reductions and memory\n  (inspired by [Luerl](https://github.com/rvirding/luerl))\n  - captured standard output\n  - atoms, without atom leaks: parsing and runtime do not\n  [leak atoms](https://hexdocs.pm/elixir/String.html#to_atom/1) (i.e. does not\n  keep\n  [filling the atom table](https://learnyousomeerlang.com/starting-out-for-real#atoms)\n  until the VM crashes)\n  - modules, without actual module creation: Dune does not let users define any\n  actual module (would leak memory and modify the state of the VM globally), but\n  `defmodule` simulates the basic behavior of a module, including private and\n  recursive functions\n\n  The list of modules and functions authorized by default is defined by the\n  `Dune.Allowlist.Default` module, but this list can be extended and customized\n  (at your own risk!) using `Dune.Allowlist`.\n\n  If you need to keep the state between evaluations, you might consider\n  `Dune.Session`.\n\n  \"\"\"\n\n  alias Dune.{Success, Failure, Parser, Eval, Opts}\n\n  @doc ~S\"\"\"\n  Evaluates the `string` in the sandbox.\n\n  Available options are detailed in `Dune.Opts`.\n\n  Returns a `Dune.Success` struct if the execution went successfully,\n  a `Dune.Failure` else.\n\n  ## Examples\n\n      iex> Dune.eval_string(\"IO.puts(\\\"Hello world!\\\")\")\n      %Dune.Success{inspected: \":ok\", stdio: \"Hello world!\\n\", value: :ok}\n\n      iex> Dune.eval_string(\"File.cwd!()\")\n      %Dune.Failure{message: \"** (DuneRestrictedError) function File.cwd!/0 is restricted\", type: :restricted}\n\n      iex> Dune.eval_string(\"List.duplicate(:spam, 100_000)\")\n      %Dune.Failure{message: \"Execution stopped - memory limit exceeded\", stdio: \"\", type: :memory}\n\n      iex> Dune.eval_string(\"Foo.bar()\")\n      %Dune.Failure{message: \"** (UndefinedFunctionError) function Foo.bar/0 is undefined (module Foo is not available)\", type: :exception}\n\n      iex> Dune.eval_string(\"][\")\n      %Dune.Failure{message: \"unexpected token: ]\", type: :parsing}\n\n  Atoms used during parsing and execution might be transformed to prevent atom leaks:\n\n      iex> Dune.eval_string(\"some_variable = IO.inspect(:some_atom)\")\n      %Dune.Success{inspected: \":some_atom\", stdio: \":some_atom\\n\", value: :a__Dune_atom_2__}\n\n  The `value` field shows the actual runtime value, but `inspected` and `stdio` are safe to display to the user.\n\n  \"\"\"\n  @spec eval_string(String.t(), Keyword.t()) :: Success.t() | Failure.t()\n  def eval_string(string, opts \\\\ []) when is_binary(string) do\n    opts = Opts.validate!(opts)\n\n    string\n    |> Parser.parse_string(opts)\n    |> Eval.run(opts)\n  end\n\n  @doc ~S\"\"\"\n  Evaluates the quoted `ast` in the sandbox.\n\n  Available options are detailed in `Dune.Opts` (parsing restrictions have no effect)..\n\n  Returns a `Dune.Success` struct if the execution went successfully,\n  a `Dune.Failure` else.\n\n  ## Examples\n\n      iex> Dune.eval_quoted(quote do: [1, 2] ++ [3, 4])\n      %Dune.Success{inspected: \"[1, 2, 3, 4]\", stdio: \"\", value: [1, 2, 3, 4]}\n\n      iex> Dune.eval_quoted(quote do: System.get_env())\n      %Dune.Failure{message: \"** (DuneRestrictedError) function System.get_env/0 is restricted\", type: :restricted}\n\n      iex> Dune.eval_quoted(quote do: Process.sleep(500))\n      %Dune.Failure{message: \"Execution timeout - 50ms\", type: :timeout}\n\n  \"\"\"\n  @spec eval_quoted(Macro.t(), Keyword.t()) :: Success.t() | Failure.t()\n  def eval_quoted(ast, opts \\\\ []) do\n    opts = Opts.validate!(opts)\n\n    ast\n    |> Parser.parse_quoted(opts)\n    |> Eval.run(opts)\n  end\n\n  @doc ~S\"\"\"\n  Returns the AST corresponding to the provided `string`, without leaking atoms.\n\n  Available options are detailed in `Dune.Opts` (runtime restrictions have no effect).\n\n  Returns a `Dune.Success` struct if the execution went successfully,\n  a `Dune.Failure` else.\n\n  ## Examples\n\n      iex> Dune.string_to_quoted(\"1 + 2\")\n      %Dune.Success{inspected: \"{:+, [line: 1], [1, 2]}\", stdio: \"\", value: {:+, [line: 1], [1, 2]}}\n\n      iex> Dune.string_to_quoted(\"[invalid\")\n      %Dune.Failure{stdio: \"\", message: \"missing terminator: ]\", type: :parsing}\n\n  The `pretty` option can make the AST more readable by adding newlines to `inspected`:\n\n      iex> Dune.string_to_quoted(\"IO.puts(:hello)\", pretty: true).inspected\n      \"{{:., [line: 1], [{:__aliases__, [line: 1], [:IO]}, :puts]}, [line: 1],\\n [:hello]}\"\n\n      iex> Dune.string_to_quoted(\"IO.puts(:hello)\").inspected\n      \"{{:., [line: 1], [{:__aliases__, [line: 1], [:IO]}, :puts]}, [line: 1], [:hello]}\"\n\n  Since the code isn't executed, there is no allowlist restriction:\n\n      iex> Dune.string_to_quoted(\"System.halt()\")\n      %Dune.Success{\n        inspected: \"{{:., [line: 1], [{:__aliases__, [line: 1], [:System]}, :halt]}, [line: 1], []}\",\n        stdio: \"\",\n        value: {{:., [line: 1], [{:__aliases__, [line: 1], [:System]}, :halt]}, [line: 1], []}\n      }\n\n  Atoms might be transformed during parsing to prevent atom leaks:\n\n      iex> Dune.string_to_quoted(\"some_variable = :some_atom\")\n      %Dune.Success{\n        inspected: \"{:=, [line: 1], [{:some_variable, [line: 1], nil}, :some_atom]}\",\n        stdio: \"\",\n        value: {:=, [line: 1], [{:a__Dune_atom_1__, [line: 1], nil}, :a__Dune_atom_2__]}\n      }\n\n  The `value` field shows the actual runtime value, but `inspected` is safe to display to the user.\n\n  \"\"\"\n  @spec string_to_quoted(String.t(), Keyword.t()) :: Success.t() | Failure.t()\n  def string_to_quoted(string, opts \\\\ []) when is_binary(string) do\n    opts = Opts.validate!(opts)\n    Parser.string_to_quoted(string, opts)\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule Dune.MixProject do\n  use Mix.Project\n\n  @version \"0.3.15\"\n  @github_url \"https://github.com/functional-rewire/dune\"\n\n  def project do\n    [\n      app: :dune,\n      version: @version,\n      elixir: \">= 1.14.0 and < 1.20.0\",\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      dialyzer: [flags: [:missing_return, :extra_return]],\n\n      # Hex\n      description: \"A sandbox for Elixir to safely evaluate untrusted code from user input\",\n      package: package(),\n      aliases: aliases(),\n      docs: docs()\n    ]\n  end\n\n  # Run \"mix help compile.app\" to learn about applications.\n  def application do\n    [\n      extra_applications: [:logger]\n    ]\n  end\n\n  # Run \"mix help deps\" to learn about dependencies.\n  defp deps do\n    [\n      # CI\n      {:dialyxir, \"~> 1.0\", only: :test, runtime: false},\n      # DOCS\n      {:ex_doc, \"~> 0.24\", only: :docs, runtime: false}\n    ]\n  end\n\n  defp package do\n    [\n      maintainers: [\"functional-rewire\", \"sabiwara\"],\n      licenses: [\"MIT\"],\n      links: %{\"GitHub\" => @github_url},\n      files: ~w(lib mix.exs .formatter.exs README.md LICENSE.md CHANGELOG.md)\n    ]\n  end\n\n  defp aliases do\n    [docs: [\"compile --force\", \"docs\"]]\n  end\n\n  def cli do\n    [preferred_envs: [docs: :docs, \"hex.publish\": :docs, dialyzer: :test]]\n  end\n\n  defp docs do\n    [\n      main: \"Dune\",\n      source_ref: \"v#{@version}\",\n      source_url: @github_url,\n      homepage_url: @github_url,\n      extras: [\"README.md\", \"CHANGELOG.md\", \"LICENSE.md\"]\n    ]\n  end\nend\n"
  },
  {
    "path": "test/dune/allowlist/default_test.exs",
    "content": "defmodule Dune.Allowlist.DefaultTest do\n  use ExUnit.Case, async: true\n  doctest Dune.Allowlist.Default\n  alias Dune.Allowlist.Default\n\n  describe \"fun_status/3\" do\n    test \"should not allow module_info/N\" do\n      assert :restricted = Default.fun_status(Float, :module_info, 0)\n      assert :restricted = Default.fun_status(Float, :module_info, 1)\n      assert :restricted = Default.fun_status(:math, :module_info, 0)\n      assert :restricted = Default.fun_status(:math, :module_info, 1)\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune/allowlist_test.exs",
    "content": "defmodule Dune.AllowlistTest do\n  use ExUnit.Case, async: true\n\n  doctest Dune.Allowlist\n\n  describe \"use/2\" do\n    test \"creates a new sandbox \" do\n      defmodule CustomAllowlist do\n        use Dune.Allowlist\n\n        allow Kernel, only: [:+, :*, :-, :/, :div, :rem]\n        allow Integer, only: [:pow]\n      end\n\n      assert :allowed = CustomAllowlist.fun_status(Kernel, :+, 2)\n      assert :restricted = CustomAllowlist.fun_status(Kernel, :<>, 2)\n      assert :undefined_function = CustomAllowlist.fun_status(Kernel, :foo, 1)\n\n      assert :allowed = CustomAllowlist.fun_status(Integer, :pow, 2)\n      assert :restricted = CustomAllowlist.fun_status(Integer, :to_string, 1)\n      assert :undefined_function = CustomAllowlist.fun_status(Integer, :foo, 1)\n\n      assert :restricted = CustomAllowlist.fun_status(String, :upcase, 1)\n\n      assert :undefined_module = CustomAllowlist.fun_status(Foo, :foo, 1)\n    end\n\n    test \"extends an existing sandbox \" do\n      defmodule CustomModule do\n        def authorized(i), do: i + 1\n        def forbidden(i), do: i - 1\n      end\n\n      defmodule ExtendedAllowlist do\n        use Dune.Allowlist, extend: Dune.Allowlist.Default\n\n        allow CustomModule, only: [:authorized]\n      end\n\n      assert :allowed = ExtendedAllowlist.fun_status(String, :upcase, 1)\n      assert :restricted = ExtendedAllowlist.fun_status(String, :to_atom, 1)\n\n      assert :allowed = ExtendedAllowlist.fun_status(CustomModule, :authorized, 1)\n      assert :restricted = ExtendedAllowlist.fun_status(CustomModule, :forbidden, 1)\n\n      assert :undefined_module = ExtendedAllowlist.fun_status(Foo, :foo, 1)\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune/atom_mapping_test.exs",
    "content": "defmodule Dune.AtomMappingTest do\n  use ExUnit.Case, async: true\n  doctest Dune.AtomMapping\nend\n"
  },
  {
    "path": "test/dune/opts_test.exs",
    "content": "defmodule Dune.OptsTest do\n  use ExUnit.Case, async: true\n  doctest Dune.Opts\nend\n"
  },
  {
    "path": "test/dune/parser/atom_encoder_test.exs",
    "content": "defmodule Dune.Parser.AtomEncoderTest do\n  use ExUnit.Case, async: true\n  import Dune.Parser.AtomEncoder\n\n  describe \"categorize_atom_binary/1\" do\n    test \"categorizes aliases cases\" do\n      assert :alias = categorize_atom_binary(\"Elixir\")\n      assert :alias = categorize_atom_binary(\"String\")\n      assert :alias = categorize_atom_binary(\"Foo.Bar\")\n    end\n\n    test \"categorizes valid public variable names\" do\n      assert :public_var = categorize_atom_binary(\"abc\")\n      assert :public_var = categorize_atom_binary(\"erlang\")\n      assert :public_var = categorize_atom_binary(\"あ\")\n    end\n\n    test \"categorizes valid private variable names\" do\n      assert :private_var = categorize_atom_binary(\"_\")\n      assert :private_var = categorize_atom_binary(\"_abc\")\n      assert :private_var = categorize_atom_binary(\"_あ\")\n    end\n\n    test \"categorizes other cases\" do\n      assert :other = categorize_atom_binary(\"\")\n      assert :other = categorize_atom_binary(\" \")\n      assert :other = categorize_atom_binary(\"a b\")\n      assert :other = categorize_atom_binary(\"A B\")\n      assert :other = categorize_atom_binary(\"Elixir. A\")\n      assert :other = categorize_atom_binary(\"Foo.Bar \")\n      assert :other = categorize_atom_binary(\" Foo.Bar\")\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune/parser/string_parser_test.exs",
    "content": "defmodule Dune.Parser.StringParserTest do\n  use ExUnit.Case, async: true\n\n  alias Dune.{AtomMapping, Opts}\n  alias Dune.Parser.{StringParser, UnsafeAst}\n\n  describe \"parse_string/2\" do\n    test \"existing atoms\" do\n      assert %UnsafeAst{ast: nil, atom_mapping: AtomMapping.new()} ==\n               StringParser.parse_string(\"nil\", %Opts{}, nil)\n\n      assert %UnsafeAst{ast: true, atom_mapping: AtomMapping.new()} ==\n               StringParser.parse_string(\"true\", %Opts{}, nil)\n\n      assert %UnsafeAst{ast: :atom, atom_mapping: AtomMapping.new()} ==\n               StringParser.parse_string(\":atom\", %Opts{}, nil)\n\n      assert %UnsafeAst{ast: :Atom, atom_mapping: AtomMapping.new()} ==\n               StringParser.parse_string(\":Atom\", %Opts{}, nil)\n    end\n\n    test \"existing modules\" do\n      assert %UnsafeAst{\n               ast: {:__aliases__, [line: 1], [:Module]},\n               atom_mapping: AtomMapping.new()\n             } == StringParser.parse_string(\"Module\", %Opts{}, nil)\n\n      assert %UnsafeAst{\n               ast: {:__aliases__, [line: 1], [:Date, :Range]},\n               atom_mapping: AtomMapping.new()\n             } == StringParser.parse_string(\"Date.Range\", %Opts{}, nil)\n    end\n\n    test \"non-existing atoms\" do\n      assert %UnsafeAst{\n               ast: :a__Dune_atom_1__,\n               atom_mapping: %AtomMapping{\n                 atoms: %{a__Dune_atom_1__: \"my_atom\"},\n                 modules: %{},\n                 extra_info: %{}\n               }\n             } == StringParser.parse_string(\":my_atom\", %Opts{}, nil)\n\n      assert %UnsafeAst{\n               ast: :__Dune_atom_1__,\n               atom_mapping: %AtomMapping{\n                 atoms: %{__Dune_atom_1__: \"_my_atom\"},\n                 modules: %{},\n                 extra_info: %{}\n               }\n             } == StringParser.parse_string(\":_my_atom\", %Opts{}, nil)\n\n      assert %UnsafeAst{\n               ast: :Dune_Atom_1__,\n               atom_mapping: %AtomMapping{\n                 atoms: %{Dune_Atom_1__: \"MyAtom\"},\n                 modules: %{},\n                 extra_info: %{}\n               }\n             } == StringParser.parse_string(\":MyAtom\", %Opts{}, nil)\n    end\n\n    test \"non-existing modules\" do\n      assert %UnsafeAst{\n               ast: {:__aliases__, [line: 1], [:Dune_Module_1__]},\n               atom_mapping: %AtomMapping{\n                 atoms: %{Dune_Atom_1__: \"MyModule\"},\n                 modules: %{Dune_Module_1__ => \"MyModule\"},\n                 extra_info: %{}\n               }\n             } == StringParser.parse_string(\"MyModule\", %Opts{}, nil)\n\n      assert %UnsafeAst{\n               ast: {:__aliases__, [line: 1], [:Dune_Module_1__]},\n               atom_mapping: %AtomMapping{\n                 atoms: %{Dune_Atom_1__: \"My\", Dune_Atom_2__: \"AwesomeModule\"},\n                 modules: %{Dune_Module_1__ => \"My.AwesomeModule\"},\n                 extra_info: %{}\n               }\n             } == StringParser.parse_string(\"My.AwesomeModule\", %Opts{}, nil)\n\n      assert %UnsafeAst{\n               ast: {:__aliases__, [line: 1], [:Dune_Module_1__]},\n               atom_mapping: %AtomMapping{\n                 atoms: %{Dune_Atom_1__: \"My\"},\n                 modules: %{Dune_Module_1__ => \"My.Module\"},\n                 extra_info: %{}\n               }\n             } == StringParser.parse_string(\"My.Module\", %Opts{}, nil)\n    end\n\n    test ~S[non-existing \"wrapped\" atoms (with whitespace)] do\n      assert %UnsafeAst{\n               ast: :__Dune_atom_1__,\n               atom_mapping: %AtomMapping{\n                 atoms: %{__Dune_atom_1__: \" \"},\n                 modules: %{},\n                 extra_info: %{__Dune_atom_1__: :wrapped}\n               }\n             } == StringParser.parse_string(~S(:\" \"), %Opts{}, nil)\n\n      assert %UnsafeAst{\n               ast: :__Dune_atom_1__,\n               atom_mapping: %AtomMapping{\n                 atoms: %{__Dune_atom_1__: \"my atom\"},\n                 modules: %{},\n                 extra_info: %{__Dune_atom_1__: :wrapped}\n               }\n             } == StringParser.parse_string(~S(:\"my atom\"), %Opts{}, nil)\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune/session_test.exs",
    "content": "defmodule Dune.SessionTest do\n  use ExUnit.Case\n\n  doctest Dune.Session, tags: [lts_only: true]\n\n  alias Dune.{Session, Success, Failure}\n\n  @module_code \"\"\"\n  defmodule MySum do\n    def sum(xs), do: sum(xs, 0)\n    defp sum([], acc), do: acc\n    defp sum([x | xs], acc) do\n      sum(xs, acc + x)\n    end\n  end\n  \"\"\"\n\n  describe \"eval_string/3\" do\n    test \"keeps variable bindings\" do\n      session =\n        Session.new()\n        |> Session.eval_string(\"abcd = 5\")\n        |> Session.eval_string(\"abcd\")\n\n      assert %Session{\n               last_result: %Success{inspected: \"5\"},\n               bindings: [a__Dune_atom_1__: 5]\n             } = session\n    end\n\n    test \"ignores failed steps\" do\n      session =\n        Session.new()\n        |> Session.eval_string(\"abcd = 5\")\n        |> Session.eval_string(\"abcd = 1 / 0\")\n        |> Session.eval_string(\"abcd\")\n\n      assert %Session{\n               last_result: %Success{inspected: \"5\"},\n               bindings: [a__Dune_atom_1__: 5]\n             } = session\n    end\n\n    test \"keeps variable bindings on errors\" do\n      session =\n        Session.new()\n        |> Session.eval_string(\"abcd = 5\")\n        |> Session.eval_string(\"abcd / 0\")\n\n      assert %Session{\n               last_result: %Failure{\n                 message: \"** (ArithmeticError) bad argument in arithmetic expression\" <> _\n               },\n               bindings: [a__Dune_atom_1__: 5]\n             } = session\n    end\n\n    test \"keeps the atom mapping\" do\n      session =\n        Session.new()\n        |> Session.eval_string(\"abcd = :abcd\")\n        |> Session.eval_string(\"efgh = :efgh\")\n        |> Session.eval_string(\"[abcd, efgh]\")\n\n      assert %Session{\n               last_result: %Success{inspected: \"[:abcd, :efgh]\"},\n               bindings: [\n                 a__Dune_atom_2__: :a__Dune_atom_2__,\n                 a__Dune_atom_1__: :a__Dune_atom_1__\n               ]\n             } = session\n    end\n\n    test \"keeps the module mapping\" do\n      session =\n        Session.new()\n        |> Session.eval_string(\"acbd = [Aa]\")\n        |> Session.eval_string(\"acbd = acbd ++ [Bb]\")\n        |> Session.eval_string(\"acbd = acbd ++ [Aa.Bb]\")\n        |> Session.eval_string(\"acbd ++ [Cc.Dd]\")\n\n      assert %Session{\n               last_result: %Success{inspected: \"[Aa, Bb, Aa.Bb, Cc.Dd]\"},\n               bindings: [a__Dune_atom_1__: [Dune_Module_1__, Dune_Module_2__, Dune_Module_3__]]\n             } = session\n    end\n\n    test \"handles modules\" do\n      session =\n        Session.new()\n        |> Session.eval_string(@module_code)\n        |> Session.eval_string(\"MySum.sum([1, 2, 100])\")\n\n      assert %Session{\n               last_result: %Success{inspected: \"103\"},\n               bindings: []\n             } = session\n    end\n\n    test \"does not break due to Elixir single atom bug\" do\n      session = Session.new() |> Session.eval_string(\":foo\")\n\n      assert %Session{\n               last_result: %Success{inspected: \":foo\"},\n               bindings: []\n             } = session\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune/shims_test.exs",
    "content": "defmodule Dune.ShimsTest do\n  use ExUnit.Case, async: true\n\n  alias Dune.Success\n  alias Dune.Failure\n\n  defmacrop sigil_E(call, _expr) do\n    quote do\n      Dune.eval_string(unquote(call), timeout: 100, inspect_sort_maps: true)\n    end\n  end\n\n  describe \"JSON\" do\n    @describetag :lts_only\n    test \"encode atoms\" do\n      assert %Success{value: ~S(\"json101\"), inspected: ~S(\"\\\"json101\\\"\")} =\n               ~E'JSON.encode!(:json101)'\n\n      assert %Success{value: ~S(\"json102\"), inspected: ~S(\"\\\"json102\\\"\")} =\n               ~E'JSON.encode_to_iodata!(:json102) |> IO.iodata_to_binary()'\n\n      assert %Success{\n               value: ~S({\"json201\":[\"json202\",123,\"foo\",null,true]}),\n               inspected: ~S(\"{\\\"json201\\\":[\\\"json202\\\",123,\\\"foo\\\",null,true]}\")\n             } = ~E'JSON.encode!(%{json201: [:json202, 123, \"foo\", nil, true]})'\n    end\n\n    test \"decode atoms\" do\n      assert %Success{value: \"json301\", inspected: ~S(\"json301\")} =\n               ~E'JSON.decode!(\"\\\"json301\\\"\")'\n    end\n  end\n\n  describe \"iodata / chardata\" do\n    test \"List.to_string\" do\n      assert %Success{value: <<1, 2, 3>>} = ~E'List.to_string([1, 2, 3])'\n      assert %Success{value: <<1, 2, 3>>} = ~E'List.to_string([1, [[2], 3]])'\n      assert %Success{value: \"abc\"} = ~E'List.to_string([\"a\", [[\"b\"], \"c\"]])'\n\n      assert %Failure{message: \"** (ArgumentError) cannot convert the given list\" <> _} =\n               ~E'List.to_string([1, :foo])'\n    end\n\n    test \"IO.chardata_to_string\" do\n      assert %Success{value: <<1, 2, 3>>} = ~E'IO.chardata_to_string([1, 2, 3])'\n      assert %Success{value: <<1, 2, 3>>} = ~E'IO.chardata_to_string([1, [[2], 3]])'\n      assert %Success{value: \"abc\"} = ~E'IO.chardata_to_string([\"a\", [[\"b\"], \"c\"]])'\n\n      assert %Failure{message: \"** (ArgumentError) cannot convert the given list\" <> _} =\n               ~E'IO.chardata_to_string([1, :foo])'\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune/validation_test.exs",
    "content": "defmodule Dune.ValidationTest do\nend\n"
  },
  {
    "path": "test/dune_modules_test.exs",
    "content": "defmodule DuneModulesTest do\n  use ExUnit.Case, async: true\n\n  alias Dune.{Success, Failure}\n\n  defmacro sigil_E(call, _expr) do\n    # TODO fix memory needs\n    quote do\n      Dune.eval_string(unquote(call), max_reductions: 25_000, max_heap_size: 50_000, timeout: 100)\n    end\n  end\n\n  describe \"Dune authorized\" do\n    test \"basic module\" do\n      result = ~E'''\n      defmodule Hello do\n        def greet(value) do\n          IO.puts \"Hello #{value}\"\n        end\n      end\n\n      Hello.greet(:world!)\n      '''\n\n      assert %Success{value: :ok, stdio: \"Hello world!\\n\"} = result\n    end\n\n    test \"plain atom module\" do\n      result = ~E'''\n      defmodule :hello do\n        def greet(value) do\n          IO.puts \"Hello #{value}\"\n        end\n      end\n\n      :hello.greet(:world!)\n      '''\n\n      assert %Success{value: :ok, stdio: \"Hello world!\\n\"} = result\n    end\n\n    test \"module without other code\" do\n      result = ~E'''\n      defmodule Hello do\n      end\n      '''\n\n      assert %Success{\n               value: {:module, Dune_Module_1__, nil, nil},\n               inspected: \"{:module, Hello, nil, nil}\",\n               stdio: \"\"\n             } = result\n    end\n\n    test \"default argument\" do\n      result = ~E'''\n      defmodule My.Default do\n        def incr(x \\\\ 0), do: x + 1\n      end\n\n      [My.Default.incr(), My.Default.incr(100)]\n      '''\n\n      assert %Success{value: [1, 101]} = result\n    end\n\n    test \"default arguments\" do\n      result = ~E'''\n      defmodule My.Defaults do\n        def defaults(a \\\\ 1, b \\\\ 2, c) do\n          [a, b, c]\n        end\n      end\n\n      {My.Defaults.defaults(:c), My.Defaults.defaults(:a, :c)}\n      '''\n\n      assert %Success{value: {[1, 2, :c], [:a, 2, :c]}} = result\n    end\n\n    test \"recursive functions with guards\" do\n      result = ~E'''\n      defmodule My.List do\n        def my_sum([]), do: 0\n        def my_sum([h | t]) when is_number(h), do: h + my_sum(t)\n      end\n\n      My.List.my_sum([1, 100, 1000])\n      '''\n\n      assert %Success{value: 1101} = result\n    end\n\n    test \"recursive functions in a nested block\" do\n      result = ~E'''\n      defmodule My.List do\n        def my_sum([]), do: 0\n        def my_sum([h | t]) do\n          if is_number(h) do\n            h + my_sum(t)\n          else\n            :NaN\n          end\n        end\n      end\n\n      My.List.my_sum([1, 100, 1000])\n      '''\n\n      assert %Success{value: 1101} = result\n    end\n\n    test \"public and private functions\" do\n      assert %Success{value: \"success!\"} = ~E'''\n             defmodule My.Module do\n               def public, do: private()\n               defp private, do: \"success!\"\n             end\n\n             My.Module.public\n             '''\n    end\n\n    test \"recursive private function with guards\" do\n      result = ~E'''\n      defmodule My.List do\n        def my_sum(list) when is_list(list), do: my_sum(list, 0)\n        defp my_sum([], acc), do: acc\n        defp my_sum([h | t], acc) when is_number(h), do: my_sum(t, h + acc)\n      end\n\n      My.List.my_sum([1, 100, 1000])\n      '''\n\n      assert %Success{value: 1101} = result\n    end\n\n    test \"captured fake module functions (external)\" do\n      assert %Success{value: [\"Joe (20)\", \"Jane (27)\"]} = ~E'''\n             defmodule My.Captures do\n               def format(%{name: name, age: age}) do\n                \"#{name} (#{age})\"\n               end\n             end\n\n             Enum.map(\n               [%{name: \"Joe\", age: 20}, %{name: \"Jane\", age: 27}],\n               &My.Captures.format/1\n             )\n             '''\n\n      assert %Success{inspected: \":foo\"} = ~E'''\n             defmodule My.Captures do\n               def const, do: :foo\n             end\n\n             f = &My.Captures.const/0\n             f.()\n             '''\n    end\n\n    test \"captured fake module functions (internal)\" do\n      assert %Success{value: [\"Joe (20)\", \"Jane (27)\"]} = ~E'''\n             defmodule My.Captures do\n               def format_many(list) do\n                 Enum.map(list, &format_one/1)\n               end\n\n               def format_one(%{name: name, age: age}) do\n                 \"#{name} (#{age})\"\n               end\n             end\n\n             My.Captures.format_many([%{name: \"Joe\", age: 20}, %{name: \"Jane\", age: 27}])\n             '''\n    end\n\n    test \"apply fake module functions\" do\n      assert %Success{value: \"Joe (20)\"} = ~E'''\n             defmodule My.Formatter do\n               def format(%{name: name, age: age}) do\n                \"#{name} (#{age})\"\n               end\n             end\n\n             apply(\n               My.Formatter,\n               :format,\n               [%{name: \"Joe\", age: 20}]\n             )\n             '''\n    end\n\n    test \"accept docs and typespecs\" do\n      assert %Success{value: \"Joe (20)\"} = ~E'''\n             defmodule My.Formatter do\n               @moduledoc \"Format all the things!\"\n\n               @typep name :: String.t()\n               @type user :: %{name: name, age: integer}\n\n               @doc \"Formats a user\"\n               @spec format(user) :: String.t()\n               def format(%{name: name, age: age}) do\n                \"#{name} (#{age})\"\n               end\n             end\n\n             My.Formatter.format(%{name: \"Joe\", age: 20})\n             '''\n    end\n\n    test \"0-arity call without parenthesis\" do\n      assert %Success{value: \"success!\"} = ~E'''\n             defmodule My.Module do\n               def public, do: private()\n               defp private, do: \"success!\"\n             end\n\n             My.Module.public\n             '''\n    end\n\n    # TODO: apply private function\n  end\n\n  describe \"exceptions\" do\n    test \"function clause error\" do\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (Dune.Eval.FunctionClauseError) no function clause matching in My.Checker.check_age/1: My.Checker.check_age(:invalid)\"\n             } = ~E'''\n             defmodule My.Checker do\n               def check_age(age) when is_integer(age) and age >= 18, do: :ok\n             end\n\n             My.Checker.check_age(:invalid)\n             '''\n    end\n\n    test \"calling private function\" do\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (UndefinedFunctionError) function My.Module.private/0 is undefined or private\"\n             } = ~E'''\n             defmodule My.Module do\n               def public, do: :public\n               defp private, do: :private\n             end\n\n             My.Module.private\n             '''\n    end\n\n    test \"conflicting public and private functions\" do\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (Dune.Eval.CompileError) nofile:3: defp conflicting/0 already defined as def in nofile:2\"\n             } = ~E'''\n             defmodule My.Module do\n               def conflicting, do: \"public\"\n               defp conflicting, do: \"private\"\n             end\n             '''\n    end\n  end\n\n  describe \"Dune restricted\" do\n    test \"unsafe function body\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function System.get_env/0 is restricted\"\n             } = ~E'''\n             defmodule Danger do\n               def danger() do\n                 System.get_env()\n               end\n             end\n\n             Danger.danger()\n             '''\n    end\n\n    test \"unsafe function default arg\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function System.get_env/0 is restricted\"\n             } = ~E'''\n             defmodule Danger do\n               def danger(env \\\\ System.get_env()) do\n                 env\n               end\n             end\n\n             Danger.danger()\n             '''\n    end\n\n    test \"restrictions in the module top level\" do\n      assert %Failure{\n               type: :module_restricted,\n               message:\n                 \"** (DuneRestrictedError) the following syntax is restricted inside defmodule:\\n         def no_block\"\n             } = ~E'''\n             defmodule My.Module do\n               def no_block\n             end\n             '''\n\n      assert %Failure{\n               type: :module_restricted,\n               message:\n                 \"** (DuneRestrictedError) the following syntax is restricted inside defmodule:\\n         @foo 1 + 1\"\n             } = ~E'''\n             defmodule My.Module do\n               @foo 1 + 1\n             end\n             '''\n    end\n\n    test \"trying to redefine existing module\" do\n      assert %Failure{\n               type: :module_conflict,\n               message:\n                 \"** (DuneRestrictedError) Following module cannot be defined/redefined: System\"\n             } = ~E'''\n             defmodule System do\n               def foo, do: :bar\n             end\n             '''\n\n      assert %Failure{\n               type: :module_conflict,\n               message:\n                 \"** (DuneRestrictedError) Following module cannot be defined/redefined: String\"\n             } = ~E'''\n             defmodule String do\n               def foo, do: :bar\n             end\n             '''\n\n      assert %Failure{\n               type: :module_conflict,\n               message:\n                 \"** (DuneRestrictedError) Following module cannot be defined/redefined: ExUnit\"\n             } = ~E'''\n             defmodule ExUnit do\n               def foo, do: :bar\n             end\n             '''\n\n      assert %Failure{\n               type: :module_conflict,\n               message:\n                 \"** (DuneRestrictedError) Following module cannot be defined/redefined: Foo\"\n             } = ~E'''\n             defmodule Foo do\n               def foo, do: :bar\n             end\n             defmodule Foo do\n               def foo, do: :bar\n             end\n             '''\n    end\n\n    test \"defmodule used with different arities\" do\n      assert %Failure{\n               type: :parsing,\n               message: \"dune parsing error: failed to safe parse\\n         defmodule\"\n             } = ~E'defmodule'\n\n      assert %Failure{\n               type: :parsing,\n               message: \"dune parsing error: failed to safe parse\\n         defmodule do\\n\" <> _\n             } = ~E'''\n             defmodule do\n               def foo, do: :bar\n             end\n             '''\n    end\n\n    test \"defmodule with invalid module names\" do\n      assert %Failure{\n               type: :exception,\n               message: \"** (Dune.Eval.CompileError) nofile:1: invalid module name: true\"\n             } = ~E'''\n             defmodule true do\n             end\n             '''\n\n      assert %Failure{\n               type: :exception,\n               message: \"** (Dune.Eval.CompileError) nofile:1: invalid module name: nil\"\n             } = ~E'''\n             defmodule nil do\n             end\n             '''\n\n      assert %Failure{\n               type: :exception,\n               message: \"** (Dune.Eval.CompileError) nofile:1: invalid module name: 1\"\n             } = ~E'''\n             defmodule 1 do\n             end\n             '''\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune_oom_safety_test.exs",
    "content": "defmodule Dune.AssertionHelper do\n  alias Dune.Failure\n\n  defmacro test_execution_stops(test_name, do: expr) do\n    quote do\n      test unquote(test_name) do\n        # not sure if reductions or memory limit this first\n        assert %Failure{message: \"Execution stopped - \" <> _} =\n                 Dune.eval_quoted(unquote(Macro.escape(expr)), timeout: 100)\n      end\n    end\n  end\nend\n\ndefmodule Dune.OOMSafetyTest do\n  # Safety integration tests for \"structural-sharing bombs\" edge cases\n  # that would cause BIFs to hang and use enormous amounts of memory\n\n  use ExUnit.Case, async: true\n\n  import Dune.AssertionHelper\n\n  test_execution_stops \"List.duplicate\" do\n    List.duplicate(:foo, 200_000)\n  end\n\n  # TODO figure out why this fails since Elixir 1.18\n  @tag :skip\n  test_execution_stops \"String.duplicate\" do\n    String.duplicate(\"foo\", 200_000)\n  end\n\n  describe \"structural sharing bombs\" do\n    test_execution_stops \"returning value directly\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end)\n    end\n\n    test_execution_stops \"inspect\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end)\n      |> inspect(limit: :infinity)\n    end\n\n    test_execution_stops \"string interpolation\" do\n      bomb = Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end)\n      \"#{bomb}!\"\n    end\n\n    test_execution_stops \"to_string\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end) |> to_string()\n    end\n\n    test_execution_stops \"List.to_string\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end) |> List.to_string()\n    end\n\n    test_execution_stops \"IO.iodata_to_binary\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end) |> IO.iodata_to_binary()\n    end\n\n    test_execution_stops \"IO.iodata_length\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end) |> IO.iodata_length()\n    end\n\n    test_execution_stops \"IO.chardata_to_string\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end) |> IO.chardata_to_string()\n    end\n\n    test_execution_stops \"Enum.join\" do\n      Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end) |> Enum.join()\n    end\n\n    @tag :lts_only\n    test_execution_stops \"JSON encode key\" do\n      bomb = Enum.reduce(1..100, [\"foo\", \"bar\"], fn _, acc -> [acc, acc] end)\n      JSON.encode!(%{bomb => 123})\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune_quoted_test.exs",
    "content": "defmodule DuneQuotedTest do\n  use ExUnit.Case, async: true\n\n  import ExUnit.CaptureIO\n\n  alias Dune.{Success, Failure}\n\n  defmacrop dune(do: ast) do\n    escaped_ast = Macro.escape(ast)\n\n    quote do\n      unquote(escaped_ast) |> Dune.eval_quoted(timeout: 100)\n    end\n  end\n\n  describe \"Dune authorized\" do\n    test \"simple operations\" do\n      assert %Success{value: 5} = dune(do: 2 + 3)\n      assert %Success{value: 15} = dune(do: 5 * 3)\n      assert %Success{value: 0.5} = dune(do: 1 / 2)\n      assert %Success{value: [1, 2, 3, 4]} = dune(do: [1, 2] ++ [3, 4])\n      assert %Success{value: \"abcd\"} = dune(do: \"ab\" <> \"cd\")\n    end\n\n    test \"basic Kernel functions\" do\n      assert %Success{value: 10} = dune(do: max(5, 10))\n      assert %Success{value: \"foo\"} = dune(do: to_string(:foo))\n      assert %Success{value: true} = dune(do: is_atom(:foo))\n    end\n\n    test \"Kernel guards\" do\n      assert %Success{value: false} = dune(do: is_binary(55))\n      assert %Success{value: true} = dune(do: is_number(55))\n    end\n\n    test \"basic String functions\" do\n      assert %Success{value: \"JoJo\"} = dune(do: String.replace(\"jojo\", \"j\", \"J\"))\n    end\n\n    test \"basic Map functions\" do\n      assert %Success{value: %{foo: 5}} = dune(do: Map.put(%{}, :foo, 5))\n      assert %Success{value: %{}} = dune(do: Map.new())\n    end\n\n    test \"basic :math functions\" do\n      assert %Success{value: 3.0} = dune(do: :math.log10(1000))\n      assert %Success{value: 3.141592653589793} = dune(do: :math.pi())\n    end\n\n    test \"tuples\" do\n      assert %Success{value: {}} = dune(do: {})\n      assert %Success{value: {:foo}} = dune(do: {:foo})\n      assert %Success{value: {\"hello\", ~c\"world\"}} = dune(do: {\"hello\", ~c\"world\"})\n      assert %Success{value: {1, 2, 3}} = dune(do: {1, 2, 3})\n    end\n\n    test \"map operations\" do\n      assert %Success{value: %{a: :foo, b: 6}} =\n               (dune do\n                  map = %{a: 5, b: 6}\n                  %{map | a: :foo}\n                end)\n\n      assert %Success{value: \"Dio\"} =\n               (dune do\n                  user = %{first_name: \"Dio\", last_name: \"Brando\"}\n                  user.first_name\n                end)\n    end\n\n    test \"dynamic module names (authorized functions)\" do\n      assert %Success{value: %{}} =\n               (dune do\n                  module = Map\n                  module.new()\n                end)\n\n      assert %Success{value: [%{}, %MapSet{}]} =\n               (dune do\n                  Enum.map([Map, MapSet], fn module -> module.new end)\n                end)\n\n      assert %Success{value: [%{}, %MapSet{}]} =\n               (dune do\n                  Enum.map([Map, MapSet], fn module -> module.new() end)\n                end)\n    end\n\n    test \"captures\" do\n      assert %Success{value: [\"1\", \"2\", \"3\"]} =\n               (dune do\n                  1..3 |> Enum.map(&inspect/1)\n                end)\n\n      assert %Success{value: [[0], [1, 0], [2, 0], [3, 0]]} =\n               (dune do\n                  0..30//10 |> Enum.map(&Integer.digits/1)\n                end)\n\n      assert %Success{value: 3.317550714905183e39} =\n               (dune do\n                  1..100//10 |> Enum.map(&:math.exp/1) |> Enum.sum()\n                end)\n    end\n\n    test \"sigils\" do\n      assert %Success{value: %Regex{source: \"(a|b)?c\"}} = dune(do: ~r/(a|b)?c/)\n      assert %Success{value: ~U[2021-05-20 01:02:03Z]} = dune(do: ~U[2021-05-20 01:02:03Z])\n      assert %Success{value: [~c\"foo\", ~c\"bar\", ~c\"baz\"]} = dune(do: ~W[foo bar baz]c)\n\n      assert %Success{value: [~c\"foo\", ~c\"bar\", ~c\"baz\"]} =\n               dune(do: ~w[#{String.downcase(\"FOO\")} bar baz]c)\n\n      assert %Dune.Failure{\n               message: \"** (DuneRestrictedError) function sigil_W/2 is restricted\",\n               type: :restricted\n             } = dune(do: ~W[foo bar baz]a)\n    end\n\n    test \"block of code\" do\n      assert %Success{value: \"quick-brown-fox-jumps-over-lazy-dog\"} =\n               (dune do\n                  sentence = \"the quick brown fox jumps over the lazy dog\"\n                  words = String.split(sentence)\n                  filtered = Enum.reject(words, &(&1 == \"the\"))\n                  Enum.join(filtered, \"-\")\n                end)\n    end\n\n    test \"pipe operator\" do\n      assert %Success{value: \"quick-brown-fox-jumps-over-lazy-dog\"} =\n               (dune do\n                  \"the quick brown fox jumps over the lazy dog\"\n                  |> String.split()\n                  |> Enum.reject(&(&1 == \"the\"))\n                  |> Enum.join(\"-\")\n                end)\n    end\n\n    test \"if block\" do\n      assert %Success{value: {:foo, 6}} =\n               (dune do\n                  x = 6\n\n                  if x > 5 do\n                    {:foo, x}\n                  else\n                    {:bar, x}\n                  end\n                end)\n    end\n\n    test \"case block\" do\n      assert %Success{value: {:bar, 4}} =\n               (dune do\n                  case 4 do\n                    x when x > 5 -> {:foo, x}\n                    y -> {:bar, y}\n                  end\n                end)\n    end\n\n    test \"for block\" do\n      assert %Success{value: [:b, :c]} =\n               (dune do\n                  for {x, y} <- [a: 1, b: 2, c: 3], y > 1, do: x\n                end)\n    end\n  end\n\n  describe \"Dune restricted\" do\n    test \"System calls\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function System.get_env/0 is restricted\"\n             } = dune(do: System.get_env())\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function System.get_env/1 is restricted\"\n             } = dune(do: System.get_env(\"TEST\"))\n    end\n\n    test \"Code calls\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = dune(do: Code.eval_string(\"IO.puts(:hello)\"))\n    end\n\n    test \"String/List restricted methods\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function String.to_atom/1 is restricted\"\n             } = dune(do: String.to_atom(\"foo\"))\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function List.to_atom/1 is restricted\"\n             } = dune(do: List.to_atom(~c\"foo\"))\n\n      assert %Failure{\n               type: :restricted,\n               message:\n                 \"** (DuneRestrictedError) function String.to_existing_atom/1 is restricted\"\n             } = dune(do: String.to_existing_atom(\"foo\"))\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function List.to_existing_atom/1 is restricted\"\n             } = dune(do: List.to_existing_atom(~c\"foo\"))\n    end\n\n    test \"atom interpolation\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.binary_to_atom/2 is restricted\"\n             } = dune(do: :\"#{1 + 1} is two\")\n    end\n\n    test \"Kernel apply/3\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Process.get/0 is restricted\"\n             } =\n               (dune do\n                  apply(Process, :get, [])\n                end)\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function IO.puts/2 is restricted\"\n             } =\n               (dune do\n                  apply(IO, :puts, [:stderr, \"Hello\"])\n                end)\n    end\n\n    test \". operator with variable modules\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.yield/0 is restricted\"\n             } =\n               (dune do\n                  module = :erlang\n                  module.yield\n                end)\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.processes/0 is restricted\"\n             } =\n               (dune do\n                  Enum.map([:erlang], fn module -> module.processes end)\n                end)\n    end\n\n    test \"capture operator\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = dune(do: f = &Code.eval_string/1)\n    end\n\n    test \"Kernel 0-arity functions\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function binding/0 is restricted\"\n             } =\n               (dune do\n                  binding\n                end)\n    end\n\n    test \"erlang unsafe libs\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.processes/0 is restricted\"\n             } = dune(do: :erlang.processes())\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.get/0 is restricted\"\n             } = dune(do: :erlang.get())\n    end\n\n    test \"nested restricted code\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } =\n               (dune do\n                  f = fn -> Code.eval_string(\"IO.puts(:hello)\") end\n                end)\n    end\n  end\n\n  describe \"process restrictions\" do\n    test \"execution timeout\" do\n      assert %Failure{type: :timeout, message: \"Execution timeout - 100ms\"} =\n               dune(do: Process.sleep(101))\n    end\n\n    test \"too many reductions\" do\n      assert %Failure{type: :reductions, message: \"Execution stopped - reductions limit exceeded\"} =\n               dune(do: Enum.any?(1..1_000_000, &(&1 < 0)))\n    end\n\n    test \"uses to much memory\" do\n      assert %Failure{type: :memory, message: \"Execution stopped - memory limit exceeded\"} =\n               dune(do: List.duplicate(:foo, 100_000))\n    end\n  end\n\n  describe \"error handling\" do\n    test \"math error\" do\n      assert %Failure{\n               type: :exception,\n               message: \"** (ArithmeticError) bad argument in arithmetic expression\\n\" <> rest\n             } = dune(do: 42 / 0)\n\n      assert rest =~ \":erlang./(42, 0)\"\n    end\n\n    test \"throw\" do\n      assert %Failure{type: :throw, message: \"** (throw) :yo\"} = dune(do: throw(:yo))\n    end\n\n    test \"raise\" do\n      assert %Failure{type: :exception, message: \"** (ArgumentError) kaboom!\"} =\n               (dune do\n                  raise ArgumentError, \"kaboom!\"\n                end)\n    end\n\n    test \"actual UndefinedFunctionError\" do\n      assert %Failure{\n               type: :exception,\n               message: \"** (UndefinedFunctionError) function Code.baz/0 is undefined or private\"\n             } = dune(do: Code.baz())\n\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (UndefinedFunctionError) function FooBar.baz/0 is undefined (module FooBar is not available)\"\n             } = dune(do: FooBar.baz())\n\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (UndefinedFunctionError) function :foo_bar.baz/0 is undefined (module :foo_bar is not available)\"\n             } = dune(do: :foo_bar.baz())\n    end\n\n    @tag :lts_only\n    test \"undefined variable\" do\n      capture_io(:stderr, fn ->\n        # TODO better error messages\n        assert %Failure{\n                 type: :compile_error,\n                 message:\n                   \"** (CompileError) nofile: cannot compile file (errors have been logged)\",\n                 stdio: \"error: undefined variable \\\"y\\\"\\n\" <> _\n               } = dune(do: y)\n\n        assert %Failure{\n                 type: :compile_error,\n                 message:\n                   \"** (CompileError) nofile: cannot compile file (errors have been logged)\",\n                 stdio: \"error: undefined variable \\\"x\\\"\\n\" <> _\n               } = dune(do: if(x, do: x))\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune_string_test.exs",
    "content": "defmodule DuneStringTest do\n  use ExUnit.Case, async: true\n\n  alias Dune.{Success, Failure}\n\n  defmacro sigil_E(call, _expr) do\n    quote do\n      Dune.eval_string(unquote(call), timeout: 100, inspect_sort_maps: true)\n    end\n  end\n\n  describe \"Dune authorized\" do\n    test \"simple operations\" do\n      assert %Success{value: 5, inspected: ~S'5'} = ~E'2 + 3'\n      assert %Success{value: 15, inspected: ~S'15'} = ~E'5 * 3'\n      assert %Success{value: 0.5, inspected: ~S'0.5'} = ~E'1 / 2'\n      assert %Success{value: [1, 2, 3, 4], inspected: ~S'[1, 2, 3, 4]'} = ~E'[1, 2] ++ [3, 4]'\n      assert %Success{value: \"abcd\", inspected: ~S'\"abcd\"'} = ~E'\"ab\" <> \"cd\"'\n      assert %Success{value: true, inspected: ~S'true'} = ~E'\"abc!\" =~ ~r/abc/'\n    end\n\n    test \"basic Kernel functions\" do\n      assert %Success{value: 10, inspected: ~S'10'} = ~E'max(5, 10)'\n      assert %Success{value: \"foo\", inspected: ~S'\"foo\"'} = ~E'to_string(:foo)'\n      assert %Success{value: true, inspected: ~S'true'} = ~E'is_atom(:foo)'\n    end\n\n    test \"Kernel guards\" do\n      assert %Success{value: false, inspected: ~S'false'} = ~E'is_binary(55)'\n      assert %Success{value: true, inspected: ~S'true'} = ~E'is_number(55)'\n    end\n\n    test \"basic String functions\" do\n      assert %Success{value: \"JoJo\", inspected: ~S'\"JoJo\"'} = ~E'String.replace(\"jojo\", \"j\", \"J\")'\n    end\n\n    test \"basic Map functions\" do\n      assert %Success{value: %{foo: 5}, inspected: ~S'%{foo: 5}'} = ~E'Map.put(%{}, :foo, 5)'\n      assert %Success{value: %{}, inspected: ~S'%{}'} = ~E'Map.new()'\n    end\n\n    test \"basic :math functions\" do\n      assert %Success{value: 3.0, inspected: ~S'3.0'} = ~E':math.log10(1000)'\n      assert %Success{value: 3.141592653589793, inspected: ~S'3.141592653589793'} = ~E':math.pi()'\n    end\n\n    @tag :lts_only\n    test \"tuples\" do\n      assert %Success{value: {}, inspected: ~S'{}'} = ~E'{}'\n      assert %Success{value: {:foo}, inspected: ~S'{:foo}'} = ~E'{:foo}'\n\n      assert %Success{value: {\"hello\", ~c\"world\"}, inspected: ~S/{\"hello\", ~c\"world\"}/} =\n               ~E/{\"hello\", 'world'}/\n\n      assert %Success{value: {1, 2, 3}, inspected: ~S'{1, 2, 3}'} = ~E'{1, 2, 3}'\n    end\n\n    test \"map operations\" do\n      assert %Success{value: %{a: :foo, b: 6}, inspected: ~S'%{a: :foo, b: 6}'} = ~E'\n        map = %{a: 5, b: 6}\n        %{map | a: :foo}\n        '\n\n      assert %Success{\n               value: \"Dio\",\n               inspected: ~S'\"Dio\"'\n             } = ~E'\n                  user = %{first_name: \"Dio\", last_name: \"Brando\"}\n                  user.first_name\n                '\n    end\n\n    test \"dynamic module names (authorized functions)\" do\n      assert %Success{value: %{}, inspected: ~S'%{}'} = ~E'module = Map; module.new()'\n\n      assert %Success{value: [%{}], inspected: ~S'[%{}]'} =\n               ~E'Enum.map([Map], fn module -> module.new end)'\n\n      assert %Success{value: [%{}], inspected: ~S'[%{}]'} =\n               ~E'Enum.map([Map], fn module -> module.new() end)'\n\n      assert %Success{value: 3, inspected: ~S'3'} = ~E'apply(List, :last, [[1, 2, 3]])'\n    end\n\n    test \"captures\" do\n      assert %Success{value: 33, inspected: ~S'33'} = ~E'f = &+/2; f.(11, 22)'\n\n      assert %Success{value: 35, inspected: ~S'35'} = ~E'f = & &1*&2; f.(5, 7)'\n\n      assert %Success{value: 1.5, inspected: ~S'1.5'} = ~E'f = & &1/&2; 3 |> f.(2)'\n\n      assert %Success{value: 20, inspected: ~S'20'} = ~E'apply(& &1 * 2, [10])'\n\n      assert %Success{value: [\"1\", \"2\", \"3\"], inspected: ~S'[\"1\", \"2\", \"3\"]'} =\n               ~E'1..3 |> Enum.map(&inspect/1)'\n\n      assert %Success{\n               value: [[0], [1, 0], [2, 0], [3, 0]],\n               inspected: ~S'[[0], [1, 0], [2, 0], [3, 0]]'\n             } = ~E'0..30//10 |> Enum.map(&Integer.digits/1)'\n\n      assert %Success{value: 3.317550714905183e39, inspected: ~S'3.317550714905183e39'} =\n               ~E'1..100//10 |> Enum.map(&:math.exp/1) |> Enum.sum()'\n    end\n\n    test \"anonymous functions\" do\n      assert %Success{value: 0, inspected: ~S'0'} = ~E'f = fn -> _x = 0 end; f.()'\n      assert %Success{value: 0, inspected: ~S'0'} = ~E'(fn -> _x = 0 end).()'\n      assert %Success{value: 3, inspected: ~S'3'} = ~E'2 |> (fn x -> x + 1 end).()'\n      assert %Success{value: -4, inspected: ~S'-4'} = ~E'(&(&2 - &1)).(7, 3)'\n      assert %Success{value: -4, inspected: ~S'-4'} = ~E'(& &2 - &1).(7, 3)'\n      assert %Success{value: 3, inspected: ~S'3'} = ~E'2 |> (& &1 + 1).()'\n      assert %Success{value: 0.2, inspected: ~S'0.2'} = ~E'(& &2 / &1).(10, 2)'\n    end\n\n    @tag :lts_only\n    test \"sigils\" do\n      assert %Success{value: %Regex{source: \"(a|b)?c\"}, inspected: ~S'~r/(a|b)?c/'} =\n               ~E'~r/(a|b)?c/'\n\n      assert %Success{value: ~U[2021-05-20 01:02:03Z], inspected: ~S'~U[2021-05-20 01:02:03Z]'} =\n               ~E'~U[2021-05-20 01:02:03Z]'\n\n      assert %Success{\n               value: [~c\"foo\", ~c\"bar\", ~c\"baz\"],\n               inspected: ~S'[~c\"foo\", ~c\"bar\", ~c\"baz\"]'\n             } = ~E'~W[foo bar baz]c'\n\n      assert %Success{\n               value: [~c\"foo\", ~c\"bar\", ~c\"baz\"],\n               inspected: ~S'[~c\"foo\", ~c\"bar\", ~c\"baz\"]'\n             } = ~E'~w[#{String.downcase(\"FOO\")} bar baz]c'\n\n      assert %Dune.Failure{\n               message: \"** (DuneRestrictedError) function sigil_W/2 is restricted\",\n               type: :restricted\n             } = ~E'~W[foo bar baz]a'\n\n      assert %Dune.Failure{\n               message: \"** (DuneRestrictedError) function sigil_w/2 is restricted\",\n               type: :restricted\n             } = ~E'~w[#{String.downcase(\"FOO\")} bar baz]a'\n    end\n\n    @tag :lts_only\n    test \"bitstring modifiers\" do\n      assert %Success{\n               value: <<3>>,\n               inspected: \"<<3>>\"\n             } = ~E'<<3>>'\n\n      assert %Success{\n               value: <<3::4>>,\n               inspected: \"<<3::size(4)>>\"\n             } = ~E'<<3::4>>'\n\n      assert %Success{\n               value: \"ߧ\",\n               inspected: ~S'\"ߧ\"'\n             } = ~E'<<2023::utf8>>'\n\n      assert %Success{\n               value: 12520,\n               inspected: \"12520\"\n             } = ~E'<<c::utf8>> = \"ヨ\"; c'\n\n      assert %Success{\n               value: <<3::4>>,\n               inspected: \"<<3::size(4)>>\"\n             } = ~E'<<3::size(4)>>'\n\n      assert %Success{\n               value: 6_382_179,\n               inspected: \"6382179\"\n             } = ~E'<<c::size(24)>> = \"abc\"; c'\n\n      assert %Success{\n               value: <<2, 3>>,\n               inspected: \"<<2, 3>>\"\n             } = ~E'<<_::binary-size(2), rest::binary>> = <<0, 1, 2, 3>>; rest'\n\n      assert %Success{\n               value: <<0, 0, 0, 1>>,\n               inspected: \"<<0, 0, 0, 1>>\"\n             } = ~E'''\n             x = 1\n             <<x::8*4>>\n             '''\n\n      assert %Success{\n               value: <<0, 0, 0, 1>>,\n               inspected: \"<<0, 0, 0, 1>>\"\n             } = ~E'''\n             x = 1\n             <<x::size(8)-unit(4)>>\n             '''\n\n      assert %Success{\n               value: {\"Frank\", \"Walrus\"},\n               inspected: ~S'{\"Frank\", \"Walrus\"}'\n             } = ~E'''\n             name_size = 5\n             <<name::binary-size(^name_size), \" the \", species::binary>> = <<\"Frank the Walrus\">>\n             {name, species}\n             {\"Frank\", \"Walrus\"}\n             '''\n    end\n\n    test \"binary comprehensions\" do\n      assert %Success{\n               value: [{213, 45, 132}, {64, 76, 32}],\n               inspected: \"[{213, 45, 132}, {64, 76, 32}]\"\n             } = ~E'''\n             pixels = <<213, 45, 132, 64, 76, 32>>\n             for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}\n             '''\n    end\n\n    test \"block of code\" do\n      assert %Success{\n               value: \"quick-brown-fox-jumps-over-lazy-dog\",\n               inspected: ~S'\"quick-brown-fox-jumps-over-lazy-dog\"'\n             } = ~E'\n                  sentence = \"the quick brown fox jumps over the lazy dog\"\n                  words = String.split(sentence)\n                  filtered = Enum.reject(words, &(&1 == \"the\"))\n                  Enum.join(filtered, \"-\")\n                '\n    end\n\n    test \"pipe operator\" do\n      assert %Success{\n               value: \"quick-brown-fox-jumps-over-lazy-dog\",\n               inspected: ~S'\"quick-brown-fox-jumps-over-lazy-dog\"'\n             } = ~E'\n                  \"the quick brown fox jumps over the lazy dog\"\n                  |> String.split()\n                  |> Enum.reject(&(&1 == \"the\"))\n                  |> Enum.join(\"-\")\n                '\n\n      assert %Success{value: \":foo\", inspected: ~S'\":foo\"'} = ~E':foo |> inspect()'\n    end\n\n    test \"atoms\" do\n      assert %Success{value: \"foo51\", inspected: ~s'\"foo51\"'} = ~E'to_string(:foo51)'\n\n      assert %Success{value: \"foo52\", inspected: ~s'\"foo52\"'} = ~E'Atom.to_string(:foo52)'\n\n      assert %Success{value: \"foo53\", inspected: ~s'\"foo53\"'} = ~E'Enum.join([:foo53])'\n\n      assert %Success{value: \"boo57\", inspected: ~s'\"boo57\"'} =\n               ~E':foo57 |> to_string() |> String.replace(\"f\", \"b\")'\n\n      assert %Success{value: \"Hello boo58\", inspected: ~s'\"Hello boo58\"'} =\n               ~E'\"Hello #{:foo58}\" |> String.replace(\"f\", \"b\")'\n\n      assert %Success{value: :Dune_Atom_1__, inspected: ~s':Foo12'} = ~E':Foo12'\n      assert %Success{value: Dune_Module_1__, inspected: ~s'Foo13'} = ~E'Foo13'\n\n      assert %Success{value: \"Foo14\", inspected: ~s'\"Foo14\"'} = ~E'Atom.to_string(:Foo14)'\n\n      assert %Success{value: \"Elixir.Foo15\", inspected: ~s(\"Elixir.Foo15\")} =\n               ~E'Atom.to_string(Foo15)'\n\n      assert %Success{value: \":Foo16\", inspected: ~s'\":Foo16\"'} = ~E'inspect(:Foo16)'\n\n      assert %Success{value: \"Foo17\", inspected: ~s'\"Foo17\"'} = ~E'inspect(Foo17)'\n\n      assert %Success{value: \"Elixir.Foo.Bar33\", inspected: ~s(\"Elixir.Foo.Bar33\")} =\n               ~E'Atom.to_string(Foo.Bar33)'\n\n      assert %Success{\n               value: [\n                 Dune_Module_1__,\n                 {:a__Dune_atom_2__, :Dune_Atom_1__},\n                 [a__Dune_atom_2__: 15, Dune_Atom_1__: 6, __Dune_atom_3__: 33]\n               ],\n               inspected: ~s([Foo91, {:foo91, :Foo91}, [foo91: 15, Foo91: 6, _foo92: 33]])\n             } = ~E'[Foo91, {:foo91, :Foo91}, [foo91: 15, Foo91: 6, _foo92: 33]]'\n    end\n\n    @tag :lts_only\n    test \"atoms to charlist\" do\n      assert %Success{value: ~c\"Hello foo59\", inspected: ~s'~c\"Hello foo59\"'} =\n               ~E'~c\"Hello #{:foo59}\"'\n\n      assert %Success{value: ~c\"Elixir.Foo15\", inspected: ~s(~c\"Elixir.Foo15\")} =\n               ~E'Atom.to_charlist(Foo15)'\n    end\n\n    test \"atoms (prefixed by Elixir)\" do\n      assert %Success{value: Elixir, inspected: ~s'Elixir'} = ~E'Elixir'\n\n      assert %Success{value: Dune_Module_1__, inspected: ~s'Foo13'} = ~E'Elixir.Foo13'\n      assert %Success{value: Dune_Module_1__, inspected: ~s'Foo13'} = ~E':\"Elixir.Foo13\"'\n\n      assert %Success{value: true, inspected: ~s'true'} = ~E'Elixir.Foo13 == Foo13'\n      assert %Success{value: true, inspected: ~s'true'} = ~E':\"Elixir.Foo13\" == Foo13'\n\n      assert %Success{value: true, inspected: ~s'true'} = ~E'Elixir.Foo13.Foo13 == Foo13.Foo13'\n      assert %Success{value: true, inspected: ~s'true'} = ~E':\"Elixir.Foo13.Foo13\" == Foo13.Foo13'\n\n      assert %Success{value: String, inspected: ~s'String'} = ~E':\"Elixir.String\"'\n\n      assert %Success{value: Dune_Module_1__, inspected: ~s'Elixir.Elixir'} = ~E'Elixir.Elixir'\n      assert %Success{value: Dune_Module_1__, inspected: ~s'Elixir.Elixir'} = ~E':\"Elixir.Elixir\"'\n    end\n\n    test \"atoms (wrapped with quotes)\" do\n      assert %Success{value: :__Dune_atom_1__, inspected: ~s':\" \"'} = ~E':\" \"'\n      assert %Success{value: :__Dune_atom_1__, inspected: ~s':\"foo/bar\"'} = ~E':\"foo/bar\"'\n\n      assert %Success{value: \" \", inspected: ~s'\" \"'} = ~E'to_string :\" \"'\n      assert %Success{value: \"foo/bar\", inspected: ~s'\"foo/bar\"'} = ~E'to_string :\"foo/bar\"'\n\n      assert %Success{\n               value: [\n                 __Dune_atom_1__: {:__Dune_atom_2__, :__Dune_atom_3__},\n                 __Dune_atom_4__: %{__Dune_atom_5__: :__Dune_atom_6__, a__Dune_atom_7__: 6}\n               ],\n               inspected: ~s([\" \": {:\"\\t\", :\" A\"}, \"ab cd\": %{\"foo+91\": :\"15\", abc: 6}])\n             } = ~E([\" \": {:\"\\t\", :\" A\"}, \"ab cd\": %{\"foo+91\": :'15', abc: 6}])\n    end\n\n    test \"function and atom parameters\" do\n      assert \":digits\" = ~E':digits'.value |> inspect()\n      assert \":turkic\" = ~E':turkic'.value |> inspect()\n    end\n\n    test \"stdio capture\" do\n      assert %Success{value: :ok, inspected: ~s(:ok), stdio: \"yo!\\n\"} = ~E'IO.puts(\"yo!\")'\n      assert %Success{value: :ok, inspected: ~s(:ok), stdio: \"foo987\\n\"} = ~E'IO.puts(:foo987)'\n\n      assert %Success{value: :ok, inspected: ~s(:ok), stdio: \"hello world!\\n\"} =\n               ~E'io = IO; io.puts([\"hello\", ?\\s, \"world\", ?!])'\n\n      assert %Success{value: :ok, inspected: ~s(:ok), stdio: \"1\\n2\\n3\\n\"} =\n               ~E'Enum.each(1..3, &IO.puts/1)'\n\n      assert %Success{value: :a__Dune_atom_1__, inspected: ~s(:foo912), stdio: \":foo912\\n\"} =\n               ~E'IO.inspect(:foo912)'\n\n      assert %Success{\n               value: %{a__Dune_atom_1__: 581},\n               inspected: ~s(%{foo9101: 581}),\n               stdio: \"bar777: %{foo9101: 581}\\n\"\n             } = ~E'%{foo9101: 581} |> IO.inspect(label: :bar777)'\n\n      assert %Success{\n               value: :ok,\n               inspected: \":ok\",\n               stdio: \":foo9321\\nfoo9321\\n\"\n             } = ~E'io = IO; io.puts(io.inspect(:foo9321))'\n    end\n\n    test \"dbg\" do\n      assert %Success{value: :a__Dune_atom_1__, inspected: ~s(:foo913), stdio: stdio} =\n               ~E'dbg(:foo913)'\n\n      assert stdio == \"\"\"\n             [nofile:1: (file)]\n             :foo913 #=> :foo913\n\n             \"\"\"\n\n      assert %Success{value: :a__Dune_atom_1__, inspected: ~s(:foo914), stdio: stdio} =\n               ~E':foo914 |> dbg()'\n\n      assert stdio == \"\"\"\n             [nofile:1: (file)]\n             :foo914 #=> :foo914\n\n             \"\"\"\n\n      assert %Success{value: \":foo915\", inspected: ~s(\":foo915\"), stdio: stdio} =\n               ~E':foo915 |> inspect() |> dbg()'\n\n      assert stdio == \"\"\"\n             [nofile:1: (file)]\n             :foo915 #=> :foo915\n             |> inspect() #=> \":foo915\"\n\n             \"\"\"\n\n      assert %Success{value: \"Hello World\", inspected: ~s(\"Hello World\"), stdio: stdio} =\n               ~E'\"hello world\" |> String.split() |> Enum.map_join(\" \", &String.capitalize/1) |> dbg()'\n\n      assert stdio == \"\"\"\n             [nofile:1: (file)]\n             \"hello world\" #=> \"hello world\"\n             |> String.split() #=> [\"hello\", \"world\"]\n             |> Enum.map_join(\" \", &String.capitalize/1) #=> \"Hello World\"\n\n             \"\"\"\n\n      assert %Success{value: \":foo987398\", inspected: ~s(\":foo987398\"), stdio: stdio} =\n               ~E':foo987398 |> inspect() |> dbg() |> dbg()'\n\n      assert stdio == \"\"\"\n             [nofile:1: (file)]\n             :foo987398 #=> :foo987398\n             |> inspect() #=> \":foo987398\"\n\n             [nofile:1: (file)]\n             :foo987398 |> inspect() |> dbg() #=> \":foo987398\"\n\n             \"\"\"\n\n      assert %Dune.Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function dbg/0 is restricted\"\n             } = ~E'dbg()'\n\n      assert %Dune.Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function dbg/2 is restricted\"\n             } = ~E'dbg(:abc, syntax_colors: [])'\n\n      assert %Dune.Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'Code.eval_string(\":hello\") |> dbg()'\n\n      assert %Dune.Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'\":hello\" |> Code.eval_string() |> dbg()'\n    end\n\n    test \"pretty option\" do\n      raw_string =\n        ~S'{\"This line is really long, maybe we should break\", [%{bar: 1, baz: 2}, %{bar: 55}]}'\n\n      with_break =\n        ~s'{\"This line is really long, maybe we should break\",\\n [%{bar: 1, baz: 2}, %{bar: 55}]}'\n\n      assert %Success{inspected: ^raw_string} =\n               Dune.eval_string(raw_string, inspect_sort_maps: true)\n\n      assert %Success{inspected: ^with_break} =\n               Dune.eval_string(raw_string, pretty: true, inspect_sort_maps: true)\n    end\n  end\n\n  describe \"Dune restricted\" do\n    test \"System calls\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function System.get_env/0 is restricted\"\n             } = ~E'System.get_env()'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function System.get_env/1 is restricted\"\n             } = ~E'System.get_env(\"TEST\")'\n    end\n\n    test \"Code calls\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'Code.eval_string(\"IO.puts(:hello)\")'\n    end\n\n    test \"String/List restricted methods\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function String.to_atom/1 is restricted\"\n             } = ~E'String.to_atom(\"foo\")'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function List.to_atom/1 is restricted\"\n             } = ~E/List.to_atom('foo')/\n    end\n\n    test \"atom interpolation\" do\n      assert %Failure{\n               type: :restricted,\n               message:\n                 \"** (DuneRestrictedError) function :erlang.binary_to_existing_atom/2 is restricted\"\n             } = ~E':\"#{1 + 1} is two\"'\n    end\n\n    test \"Kernel apply/3\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Process.get/0 is restricted\"\n             } = ~E'apply(Process, :get, [])'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function IO.puts/2 is restricted\"\n             } = ~E'apply(IO, :puts, [:stderr, \"Hello\"])'\n    end\n\n    test \". operator with variable modules\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.yield/0 is restricted\"\n             } = ~E'\n                  module = :erlang\n                  module.yield\n                '\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.processes/0 is restricted\"\n             } = ~E'\n                  Enum.map([:erlang], fn module -> module.processes end)\n                '\n    end\n\n    test \". operator as key access\" do\n      assert %Success{value: 100, inspected: ~S'100'} =\n               ~E'users = [john: %{age: 100}]; users[:john].age'\n    end\n\n    test \". operator various failures\" do\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (UndefinedFunctionError) function :foo.bar/0 is undefined (module :foo is not available)\"\n             } = ~E'module = :foo; module.bar()'\n\n      assert %Failure{\n               type: :exception,\n               message: \"** (UndefinedFunctionError) function List.bar/0 is undefined or private\"\n             } = ~E'module = List; module.bar()'\n\n      assert %Failure{\n               type: :exception,\n               message: \"** (KeyError) key :job not found in\" <> rest_message\n             } = ~E'users = [john: %{age: 100}]; users[:john].job'\n\n      assert rest_message =~ \"%{age: 100}\"\n\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (UndefinedFunctionError) function Foo1234.bar567/0 is undefined (module Foo1234 is not available)\"\n             } = ~E'Foo1234.bar567.baz890'\n    end\n\n    test \"pipe operator\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'\":foo\" |> Code.eval_string()'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'code = Code; \"1 + 1\" |> code.eval_string()'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'code = Code; \"1 + 1\" |> code.eval_string'\n    end\n\n    test \"capture operator\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'f = &Code.eval_string/1'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'f = &Code.eval_string(&1)'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'(&Code.eval_string/1).(\":pawned!\")'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'(&Code.eval_string(&1)).(\":pawned!\")'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'\":pawned!\" |> (&Code.eval_string/1).()'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'\":pawned!\" |> (&Code.eval_string(&1)).()'\n    end\n\n    test \"Kernel 0-arity functions\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function binding/0 is restricted\"\n             } = ~E'binding'\n    end\n\n    test \"erlang unsafe libs\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.processes/0 is restricted\"\n             } = ~E':erlang.processes()'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.get/0 is restricted\"\n             } = ~E':erlang.get()'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function :erlang.process_info/1 is restricted\"\n             } = ~E':erlang.process_info(self)'\n    end\n\n    test \"nested restricted code\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function Code.eval_string/1 is restricted\"\n             } = ~E'f = fn -> Code.eval_string(\"IO.puts(:hello)\") end'\n    end\n\n    test \"partially restricted shims\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function IO.puts/2 is restricted\"\n             } = ~E'IO.puts(:stderr, \"foo\")'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function IO.puts/2 is restricted\"\n             } = ~E':stderr |> IO.puts(\"foo\")'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function IO.inspect/3 is restricted\"\n             } = ~E'IO.inspect(:stderr, \"foo\", [])'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function IO.inspect/3 is restricted\"\n             } = ~E'io = IO; io.inspect(:stderr, \"foo\", [])'\n    end\n\n    test \"forbidden atoms\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"Atoms containing `Dune` are restricted for safety: Dune\"\n             } = ~E'Dune'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"Atoms containing `Dune` are restricted for safety: Dune\"\n             } = ~E'Dune.Foo'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"Atoms containing `Dune` are restricted for safety: Dune\"\n             } = ~E':Dune'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"Atoms containing `Dune` are restricted for safety: __Dune__\"\n             } = ~E':__Dune__'\n    end\n\n    test \"forbidden use\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function use/1 is restricted\"\n             } = ~E'use GenServer'\n    end\n\n    test \"forbidden import/requires\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function import/1 is restricted\"\n             } = ~E'import Logger'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function import/2 is restricted\"\n             } = ~E'import Logger, only: [info: 2]'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function require/1 is restricted\"\n             } = ~E'require Logger'\n    end\n\n    test \"forbidden alias\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function alias/1 is restricted\"\n             } = ~E'alias Task.Supervised'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function alias/2 is restricted\"\n             } = ~E'alias Process, as: P; P.get'\n    end\n\n    test \"forbidden quote/unquote\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function quote/1 is restricted\"\n             } = ~E'quote do: 1 + 1'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function quote/1 is restricted\"\n             } = ~E'quote do: unquote(a)'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function unquote/1 is restricted\"\n             } = ~E'unquote(10)'\n    end\n\n    test \"forbidden receive\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function receive/1 is restricted\"\n             } = ~E'''\n             receive do\n               {:ok, foo} -> foo\n             end\n             '''\n    end\n\n    test \"forbidden __ENV__\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function __ENV__/0 is restricted\"\n             } = ~E'__ENV__'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) function __ENV__/0 is restricted\"\n             } = ~E'__ENV__.requires'\n    end\n\n    test \"bitstring modifiers\" do\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) size modifiers above 256 are restricted\"\n             } = ~E'<<0::123456789123456789>>'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) size modifiers above 256 are restricted\"\n             } = ~E'<<0::size(257)>>'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) size modifiers above 256 are restricted\"\n             } = ~E'<<0::256*2>>'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) size modifiers above 256 are restricted\"\n             } = ~E'<<0::integer-size(257)>>'\n\n      assert %Failure{\n               type: :restricted,\n               message: \"** (DuneRestrictedError) size modifiers above 256 are restricted\"\n             } = ~E'<<0::integer-size(256)-unit(2)>>'\n\n      assert %Failure{\n               type: :restricted,\n               message:\n                 \"** (DuneRestrictedError) bitstring modifier is restricted:\\n         size(x)\"\n             } = ~E'''\n             x = 123456789123456789\n             <<0::size(x)>>\n             '''\n\n      assert %Failure{\n               message:\n                 \"** (DuneRestrictedError) bitstring modifier is restricted:\\n         unquote(1)\"\n             } = ~E'<<1::unquote(1)>>'\n\n      assert %Failure{\n               message:\n                 \"** (DuneRestrictedError) bitstring modifier is restricted:\\n         unquote(1)\"\n             } = ~E'<<1::integer-unquote(1)>>'\n\n      assert %Failure{\n               message: \"** (DuneRestrictedError) function unquote/1 is restricted\"\n             } = ~E'<<unquote(1)>>'\n    end\n  end\n\n  describe \"process restrictions\" do\n    test \"execution timeout\" do\n      assert %Failure{type: :timeout, message: \"Execution timeout - 100ms\"} =\n               ~E'Process.sleep(101)'\n    end\n\n    test \"too many reductions\" do\n      assert %Failure{type: :reductions, message: \"Execution stopped - reductions limit exceeded\"} =\n               ~E'Enum.any?(1..1_000_000, &(&1 < 0))'\n    end\n\n    test \"uses to much memory\" do\n      assert %Failure{type: :memory, message: \"Execution stopped - memory limit exceeded\"} =\n               ~E'List.duplicate(:foo, 100_000)'\n    end\n  end\n\n  describe \"error handling\" do\n    test \"math error\" do\n      assert %Failure{\n               type: :exception,\n               message: \"** (ArithmeticError) bad argument in arithmetic expression\\n\" <> rest\n             } = ~E'42 / 0'\n\n      assert rest =~ \":erlang./(42, 0)\"\n    end\n\n    test \"throw\" do\n      assert %Failure{type: :throw, message: \"** (throw) :yo\"} = ~E'throw(:yo)'\n\n      assert %Failure{type: :throw, message: \"** (throw) {:undefined_function, Kernel, :+, 2}\"} =\n               ~E'throw({:undefined_function, Kernel, :+, 2})'\n    end\n\n    test \"raise\" do\n      assert %Failure{type: :exception, message: \"** (ArgumentError) kaboom!\"} =\n               ~E'raise ArgumentError, \"kaboom!\"'\n    end\n\n    test \"actual UndefinedFunctionError\" do\n      assert %Failure{\n               type: :exception,\n               message: \"** (UndefinedFunctionError) function Code.baz/0 is undefined or private\"\n             } = ~E'Code.baz()'\n\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (UndefinedFunctionError) function FooBar.baz/0 is undefined (module FooBar is not available)\"\n             } = ~E'FooBar.baz()'\n\n      assert %Failure{\n               type: :exception,\n               message:\n                 \"** (UndefinedFunctionError) function :foo_bar.baz/0 is undefined (module :foo_bar is not available)\"\n             } = ~E':foo_bar.baz()'\n    end\n\n    @tag :lts_only\n    test \"syntax error\" do\n      assert %Failure{\n               type: :parsing,\n               message: \"missing terminator: )\"\n             } = ~E'foo('\n\n      assert %Failure{\n               type: :parsing,\n               message: \"missing terminator: }\"\n             } = ~E'{'\n\n      assert %Failure{\n               type: :parsing,\n               message: \"unexpected reserved word: do. In case you wanted to write \" <> _\n             } = ~E'if true, do'\n\n      # TODO improve message\n      assert %Failure{type: :parsing, message: \"syntax error before: \"} = ~E'%'\n\n      assert %Failure{type: :parsing, message: \"syntax error before: foo120987\"} =\n               ~E'<<>>foo120987'\n    end\n\n    test \"max length\" do\n      assert %Failure{\n               type: :parsing,\n               message: \"max code length exceeded: 26 > 10\"\n             } = Dune.eval_string(\"exceeeds_max_length = true\", max_length: 10)\n    end\n\n    test \"atom pool\" do\n      assert %Failure{\n               type: :parsing,\n               message: \"atom_pool_size exceeded, failed to parse atom: bar1462\"\n             } = Dune.eval_string(\"{foo5345, bar1462} = {9, 10}\", atom_pool_size: 4)\n    end\n\n    test \"invalid pipe\" do\n      assert %Failure{\n               type: :exception,\n               message: \"** (ArgumentError) cannot pipe 1 into 2, can only pipe into \" <> _\n             } = ~E'1 |> 2'\n\n      assert %Failure{\n               type: :exception,\n               message: \"** (UndefinedFunctionError) function b/1 is undefined or private\"\n             } = ~E'a |> b'\n    end\n\n    @tag :lts_only\n    test \"compile error\" do\n      assert %Failure{\n               type: :compile_error,\n               message: \"** (CompileError) nofile: cannot compile file (errors have been logged)\",\n               stdio: \"error: expected -> clauses for :do in \\\"case\\\"\\n  nofile:2\"\n             } = ~E'\n                case 1 do\n                end\n              '\n\n      assert %Failure{\n               type: :compile_error,\n               message: \"** (CompileError) nofile: cannot compile file (errors have been logged)\",\n               stdio: \"error: undefined variable \\\"this_var_does_not_exist\\\"\\n  nofile:1\"\n             } = ~E'this_var_does_not_exist'\n    end\n\n    @tag :lts_only\n    test \"warnings\" do\n      assert %Success{value: 1, inspected: \"1\", stdio: stdio} = ~E'\n                f = fn ->\n                  IO.puts(\"hello\")\n                  this_var_is_unused = 1\n                end\n                f.()\n              '\n\n      assert stdio == \"\"\"\n             warning: variable \\\"this_var_is_unused\\\" is unused (if the variable is not meant to be used, prefix it with an underscore)\n               nofile:4\n\n             hello\n             \"\"\"\n    end\n\n    @tag :lts_only\n    test \"parser warnings\" do\n      assert %Success{value: ~c\"123abc\", inspected: \"~c\\\"123abc\\\"\", stdio: stdio} =\n               ~E\"'123' ++ [97, 98, 99]\"\n\n      assert stdio =~\n               \"\"\"\n               warning: using single-quoted strings to represent charlists is deprecated.\n               Use ~c\\\"\\\" if you indeed want a charlist or use \\\"\\\" instead\\\n               \"\"\"\n    end\n\n    test \"def/defp outside of module\" do\n      assert %Failure{\n               type: :exception,\n               message: \"** (ArgumentError) cannot invoke def/2 inside function/macro\"\n             } = ~E'def foo(x), do: x + x'\n\n      assert %Failure{\n               type: :exception,\n               message: \"** (ArgumentError) cannot invoke defp/2 inside function/macro\"\n             } = ~E'defp foo(x), do: x + x'\n\n      assert %Failure{\n               type: :exception,\n               message: \"** (ArgumentError) cannot invoke def/2 inside function/macro\"\n             } = ~E'&def/2'\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune_string_to_quoted_test.exs",
    "content": "defmodule DuneStringToQuotedTest do\n  use ExUnit.Case, async: true\n\n  alias Dune.Success\n\n  describe \"Dune.string_to_quoted/2\" do\n    test \"modules\" do\n      assert %Success{\n               value: {:__aliases__, [line: 1], [:Dune_Atom_1__, :Dune_Atom_2__]},\n               inspected: ~S\"{:__aliases__, [line: 1], [:Foooo, :Barrr]}\"\n             } = Dune.string_to_quoted(~S(Foooo.Barrr))\n    end\n\n    @tag :lts_only\n    test \"captures tokenizer warnings\" do\n      assert %Success{\n               value: ~c\"single quotes\",\n               inspected: ~S(~c\"single quotes\"),\n               stdio: stdio\n             } = Dune.string_to_quoted(~S('single quotes'))\n\n      assert stdio =~ \"warning: using single-quoted strings to represent charlists is deprecated.\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/dune_test.exs",
    "content": "defmodule DuneTest do\n  use ExUnit.Case, async: true\n\n  # TODO remove then dropping support for 1.15\n  doctest Dune, tags: [lts_only: true]\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "ExUnit.start()\n"
  }
]