Skip to content

OriPekelman/swiex

Repository files navigation

Swiex - Multi-Adapter Prolog Integration for Elixir

Swiex is an Elixir library that provides seamless integration with multiple Prolog implementations, including SWI-Prolog (via MQI), Erlog, and Scryer Prolog. Execute Prolog queries, define inline logic, and leverage constraint solving directly from your Elixir applications.

Version 0.3.0: Now with multi-adapter architecture, monitoring capabilities, and a comprehensive Phoenix demo application showcasing real-world Prolog integration!

Features

Core Capabilities

  • Multi-Adapter Architecture - Support for SWI-Prolog, Erlog, and Scryer Prolog
  • Full MQI Protocol Support - Complete implementation of SWI-Prolog's Machine Query Interface
  • Persistent Sessions - Maintain stateful connections for complex workflows
  • Inline Prolog Code - Define facts and rules on-the-fly using assertz/1
  • Variable Binding Extraction - Clean map-based results with variable names
  • Hybrid DSL Support - Choose between Elixir DSL syntax and raw Prolog code
  • Pin Operator Support - Variable interpolation in DSL queries using ^ operator
  • Streaming Results - Handle large datasets efficiently with configurable chunk sizes
  • Transaction Support - Atomic operations with automatic session management
  • Security Features - Query validation and sanitization to prevent injection attacks

New in v0.3.0

  • Adapter Abstraction - Unified interface for multiple Prolog implementations
  • Performance Monitoring - Real-time query statistics and session health tracking
  • Phoenix Demo Application - Full-featured web app demonstrating:
    • Medical causal reasoning with CauseNet data
    • Constraint solving (N-Queens with all 92 solutions, Sudoku)
    • Interactive Prolog playground
    • Multi-adapter comparison interface
    • Real-time monitoring dashboard
  • Enhanced Testing - Comprehensive test suite with proper concurrent test isolation

Status: Pre-Alpha forever

This is a vibe-coded toy implementation built for weird reasons. I expect the performance to be horrendous; security to be inexistant; and the potential to summon evil spirits from another century to be very high.

Installation

Add swiex to your list of dependencies in mix.exs:

def deps do
  [
    {:swiex, "~> 0.3.0"}
    # Optional: Add for Erlog support
    # {:erlog, github: "rvirding/erlog", branch: "develop", optional: true}
  ]
end

Prerequisites

  • SWI-Prolog must be installed and available in your PATH
  • Test with: swipl --version

Possily we also get to have:

brew install scryer-prolog

Quick Start

Basic Query

# Simple one-shot query
case Swiex.MQI.query("member(X, [1,2,3])") do
  {:ok, results} ->
    IO.inspect(results)
    # Returns: [%{"X" => 1}, %{"X" => 2}, %{"X" => 3}]
  {:error, reason} ->
    IO.puts("Error: #{reason}")
end

Persistent Sessions with Inline Code

# Start a persistent session
{:ok, session} = Swiex.MQI.start_session()

# Define Prolog predicates inline
Swiex.MQI.assertz(session, "factorial(0, 1).")
Swiex.MQI.assertz(session, "factorial(N, Result) :- N > 0, N1 is N - 1, factorial(N1, F1), Result is N * F1.")

# Query the defined predicates
case Swiex.MQI.query(session, "factorial(5, Result)") do
  {:ok, [%{"Result" => result}]} ->
    IO.puts("Factorial of 5 = #{result}")  # Prints: Factorial of 5 = 120
  {:error, reason} ->
    IO.puts("Error: #{reason}")
end

# Clean up
Swiex.MQI.stop_session(session)

Domain Specific Language (DSL)

Swiex provides a powerful DSL that gives you two approaches for working with Prolog:

Elixir DSL (Natural Syntax)

import Swiex.DSL

# Simple, natural syntax
all(member(X, [1,2,3]))           # Get all solutions
one(factorial(5, Result))         # Get first solution
solutions(member(X, [1,2,3]), 2)  # Get limited solutions
query(factorial(5, Result))       # Execute query

Inline Prolog (Raw Prolog Code)

import Swiex.DSL

# Define Prolog predicates inline
prolog do
  """
  member(X, [X|_]).
  member(X, [_|T]) :- member(X, T).
  
  append([], L, L).
  append([H|T], L, [H|R]) :- append(T, L, R).
  """
