Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Combinator Logic Playground</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.combinator { | |
transition: all 0.2s ease; | |
} | |
.combinator.dragging { | |
opacity: 0.5; | |
transform: scale(1.1); | |
} | |
.drop-zone { | |
min-height: 100px; | |
transition: all 0.2s ease; | |
} | |
.drop-zone.highlight { | |
background-color: rgba(147, 197, 253, 0.3); | |
border-color: #3b82f6; | |
} | |
.expression-item { | |
position: relative; | |
} | |
.expression-item:hover .remove-btn { | |
opacity: 1; | |
} | |
.remove-btn { | |
opacity: 0; | |
transition: opacity 0.2s; | |
position: absolute; | |
top: -8px; | |
right: -8px; | |
width: 20px; | |
height: 20px; | |
border-radius: 50%; | |
background-color: #ef4444; | |
color: white; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 10px; | |
cursor: pointer; | |
} | |
@keyframes pulse { | |
0%, 100% { transform: scale(1); } | |
50% { transform: scale(1.05); } | |
} | |
.reduction-step { | |
animation: pulse 0.5s ease; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="mb-8 text-center"> | |
<h1 class="text-3xl font-bold text-blue-600 mb-2">Combinator Logic Playground</h1> | |
<p class="text-gray-600">Drag and drop combinators to form expressions and reduce them</p> | |
</header> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<!-- Combinator Palette --> | |
<div class="bg-white rounded-lg shadow-md p-4"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-shapes mr-2 text-blue-500"></i> Combinators | |
</h2> | |
<div class="grid grid-cols-2 gap-3"> | |
<div class="combinator bg-blue-100 text-blue-800 py-3 px-4 rounded-lg cursor-move flex items-center justify-center font-mono font-bold" | |
draggable="true" data-type="S"> | |
<span>S</span> | |
</div> | |
<div class="combinator bg-green-100 text-green-800 py-3 px-4 rounded-lg cursor-move flex items-center justify-center font-mono font-bold" | |
draggable="true" data-type="K"> | |
<span>K</span> | |
</div> | |
<div class="combinator bg-purple-100 text-purple-800 py-3 px-4 rounded-lg cursor-move flex items-center justify-center font-mono font-bold" | |
draggable="true" data-type="I"> | |
<span>I</span> | |
</div> | |
<div class="combinator bg-yellow-100 text-yellow-800 py-3 px-4 rounded-lg cursor-move flex items-center justify-center font-mono font-bold" | |
draggable="true" data-type="B"> | |
<span>B</span> | |
</div> | |
<div class="combinator bg-red-100 text-red-800 py-3 px-4 rounded-lg cursor-move flex items-center justify-center font-mono font-bold" | |
draggable="true" data-type="C"> | |
<span>C</span> | |
</div> | |
<div class="combinator bg-indigo-100 text-indigo-800 py-3 px-4 rounded-lg cursor-move flex items-center justify-center font-mono font-bold" | |
draggable="true" data-type="W"> | |
<span>W</span> | |
</div> | |
</div> | |
<div class="mt-4"> | |
<div class="combinator bg-gray-200 text-gray-800 py-2 px-3 rounded-lg cursor-move flex items-center justify-center font-mono text-sm" | |
draggable="true" data-type="variable"> | |
<span>Variable (drag to edit)</span> | |
</div> | |
</div> | |
</div> | |
<!-- Workspace --> | |
<div class="bg-white rounded-lg shadow-md p-4"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-edit mr-2 text-blue-500"></i> Workspace | |
</h2> | |
<div id="expression-container" class="drop-zone border-2 border-dashed border-gray-300 rounded-lg p-4 mb-4 min-h-32"> | |
<p class="text-gray-400 text-center py-8" id="empty-message">Drag combinators here to build your expression</p> | |
<div class="flex flex-wrap gap-2" id="expression-items"></div> | |
</div> | |
<div class="flex flex-wrap gap-2"> | |
<button id="reduce-btn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center disabled:opacity-50" disabled> | |
<i class="fas fa-play mr-2"></i> Reduce | |
</button> | |
<button id="reset-btn" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-redo mr-2"></i> Reset | |
</button> | |
<button id="add-parens-btn" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg flex items-center"> | |
<i class="fas fa-parentheses mr-2"></i> Add Parentheses | |
</button> | |
</div> | |
</div> | |
<!-- Reduction Steps --> | |
<div class="bg-white rounded-lg shadow-md p-4"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-list-ol mr-2 text-blue-500"></i> Reduction Steps | |
</h2> | |
<div id="reduction-steps" class="space-y-2 max-h-96 overflow-y-auto p-2"> | |
<p class="text-gray-400 text-center py-8">Your reduction steps will appear here</p> | |
</div> | |
</div> | |
</div> | |
<!-- Help Section --> | |
<div class="mt-8 bg-white rounded-lg shadow-md p-4"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> | |
<i class="fas fa-question-circle mr-2 text-blue-500"></i> Combinator Definitions | |
</h2> | |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> | |
<div class="bg-blue-50 p-3 rounded-lg"> | |
<h3 class="font-mono font-bold text-blue-800">S x y z = x z (y z)</h3> | |
<p class="text-gray-600 text-sm">Substitution combinator</p> | |
</div> | |
<div class="bg-green-50 p-3 rounded-lg"> | |
<h3 class="font-mono font-bold text-green-800">K x y = x</h3> | |
<p class="text-gray-600 text-sm">Constant combinator</p> | |
</div> | |
<div class="bg-purple-50 p-3 rounded-lg"> | |
<h3 class="font-mono font-bold text-purple-800">I x = x</h3> | |
<p class="text-gray-600 text-sm">Identity combinator</p> | |
</div> | |
<div class="bg-yellow-50 p-3 rounded-lg"> | |
<h3 class="font-mono font-bold text-yellow-800">B x y z = x (y z)</h3> | |
<p class="text-gray-600 text-sm">Composition combinator</p> | |
</div> | |
<div class="bg-red-50 p-3 rounded-lg"> | |
<h3 class="font-mono font-bold text-red-800">C x y z = x z y</h3> | |
<p class="text-gray-600 text-sm">Swap combinator</p> | |
</div> | |
<div class="bg-indigo-50 p-3 rounded-lg"> | |
<h3 class="font-mono font-bold text-indigo-800">W x y = x y y</h3> | |
<p class="text-gray-600 text-sm">Duplication combinator</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM elements | |
const expressionContainer = document.getElementById('expression-container'); | |
const expressionItems = document.getElementById('expression-items'); | |
const emptyMessage = document.getElementById('empty-message'); | |
const reduceBtn = document.getElementById('reduce-btn'); | |
const resetBtn = document.getElementById('reset-btn'); | |
const addParensBtn = document.getElementById('add-parens-btn'); | |
const reductionSteps = document.getElementById('reduction-steps'); | |
// Store the current expression and reduction steps | |
let currentExpression = []; | |
let steps = []; | |
// Add drag events to all combinators | |
document.querySelectorAll('.combinator').forEach(combinator => { | |
combinator.addEventListener('dragstart', dragStart); | |
}); | |
// Setup drop zones | |
expressionContainer.addEventListener('dragover', dragOver); | |
expressionContainer.addEventListener('dragleave', dragLeave); | |
expressionContainer.addEventListener('drop', drop); | |
// Button events | |
reduceBtn.addEventListener('click', reduceExpression); | |
resetBtn.addEventListener('click', resetWorkspace); | |
addParensBtn.addEventListener('click', addParentheses); | |
// Drag start handler | |
function dragStart(e) { | |
this.classList.add('dragging'); | |
e.dataTransfer.setData('text/plain', this.dataset.type); | |
e.dataTransfer.effectAllowed = 'move'; | |
} | |
// Drag over handler | |
function dragOver(e) { | |
e.preventDefault(); | |
this.classList.add('highlight'); | |
e.dataTransfer.dropEffect = 'move'; | |
} | |
// Drag leave handler | |
function dragLeave() { | |
this.classList.remove('highlight'); | |
} | |
// Drop handler | |
function drop(e) { | |
e.preventDefault(); | |
this.classList.remove('highlight'); | |
const combinatorType = e.dataTransfer.getData('text/plain'); | |
addCombinatorToExpression(combinatorType); | |
} | |
// Add combinator to expression | |
function addCombinatorToExpression(type, isParens = false) { | |
// Hide empty message if this is the first item | |
if (currentExpression.length === 0) { | |
emptyMessage.style.display = 'none'; | |
reduceBtn.disabled = false; | |
} | |
// Create a unique ID for this combinator | |
const id = Date.now().toString(36) + Math.random().toString(36).substr(2); | |
// Add to current expression | |
currentExpression.push({ | |
id, | |
type, | |
isParens | |
}); | |
// Update the UI | |
updateExpressionUI(); | |
} | |
// Update the expression UI | |
function updateExpressionUI() { | |
expressionItems.innerHTML = ''; | |
currentExpression.forEach((item, index) => { | |
const itemEl = document.createElement('div'); | |
itemEl.className = 'expression-item'; | |
itemEl.dataset.id = item.id; | |
let bgColor, textColor; | |
if (item.isParens) { | |
itemEl.className += ' cursor-default'; | |
itemEl.innerHTML = '('; | |
} else { | |
itemEl.className += ' cursor-move'; | |
itemEl.draggable = true; | |
itemEl.addEventListener('dragstart', dragStart); | |
switch(item.type) { | |
case 'S': | |
bgColor = 'bg-blue-100'; | |
textColor = 'text-blue-800'; | |
break; | |
case 'K': | |
bgColor = 'bg-green-100'; | |
textColor = 'text-green-800'; | |
break; | |
case 'I': | |
bgColor = 'bg-purple-100'; | |
textColor = 'text-purple-800'; | |
break; | |
case 'B': | |
bgColor = 'bg-yellow-100'; | |
textColor = 'text-yellow-800'; | |
break; | |
case 'C': | |
bgColor = 'bg-red-100'; | |
textColor = 'text-red-800'; | |
break; | |
case 'W': | |
bgColor = 'bg-indigo-100'; | |
textColor = 'text-indigo-800'; | |
break; | |
case 'variable': | |
bgColor = 'bg-gray-200'; | |
textColor = 'text-gray-800'; | |
break; | |
} | |
itemEl.className += ` ${bgColor} ${textColor} py-2 px-3 rounded-lg font-mono font-bold`; | |
itemEl.textContent = item.type === 'variable' ? 'x' : item.type; | |
// Add remove button | |
const removeBtn = document.createElement('span'); | |
removeBtn.className = 'remove-btn'; | |
removeBtn.innerHTML = '<i class="fas fa-times"></i>'; | |
removeBtn.addEventListener('click', (e) => { | |
e.stopPropagation(); | |
removeCombinator(item.id); | |
}); | |
itemEl.appendChild(removeBtn); | |
} | |
expressionItems.appendChild(itemEl); | |
}); | |
} | |
// Remove combinator from expression | |
function removeCombinator(id) { | |
currentExpression = currentExpression.filter(item => item.id !== id); | |
if (currentExpression.length === 0) { | |
emptyMessage.style.display = 'block'; | |
reduceBtn.disabled = true; | |
} | |
updateExpressionUI(); | |
} | |
// Add parentheses to expression | |
function addParentheses() { | |
if (currentExpression.length === 0) return; | |
// Add opening parenthesis | |
addCombinatorToExpression('(', true); | |
// Add closing parenthesis at the end | |
setTimeout(() => { | |
addCombinatorToExpression(')', true); | |
}, 0); | |
} | |
// Reset workspace | |
function resetWorkspace() { | |
currentExpression = []; | |
steps = []; | |
reductionSteps.innerHTML = '<p class="text-gray-400 text-center py-8">Your reduction steps will appear here</p>'; | |
emptyMessage.style.display = 'block'; | |
reduceBtn.disabled = true; | |
updateExpressionUI(); | |
} | |
// Reduce expression | |
function reduceExpression() { | |
if (currentExpression.length === 0) return; | |
// Clear previous steps | |
steps = []; | |
reductionSteps.innerHTML = ''; | |
// Get the current expression as a string | |
let expr = currentExpression.map(item => { | |
if (item.isParens) { | |
return item.type === '(' ? '(' : ')'; | |
} | |
return item.type === 'variable' ? 'x' : item.type; | |
}).join(' '); | |
// Add initial expression to steps | |
addReductionStep(expr, 'Initial expression'); | |
// Simple reduction logic (this is a simplified version) | |
// In a real implementation, you'd need a proper reduction algorithm | |
let reduced = false; | |
// Try to find reducible expressions | |
for (let i = 0; i < expr.length - 2; i++) { | |
const subExpr = expr.substr(i, 3); | |
// Check for K combinator pattern: K x y β x | |
if (subExpr.startsWith('K ') && expr[i+2] !== ' ' && expr[i+3] !== ' ') { | |
const before = expr.substring(0, i); | |
const after = expr.substring(i + 4); | |
expr = before + expr[i+2] + after; | |
addReductionStep(expr, 'Applied K combinator: K x y β x'); | |
reduced = true; | |
break; | |
} | |
// Check for I combinator pattern: I x β x | |
if (subExpr.startsWith('I ') && expr[i+2] !== ' ') { | |
const before = expr.substring(0, i); | |
const after = expr.substring(i + 3); | |
expr = before + expr[i+2] + after; | |
addReductionStep(expr, 'Applied I combinator: I x β x'); | |
reduced = true; | |
break; | |
} | |
} | |
if (!reduced) { | |
addReductionStep(expr, 'No further reductions possible', true); | |
} | |
} | |
// Add a reduction step to the UI | |
function addReductionStep(expression, explanation, isFinal = false) { | |
const stepEl = document.createElement('div'); | |
stepEl.className = `bg-gray-50 p-3 rounded-lg mb-2 reduction-step ${isFinal ? 'border-l-4 border-blue-500' : ''}`; | |
const exprEl = document.createElement('div'); | |
exprEl.className = 'font-mono text-lg mb-1'; | |
exprEl.textContent = expression; | |
const explanationEl = document.createElement('div'); | |
explanationEl.className = 'text-sm text-gray-600'; | |
explanationEl.textContent = explanation; | |
stepEl.appendChild(exprEl); | |
stepEl.appendChild(explanationEl); | |
reductionSteps.appendChild(stepEl); | |
stepEl.scrollIntoView({ behavior: 'smooth' }); | |
} | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 𧬠<a href="https://enzostvs-deepsite.hf.space?remix=MarcdeFalco/combinators" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |