├── .tool-versions ├── mix.lock.license ├── .tool-versions.license ├── test ├── test_helper.exs └── simple_sat_test.exs ├── .check.exs ├── .formatter.exs ├── .doctor.exs ├── .github ├── workflows │ └── elixir.yml └── dependabot.yml ├── config └── config.exs ├── .gitignore ├── README.md ├── LICENSES └── MIT.txt ├── CHANGELOG.md ├── mix.exs ├── lib └── simple_sat.ex ├── .credo.exs └── mix.lock /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 26.0.2 2 | elixir 1.16.0 3 | pipx 1.8.0 4 | -------------------------------------------------------------------------------- /mix.lock.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /.tool-versions.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | ExUnit.start() 6 | -------------------------------------------------------------------------------- /.check.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | [ 6 | tools: [ 7 | {:reuse, command: ["pipx", "run", "reuse", "lint", "-q"]} 8 | ] 9 | ] 10 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # Used by "mix format" 6 | [ 7 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 8 | ] 9 | -------------------------------------------------------------------------------- /.doctor.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | %Doctor.Config{ 6 | ignore_modules: [], 7 | ignore_paths: [], 8 | min_module_doc_coverage: 40, 9 | min_module_spec_coverage: 0, 10 | min_overall_doc_coverage: 48, 11 | min_overall_moduledoc_coverage: 100, 12 | min_overall_spec_coverage: 0, 13 | exception_moduledoc_required: true, 14 | raise: false, 15 | reporter: Doctor.Reporters.Full, 16 | struct_type_spec_required: true, 17 | umbrella: false, 18 | failed: false 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: CI 6 | 7 | on: 8 | push: 9 | tags: 10 | - "v*" 11 | branches: [main] 12 | pull_request: 13 | branches: [main] 14 | jobs: 15 | ash-ci: 16 | uses: ash-project/ash/.github/workflows/ash-ci.yml@main 17 | secrets: 18 | HEX_API_KEY: ${{ secrets.HEX_API_KEY }} 19 | with: 20 | spark-formatter: false 21 | spark-cheat-sheets: false 22 | reuse: true 23 | igniter-upgrade: false 24 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | --- 6 | updates: 7 | - directory: / 8 | groups: 9 | dev-dependencies: 10 | dependency-type: development 11 | production-dependencies: 12 | dependency-type: production 13 | package-ecosystem: mix 14 | schedule: 15 | interval: monthly 16 | versioning-strategy: lockfile-only 17 | - directory: / 18 | groups: 19 | github-actions: 20 | applies-to: version-updates 21 | patterns: 22 | - '*' 23 | package-ecosystem: github-actions 24 | schedule: 25 | interval: monthly 26 | version: 2 27 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | import Config 6 | 7 | if Mix.env() == :dev do 8 | config :git_ops, 9 | mix_project: SimpleSat.MixProject, 10 | changelog_file: "CHANGELOG.md", 11 | repository_url: "https://github.com/ash-project/simple_sat", 12 | # Instructs the tool to manage your mix version in your `mix.exs` file 13 | # See below for more information 14 | manage_mix_version?: true, 15 | # Instructs the tool to manage the version in your README.md 16 | # Pass in `true` to use `"README.md"` or a string to customize 17 | manage_readme_version: ["README.md"], 18 | version_tag_prefix: "v" 19 | end 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # The directory Mix will write compiled artifacts to. 6 | /_build/ 7 | 8 | # If you run "mix test --cover", coverage assets end up here. 9 | /cover/ 10 | 11 | # The directory Mix downloads your dependencies sources to. 12 | /deps/ 13 | 14 | # Where third-party dependencies like ExDoc output generated docs. 15 | /doc/ 16 | 17 | # Ignore .fetch files in case you like to edit your project deps locally. 18 | /.fetch 19 | 20 | # If the VM crashes, it generates a dump, let's ignore it too. 21 | erl_crash.dump 22 | 23 | # Also ignore archive artifacts (built via "mix archive.build"). 24 | *.ez 25 | 26 | # Ignore package tarball (built via "mix hex.build"). 27 | simple_sat-*.tar 28 | 29 | # Temporary files, for example, from tests. 30 | /tmp/ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | ![Elixir CI](https://github.com/ash-project/simple_sat/workflows/CI/badge.svg) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 9 | [![Hex version badge](https://img.shields.io/hexpm/v/simple_sat.svg)](https://hex.pm/packages/simple_sat) 10 | [![Hexdocs badge](https://img.shields.io/badge/docs-hexdocs-purple)](https://hexdocs.pm/simple_sat) 11 | [![REUSE status](https://api.reuse.software/badge/github.com/ash-project/simple_sat)](https://api.reuse.software/info/github.com/ash-project/simple_sat) 12 | 13 | 14 | # SimpleSat 15 | 16 | A simple, dependency free boolean satisfiability solver. 17 | 18 | See the [documentation](https://hexdocs.pm/simple_sat) for more. 19 | 20 | ## Installation 21 | 22 | ```elixir 23 | def deps do 24 | [ 25 | {:simple_sat, "~> 0.1.4"} 26 | ] 27 | end 28 | ``` 29 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 15 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 16 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 18 | USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Change Log 8 | 9 | All notable changes to this project will be documented in this file. 10 | See [Conventional Commits](Https://conventionalcommits.org) for commit guidelines. 11 | 12 | 13 | 14 | ## [v0.1.4](https://github.com/ash-project/simple_sat/compare/v0.1.3...v0.1.4) (2025-10-15) 15 | 16 | 17 | 18 | 19 | ### Bug Fixes: 20 | 21 | * Fix Sat Result with Backtrack (#10) by Jonatan Männchen 22 | 23 | ## [v0.1.3](https://github.com/ash-project/simple_sat/compare/v0.1.2...v0.1.3) (2024-04-06) 24 | 25 | 26 | 27 | 28 | ### Bug Fixes: 29 | 30 | * lower elixir version requirement 31 | 32 | ## [v0.1.2](https://github.com/ash-project/simple_sat/compare/v0.1.1...v0.1.2) (2024-04-05) 33 | 34 | 35 | 36 | 37 | ### Bug Fixes: 38 | 39 | * fix logic bug for detecting truth when variable is false 40 | 41 | ## [v0.1.1](https://github.com/ash-project/simple_sat/compare/v0.1.0...v0.1.1) (2024-03-26) 42 | 43 | 44 | 45 | 46 | ### Bug Fixes: 47 | 48 | * ensure variables come out sorted, as Ash expects that and it won't hurt those that don't 49 | 50 | ## [v0.1.0](https://github.com/ash-project/simple_sat/compare/v0.1.0...v0.1.0) (2024-02-24) 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule SimpleSat.MixProject do 6 | use Mix.Project 7 | 8 | @description "A simple, dependency free boolean satisfiability solver." 9 | @version "0.1.4" 10 | 11 | def project do 12 | [ 13 | app: :simple_sat, 14 | version: @version, 15 | elixir: "~> 1.13", 16 | start_permanent: Mix.env() == :prod, 17 | description: @description, 18 | source_url: "https://github.com/ash-project/simple_sat", 19 | homepage_url: "https://github.com/ash-project/simple_sat", 20 | deps: deps(), 21 | docs: docs(), 22 | package: package() 23 | ] 24 | end 25 | 26 | defp docs() do 27 | [ 28 | before_closing_head_tag: fn type -> 29 | if type == :html do 30 | """ 31 | 40 | """ 41 | end 42 | end 43 | ] 44 | end 45 | 46 | defp package do 47 | [ 48 | maintainers: [ 49 | "Zach Daniel " 50 | ], 51 | licenses: ["MIT"], 52 | files: ~w(lib .formatter.exs mix.exs README* LICENSE* CHANGELOG*), 53 | links: %{ 54 | "GitHub" => "https://github.com/ash-project/simple_sat", 55 | "Changelog" => "https://github.com/ash-project/simple_sat/blob/main/CHANGELOG.md", 56 | "Discord" => "https://discord.gg/HTHRaaVPUc", 57 | "Website" => "https://ash-hq.org", 58 | "Forum" => "https://elixirforum.com/c/elixir-framework-forums/ash-framework-forum", 59 | "REUSE Compliance" => "https://api.reuse.software/info/github.com/ash-project/simple_sat" 60 | } 61 | ] 62 | end 63 | 64 | # Run "mix help compile.app" to learn about applications. 65 | def application do 66 | [ 67 | extra_applications: [:logger] 68 | ] 69 | end 70 | 71 | # Run "mix help deps" to learn about dependencies. 72 | defp deps do 73 | [ 74 | # Dev/Test dependencies 75 | {:ex_doc, github: "elixir-lang/ex_doc", only: [:dev, :test], runtime: false}, 76 | {:ex_check, "~> 0.12", only: [:dev, :test]}, 77 | {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false}, 78 | {:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false}, 79 | {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}, 80 | {:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false}, 81 | {:git_ops, "~> 2.5", only: [:dev, :test]}, 82 | {:doctor, "~> 0.21", only: [:dev, :test]}, 83 | {:picosat_elixir, "~> 0.2", only: [:dev, :test]}, 84 | {:stream_data, "~> 1.2", only: [:dev, :test]} 85 | ] 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/simple_sat_test.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule SimpleSatTest do 6 | use ExUnit.Case 7 | use ExUnitProperties 8 | 9 | doctest SimpleSat 10 | 11 | test "solves a single variable statement" do 12 | assert {:ok, [1]} = SimpleSat.solve([[1]]) 13 | end 14 | 15 | test "solves an obvious negation" do 16 | assert {:error, :unsatisfiable} = SimpleSat.solve([[1], [-1]]) 17 | end 18 | 19 | test "solves a less obvious negation" do 20 | assert {:error, :unsatisfiable} = SimpleSat.solve([[1, 2], [-1, -2], [1], [2]]) 21 | end 22 | 23 | test "solves for three variables" do 24 | assert {:ok, [2, 3]} = SimpleSat.solve([[1, 3], [2], [1, -2, 3]]) 25 | end 26 | 27 | test "solves for many variables" do 28 | assert {:ok, [-1, 2, -3, -4, -5, 6, 7, -8]} = 29 | SimpleSat.solve([[7], [-8], [6], [-5], [-4], [-3], [2], [-1]]) 30 | end 31 | 32 | @tag :regression 33 | test "solves this properly" do 34 | assert {:ok, [1, 2, 3, 4, -5, -6]} = 35 | SimpleSat.solve([[1], [-6], [-5], [4], [3], [1, 2]]) 36 | 37 | assert {:ok, [-1, 2, -3, 4, -5]} = 38 | SimpleSat.solve([[2], [-3], [4, 5], [-4, -1], [-5, -2], [-5, -3]]) 39 | end 40 | 41 | test "solves this crazy example" do 42 | SimpleSat.solve([ 43 | [1], 44 | [-3], 45 | [-7], 46 | [6], 47 | [-5], 48 | [-4], 49 | [3, 2], 50 | [1, 2], 51 | [-7, -6, 5, 4, 3, -1, -2] 52 | ]) 53 | end 54 | 55 | test "solves https://github.com/ash-project/simple_sat/issues/2" do 56 | problem = [ 57 | [-3, 36, 7], 58 | [-3, -42, -48], 59 | [-49, -47, -41], 60 | [8, -40, 17], 61 | [-21, -31, -39], 62 | [36, -22, 49], 63 | [27, 38, 14], 64 | [15, -18, 6], 65 | [6, 7, -43], 66 | [34, -7, 23], 67 | [2, 14, -13], 68 | [2, 47, -42], 69 | [-33, -35, 3], 70 | [44, 40, 49], 71 | [50, 36, 31], 72 | [-36, -3, -37], 73 | [26, -29, 43], 74 | [15, 29, -45], 75 | [24, -11, 18], 76 | [-47, -26, 6], 77 | [-50, -33, -10], 78 | [32, 6, 16], 79 | [-34, 37, 41], 80 | [7, -28, -17], 81 | [-44, 46, 19], 82 | [7, 22, -48], 83 | [3, 39, 34], 84 | [31, 46, -43], 85 | [-27, 32, 23], 86 | [37, -50, -18], 87 | [20, 5, 11], 88 | [-45, -24, 6], 89 | [-34, -23, -14], 90 | [-22, 21, 20], 91 | [-17, 50, 24], 92 | [-25, -24, -27], 93 | [3, 35, 21], 94 | [-26, 47, -36], 95 | [-28, -45, 49], 96 | [-21, -6, 12], 97 | [-17, -15, -39], 98 | [41, 2, -14], 99 | [25, 36, -23], 100 | [-39, -3, -40], 101 | [50, 20, 35], 102 | [27, 31, -39], 103 | [45, -15, -40], 104 | [34, 50, 35], 105 | [-1, -48, 12], 106 | [18, -35, -30], 107 | [27, -24, -25], 108 | [-4, -33, -12], 109 | [-43, -24, -37], 110 | [-37, 31, -44], 111 | [-9, -38, 14], 112 | [33, -16, 34], 113 | [4, -35, -5], 114 | [-3, -21, -19], 115 | [-35, -36, -29], 116 | [7, -43, 36], 117 | [30, 14, 41], 118 | [-35, -24, -7], 119 | [35, -42, 6], 120 | [-1, -15, 39], 121 | [27, 49, -16], 122 | [-37, 49, -10], 123 | [50, -46, -3], 124 | [-41, 20, 34], 125 | [-1, 23, 28], 126 | [-12, -30, -20], 127 | [-24, 29, -37], 128 | [12, 5, -44], 129 | [-6, -2, 48], 130 | [-2, -49, -43], 131 | [1, -50, 24], 132 | [-7, -50, -44], 133 | [-41, 43, 4], 134 | [13, 15, -11], 135 | [-3, -11, 23], 136 | [33, 48, 41] 137 | ] 138 | 139 | assert {:ok, solution} = 140 | SimpleSat.solve(problem) 141 | 142 | assert {:ok, _result} = Picosat.solve([solution | problem]) 143 | end 144 | 145 | property "gives same unsat / sat status as Picosat" do 146 | variable = StreamData.filter(StreamData.integer(-5..5), &(&1 != 0)) 147 | 148 | check all( 149 | statements <- 150 | list_of(list_of(variable, min_length: 1, max_length: 10), 151 | min_length: 0, 152 | max_length: 10 153 | ), 154 | max_runs: 100_000 155 | ) do 156 | case SimpleSat.solve(statements) do 157 | {:ok, []} -> 158 | assert {:ok, []} = Picosat.solve(statements) 159 | 160 | {:ok, solution} -> 161 | assert {:ok, _} = Picosat.solve(statements ++ [solution]) 162 | 163 | {:error, :unsatisfiable} -> 164 | assert {:error, :unsatisfiable} = Picosat.solve(statements) 165 | end 166 | end 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /lib/simple_sat.ex: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | defmodule SimpleSat do 6 | @moduledoc """ 7 | A simple, dependency free boolean satisfiability solver. 8 | 9 | This was created for folks having trouble running `picosat_elixir` 10 | to use as a drop-in replacement for Ash Framework, but feel free to use it in 11 | any way you see fit. 12 | 13 | SimpleSat expects to receive a list of lists of integers, where each integer 14 | represents a variable. If the integer is negative, it represents the negation 15 | of said variable. 16 | 17 | Additionally, SimpleSat expects to receive variables in "Conjunctive Normal Form" or CNF. 18 | This is the same interface that `picosat_elixir` expects. CNF is a conjunction of disjunctions. 19 | In other words, it is a list of statements that must *all be true*, where each statement is a list of 20 | variables where at *least one must be true*. For example: 21 | 22 | ```elixir 23 | # A AND (NOT A) 24 | [[1], [-1]] 25 | 26 | # A AND (A OR B) 27 | [[1], [1, 2]] 28 | 29 | # A AND B AND C 30 | [[1], [2], [3]] 31 | ``` 32 | """ 33 | 34 | @doc """ 35 | Provide a valid solution for a statement in CNF form 36 | """ 37 | @spec solve([[integer]]) :: {:ok, [integer]} | {:error, :unsatisfiable} 38 | def solve(clauses) 39 | def solve([]), do: {:ok, []} 40 | 41 | def solve(cnf_integer) do 42 | {statements, all_vars} = 43 | cnf_integer 44 | |> Enum.reduce({[], []}, fn statement, {statements, all_vars} -> 45 | {statement, all_vars} = 46 | Enum.reduce(statement, {[], all_vars}, fn var, {statement, all_vars} -> 47 | {[variable(var) | statement], [abs(var) | all_vars]} 48 | end) 49 | 50 | {[statement | statements], all_vars} 51 | end) 52 | 53 | all_vars = Enum.uniq(all_vars) 54 | 55 | Enum.find_value(all_vars, {:error, :unsatisfiable}, fn var -> 56 | if solution = find_solution(var, all_vars -- [var], statements) do 57 | {:ok, Enum.sort_by(solution, &abs/1)} 58 | end 59 | end) 60 | end 61 | 62 | defp find_solution(var, other_vars, statements, trail \\ []) do 63 | # --- Try var = true 64 | case simplify(guess_value(statements, var, true)) do 65 | true -> 66 | [var | trail] 67 | 68 | false -> 69 | # fall through and try var = false 70 | try_false(var, other_vars, statements, trail) 71 | 72 | simp_true -> 73 | case branch(other_vars, simp_true, [var | trail]) do 74 | # <— BACKTRACK 75 | nil -> try_false(var, other_vars, statements, trail) 76 | sol -> sol 77 | end 78 | end 79 | end 80 | 81 | defp try_false(var, other_vars, statements, trail) do 82 | case simplify(guess_value(statements, var, false)) do 83 | true -> 84 | [-var | trail] 85 | 86 | false -> 87 | nil 88 | 89 | simp_false -> 90 | branch(other_vars, simp_false, [-var | trail]) 91 | end 92 | end 93 | 94 | defp branch([], _simp, _trail), do: nil 95 | 96 | defp branch([next | rest], simp, trail), 97 | do: find_solution(next, rest, simp, trail) 98 | 99 | defp simplify(statements) do 100 | statements 101 | |> Enum.map(fn statement -> 102 | simplify_statement(statement) 103 | end) 104 | |> case do 105 | [] -> 106 | false 107 | 108 | statements -> 109 | cond do 110 | Enum.any?(statements, &(&1 == false)) -> 111 | false 112 | 113 | Enum.all?(statements, &(&1 == true)) -> 114 | true 115 | 116 | true -> 117 | statements 118 | end 119 | end 120 | end 121 | 122 | defp simplify_statement(true) do 123 | true 124 | end 125 | 126 | defp simplify_statement(statement) do 127 | if Enum.any?(statement, &true?/1) do 128 | true 129 | else 130 | if Enum.all?(statement, &false?/1) do 131 | false 132 | else 133 | Enum.reject(statement, &false?/1) 134 | end 135 | end 136 | end 137 | 138 | defp true?({:const, true}), do: true 139 | defp true?(_), do: false 140 | 141 | defp false?({:const, false}), do: true 142 | defp false?(_), do: false 143 | 144 | defp guess_value(statements, var, value) do 145 | var = 146 | if value == true do 147 | var 148 | else 149 | -var 150 | end 151 | 152 | Enum.map(statements, fn 153 | true -> 154 | true 155 | 156 | statement -> 157 | Enum.map(statement, fn 158 | {:variable, s_var} -> 159 | cond do 160 | s_var == var -> 161 | {:const, true} 162 | 163 | s_var == -var -> 164 | {:const, false} 165 | 166 | true -> 167 | {:variable, s_var} 168 | end 169 | 170 | other -> 171 | other 172 | end) 173 | end) 174 | end 175 | 176 | defp variable(var), do: {:variable, var} 177 | end 178 | -------------------------------------------------------------------------------- /.credo.exs: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 simple_sat contributors 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # This file contains the configuration for Credo and you are probably reading 6 | # this after creating it with `mix credo.gen.config`. 7 | # 8 | # If you find anything wrong or unclear in this file, please report an 9 | # issue on GitHub: https://github.com/rrrene/credo/issues 10 | # 11 | %{ 12 | # 13 | # You can have as many configs as you like in the `configs:` field. 14 | configs: [ 15 | %{ 16 | # 17 | # Run any config using `mix credo -C `. If no config name is given 18 | # "default" is used. 19 | # 20 | name: "default", 21 | # 22 | # These are the files included in the analysis: 23 | files: %{ 24 | # 25 | # You can give explicit globs or simply directories. 26 | # In the latter case `**/*.{ex,exs}` will be used. 27 | # 28 | included: [ 29 | "lib/", 30 | "src/", 31 | "test/", 32 | "web/", 33 | "apps/*/lib/", 34 | "apps/*/src/", 35 | "apps/*/test/", 36 | "apps/*/web/" 37 | ], 38 | excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] 39 | }, 40 | # 41 | # Load and configure plugins here: 42 | # 43 | plugins: [], 44 | # 45 | # If you create your own checks, you must specify the source files for 46 | # them here, so they can be loaded by Credo before running the analysis. 47 | # 48 | requires: [], 49 | # 50 | # If you want to enforce a style guide and need a more traditional linting 51 | # experience, you can change `strict` to `true` below: 52 | # 53 | strict: false, 54 | # 55 | # To modify the timeout for parsing files, change this value: 56 | # 57 | parse_timeout: 5000, 58 | # 59 | # If you want to use uncolored output by default, you can change `color` 60 | # to `false` below: 61 | # 62 | color: true, 63 | # 64 | # You can customize the parameters of any check by adding a second element 65 | # to the tuple. 66 | # 67 | # To disable a check put `false` as second element: 68 | # 69 | # {Credo.Check.Design.DuplicatedCode, false} 70 | # 71 | checks: [ 72 | # 73 | ## Consistency Checks 74 | # 75 | {Credo.Check.Consistency.ExceptionNames, []}, 76 | {Credo.Check.Consistency.LineEndings, []}, 77 | {Credo.Check.Consistency.ParameterPatternMatching, []}, 78 | # This check was erroring on sigils so I had to disable it 79 | {Credo.Check.Consistency.SpaceAroundOperators, false}, 80 | {Credo.Check.Consistency.SpaceInParentheses, []}, 81 | {Credo.Check.Consistency.TabsOrSpaces, []}, 82 | 83 | 84 | # 85 | ## Design Checks 86 | # 87 | # You can customize the priority of any check 88 | # Priority values are: `low, normal, high, higher` 89 | # 90 | {Credo.Check.Design.AliasUsage, false}, 91 | # You can also customize the exit_status of each check. 92 | # If you don't want TODO comments to cause `mix credo` to fail, just 93 | # set this value to 0 (zero). 94 | # 95 | {Credo.Check.Design.TagTODO, false}, 96 | {Credo.Check.Design.TagFIXME, []}, 97 | 98 | # 99 | ## Readability Checks 100 | # 101 | {Credo.Check.Readability.AliasOrder, []}, 102 | {Credo.Check.Readability.FunctionNames, []}, 103 | {Credo.Check.Readability.LargeNumbers, []}, 104 | {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, 105 | {Credo.Check.Readability.ModuleAttributeNames, []}, 106 | {Credo.Check.Readability.ModuleDoc, []}, 107 | {Credo.Check.Readability.ModuleNames, []}, 108 | {Credo.Check.Readability.ParenthesesInCondition, false}, 109 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, 110 | {Credo.Check.Readability.PredicateFunctionNames, false}, 111 | {Credo.Check.Readability.PreferImplicitTry, []}, 112 | {Credo.Check.Readability.RedundantBlankLines, []}, 113 | {Credo.Check.Readability.Semicolons, []}, 114 | {Credo.Check.Readability.SpaceAfterCommas, []}, 115 | {Credo.Check.Readability.StringSigils, []}, 116 | {Credo.Check.Readability.TrailingBlankLine, []}, 117 | {Credo.Check.Readability.TrailingWhiteSpace, []}, 118 | {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, 119 | {Credo.Check.Readability.VariableNames, []}, 120 | 121 | # 122 | ## Refactoring Opportunities 123 | # 124 | {Credo.Check.Refactor.CondStatements, []}, 125 | {Credo.Check.Refactor.CyclomaticComplexity, false}, 126 | {Credo.Check.Refactor.FunctionArity, false}, 127 | {Credo.Check.Refactor.LongQuoteBlocks, false}, 128 | {Credo.Check.Refactor.MapInto, false}, 129 | {Credo.Check.Refactor.MatchInCondition, false}, 130 | {Credo.Check.Refactor.NegatedConditionsInUnless, []}, 131 | {Credo.Check.Refactor.NegatedConditionsWithElse, []}, 132 | {Credo.Check.Refactor.Nesting, [max_nesting: 10]}, 133 | {Credo.Check.Refactor.UnlessWithElse, []}, 134 | {Credo.Check.Refactor.WithClauses, []}, 135 | 136 | # 137 | ## Warnings 138 | # 139 | {Credo.Check.Warning.BoolOperationOnSameValues, []}, 140 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, 141 | {Credo.Check.Warning.IExPry, []}, 142 | {Credo.Check.Warning.IoInspect, []}, 143 | {Credo.Check.Warning.LazyLogging, false}, 144 | {Credo.Check.Warning.MixEnv, false}, 145 | {Credo.Check.Warning.OperationOnSameValues, []}, 146 | {Credo.Check.Warning.OperationWithConstantResult, []}, 147 | {Credo.Check.Warning.RaiseInsideRescue, []}, 148 | {Credo.Check.Warning.UnusedEnumOperation, []}, 149 | {Credo.Check.Warning.UnusedFileOperation, []}, 150 | {Credo.Check.Warning.UnusedKeywordOperation, []}, 151 | {Credo.Check.Warning.UnusedListOperation, []}, 152 | {Credo.Check.Warning.UnusedPathOperation, []}, 153 | {Credo.Check.Warning.UnusedRegexOperation, []}, 154 | {Credo.Check.Warning.UnusedStringOperation, []}, 155 | {Credo.Check.Warning.UnusedTupleOperation, []}, 156 | {Credo.Check.Warning.UnsafeExec, []}, 157 | 158 | # 159 | # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) 160 | 161 | # 162 | # Controversial and experimental checks (opt-in, just replace `false` with `[]`) 163 | # 164 | # {Credo.Check.Readability.StrictModuleLayout, 165 | # order: [:shortdoc, :moduledoc, :behaviour, :use, :defstruct, :type, :import, :alias, :require], 166 | # ignore: [:module_attribute, :type]}, 167 | {Credo.Check.Readability.StrictModuleLayout, false}, 168 | {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, 169 | {Credo.Check.Consistency.UnusedVariableNames, false}, 170 | {Credo.Check.Design.DuplicatedCode, false}, 171 | {Credo.Check.Readability.AliasAs, false}, 172 | {Credo.Check.Readability.MultiAlias, false}, 173 | {Credo.Check.Readability.Specs, false}, 174 | {Credo.Check.Readability.SinglePipe, false}, 175 | {Credo.Check.Readability.WithCustomTaggedTuple, false}, 176 | {Credo.Check.Refactor.ABCSize, false}, 177 | {Credo.Check.Refactor.Apply, false}, 178 | {Credo.Check.Refactor.AppendSingleItem, false}, 179 | {Credo.Check.Refactor.DoubleBooleanNegation, false}, 180 | {Credo.Check.Refactor.ModuleDependencies, false}, 181 | {Credo.Check.Refactor.NegatedIsNil, false}, 182 | {Credo.Check.Refactor.PipeChainStart, false}, 183 | {Credo.Check.Refactor.VariableRebinding, false}, 184 | {Credo.Check.Warning.LeakyEnvironment, false}, 185 | {Credo.Check.Warning.MapGetUnsafePass, false}, 186 | {Credo.Check.Warning.UnsafeToAtom, false} 187 | 188 | # 189 | # Custom checks can be created using `mix credo.gen.check`. 190 | # 191 | ] 192 | } 193 | ] 194 | } 195 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 3 | "credo": {:hex, :credo, "1.7.14", "c7e75216cea8d978ba8c60ed9dede4cc79a1c99a266c34b3600dd2c33b96bc92", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "12a97d6bb98c277e4fb1dff45aaf5c137287416009d214fb46e68147bd9e0203"}, 4 | "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, 5 | "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, 6 | "doctor": {:hex, :doctor, "0.22.0", "223e1cace1f16a38eda4113a5c435fa9b10d804aa72d3d9f9a71c471cc958fe7", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "96e22cf8c0df2e9777dc55ebaa5798329b9028889c4023fed3305688d902cd5b"}, 7 | "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, 8 | "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, 9 | "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, 10 | "ex_check": {:hex, :ex_check, "0.16.0", "07615bef493c5b8d12d5119de3914274277299c6483989e52b0f6b8358a26b5f", [:mix], [], "hexpm", "4d809b72a18d405514dda4809257d8e665ae7cf37a7aee3be6b74a34dec310f5"}, 11 | "ex_doc": {:git, "https://github.com/elixir-lang/ex_doc.git", "72bb755a90c9a98b645e7e923ebd3bb85ead8f32", []}, 12 | "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, 13 | "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, 14 | "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, 15 | "git_ops": {:hex, :git_ops, "2.9.0", "b74f6040084f523055b720cc7ef718da47f2cbe726a5f30c2871118635cb91c1", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:igniter, ">= 0.5.27 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "7fdf84be3490e5692c5dc1f8a1084eed47a221c1063e41938c73312f0bfea259"}, 16 | "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, 17 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 18 | "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, 19 | "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, 20 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, 21 | "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, 22 | "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, 23 | "mix_audit": {:hex, :mix_audit, "2.1.5", "c0f77cee6b4ef9d97e37772359a187a166c7a1e0e08b50edf5bf6959dfe5a016", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "87f9298e21da32f697af535475860dc1d3617a010e0b418d2ec6142bc8b42d69"}, 24 | "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, 25 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, 26 | "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, 27 | "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, 28 | "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, 29 | "sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"}, 30 | "stream_data": {:hex, :stream_data, "1.2.0", "58dd3f9e88afe27dc38bef26fce0c84a9e7a96772b2925c7b32cd2435697a52b", [:mix], [], "hexpm", "eb5c546ee3466920314643edf68943a5b14b32d1da9fe01698dc92b73f89a9ed"}, 31 | "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, 32 | "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, 33 | "yaml_elixir": {:hex, :yaml_elixir, "2.12.0", "30343ff5018637a64b1b7de1ed2a3ca03bc641410c1f311a4dbdc1ffbbf449c7", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "ca6bacae7bac917a7155dca0ab6149088aa7bc800c94d0fe18c5238f53b313c6"}, 34 | } 35 | --------------------------------------------------------------------------------