end

# Execute raw Prolog queries
query_prolog("member(X, [1,2,3]), X > 2")
query_prolog_one("member(X, [1,2,3,4,5]), X > 3")
query_prolog_solutions("member(X, [1,2,3,4,5]), X > 2", 2)

Mixed Approach

# Use inline Prolog for complex predicate definition
Swiex.MQI.consult_string("""
  filter_positive([], []).
  filter_positive([H|T], [H|R]) :- H > 0, filter_positive(T, R).
  filter_positive([H|T], R) :- H =< 0, filter_positive(T, R).
""")

# Use Elixir DSL for simple queries
all(filter_positive([-1, 2, -3, 4, -5], Result))

# Use inline Prolog for complex queries
query_prolog("""
  filter_positive([-1, 2, -3, 4, -5], PosList),
  sum_list(PosList, Sum),
  Sum > 5
""")

See examples/hybrid_dsl_usage.exs for comprehensive examples.

API Reference

One-Shot Queries

# Execute a Prolog query
Swiex.MQI.query(prolog_query, opts \\ [])

# Options:
# - :host - Server host (default: {127, 0, 0, 1})
# - :timeout - Connection timeout in milliseconds (default: 5000)

Session Management

# Start a persistent session
{:ok, session} = Swiex.MQI.start_session(opts \\ [])

# Stop a session
Swiex.MQI.stop_session(session)

Session-Based Operations

# Query within a session
Swiex.MQI.query(session, prolog_query)

# Assert a Prolog clause
Swiex.MQI.assertz(session, "predicate(X, Y) :- condition(X), action(Y).")

# Consult Prolog code from a string
Swiex.MQI.consult_string(session, """
  factorial(0, 1).
  factorial(N, Result) :-
    N > 0,
    N1 is N - 1,
    factorial(N1, F1),
    Result is N * F1.
""")

Utility Functions

# Check if MQI server is available
Swiex.MQI.ping(opts \\ [])

# Execute async query (placeholder implementation)
{:ok, query_id} = Swiex.MQI.query_async(prolog_query)

Examples

DSL Usage

# Hybrid DSL examples (Elixir DSL + Inline Prolog)
mix run examples/hybrid_dsl_usage.exs

# Basic DSL examples
mix run examples/dsl_usage.exs

# Advanced features demo (Pin Operator, Streaming, Transactions)
mix run examples/advanced_features_demo.exs

List Processing

{:ok, session} = Swiex.MQI.start_session()

# Define a list processing predicate
Swiex.MQI.assertz(session, "double_list([], []).")
Swiex.MQI.assertz(session, "double_list([H|T], [H2|T2]) :- H2 is H * 2, double_list(T, T2).")

# Use it
case Swiex.MQI.query(session, "double_list([1,2,3], Result)") do
  {:ok, [%{"Result" => result}]} ->
    IO.inspect(result)  # [2, 4, 6]
end

Swiex.MQI.stop_session(session)

Complex Logic

{:ok, session} = Swiex.MQI.start_session()

# Define a family tree
Swiex.MQI.assertz(session, "parent(john, mary).")
Swiex.MQI.assertz(session, "parent(mary, bob).")
Swiex.MQI.assertz(session, "ancestor(X, Y) :- parent(X, Y).")
Swiex.MQI.assertz(session, "ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).")

# Query relationships
case Swiex.MQI.query(session, "ancestor(john, X)") do
  {:ok, results} ->
    IO.inspect(results)  # [%{"X" => "mary"}, %{"X" => "bob"}]
end

Swiex.MQI.stop_session(session)

Phoenix Integration

See the examples/phoenix_demo/ directory for a complete Phoenix application example that demonstrates:

  • Web interface for Prolog queries
  • Real-time query execution
  • Session management in web context

Advanced Features

Pin Operator Support

The DSL supports variable interpolation using the pin operator (^):

import Swiex.DSL

# Define a query with variables
member_ast = {:member, [], [{:^, [], [{:X, [], nil}]}, [1, 2, 3, 4, 5]]}

# Execute with bindings
case query_with_bindings(member_ast, [X: 3]) do
  {:ok, results} ->
    IO.inspect(results)  # [%{}] (ground query returns true)
  {:error, reason} ->
    IO.puts("Error: #{reason}")
end

Streaming Results

Handle large datasets efficiently with streaming:

import Swiex.Stream

# Stream results with custom chunk size
stream = query_stream("member(X, [1,2,3,4,5])", 2)

# Process results as they arrive
results = stream
  |> Stream.map(fn result -> result["X"] end)
  |> Stream.filter(fn x -> rem(x, 2) == 0 end)  # Only even numbers
  |> Enum.to_list()

IO.inspect(results)  # [2, 4]

Transaction Support

Execute multiple operations atomically:

import Swiex.Transaction

# Simple transaction
result = transaction(fn session ->
  Swiex.MQI.assertz(session, "person(alice, 28)")
  Swiex.MQI.assertz(session, "person(david, 32)")
  {:ok, "Both persons added"}
end)

# Batch operations
operations = [
  {"person(john, 30)", :assertz},
  {"person(jane, 25)", :assertz},
  {"person(john, Age)", :query}
]

case batch(operations) do
  {:ok, results} ->
    IO.inspect(results)
  {:error, reason} ->
    IO.puts("Error: #{reason}")
end
  • Error handling and result display

Error Handling

The library returns {:error, reason} for various error conditions:

  • Connection errors - Network or authentication issues
  • Syntax errors - Invalid Prolog syntax
  • Existence errors - Undefined predicates
  • Protocol errors - MQI communication issues

Multi-Adapter Support

Swiex now supports multiple Prolog implementations through a unified adapter interface:

Available Adapters

  • SWI-Prolog (via MQI) - Full-featured, production-ready
  • Erlog - Embedded Erlang-based Prolog (experimental)
  • Scryer Prolog - ISO-compliant Prolog (experimental)

Using Different Adapters

# Use the default adapter (SWI-Prolog)
{:ok, results} = Swiex.Prolog.query("member(X, [1,2,3])")

# Specify a different adapter
{:ok, results} = Swiex.Prolog.query("member(X, [1,2,3])", adapter: Swiex.Adapters.ErlogAdapter)

# Start a session with a specific adapter
{:ok, session} = Swiex.Prolog.start_session(adapter: Swiex.Adapters.ScryerAdapter)

# Compare results across all adapters
results = Swiex.Prolog.query_all("member(X, [1,2,3])")

Phoenix Demo Application

The examples/phoenix_demo directory contains a comprehensive web application showcasing Swiex capabilities:

Features

  • Medical Causal Reasoning - Interactive exploration of cause-effect relationships using CauseNet data
  • Constraint Solving - N-Queens solver (returns all 92 solutions for 8-Queens), Sudoku solver with CLP(FD)
  • Prolog Playground - Browser-based REPL with syntax highlighting
  • Multi-Adapter Comparison - Side-by-side performance testing
  • Real-time Monitoring - Live statistics dashboard for SWI-Prolog MQI sessions

Running the Demo

cd examples/phoenix_demo
mix deps.get
mix phx.server
# Visit http://localhost:4000

Architecture

Core Architecture

Swiex uses a modular adapter-based architecture:

  1. Adapter Interface - Common behavior for all Prolog implementations
  2. Session Management - Persistent connections with automatic cleanup
  3. Monitoring - Performance tracking and statistics collection
  4. Security - Query validation and sanitization

SWI-Prolog MQI Protocol

For SWI-Prolog specifically:

  1. Server Management - Automatically starts swipl mqi --write_connection_values=true
  2. Connection Handling - TCP socket communication with proper framing
  3. Message Protocol - <length>.\n<message>.\n format as per MQI specification
  4. JSON Parsing - Extracts variable bindings from MQI JSON responses

Development

Running Examples

# Basic usage examples
mix run examples/basic_usage.exs

# Phoenix demo
cd examples/phoenix_demo
mix deps.get
mix phx.server

Testing

mix test

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

Acknowledgments

  • SWI-Prolog team for the MQI protocol
  • Elixir community for inspiration and tooling

About

Swiex is an Elixir library that provides a client for SWI-Prolog's Machine Query Interface (MQI), allowing you to execute Prolog queries and define inline Prolog code directly from Elixir applications.

Resources

Code of conduct

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages