abstract sig Formula {
  -- Trick: formulas carry which instances
  --  make them true.
  eval: set Instance
}

sig Var extends Formula {} {
  eval = {i: Instance | this in i.val}
}
sig Not extends Formula {
  child: Formula
} {
  eval = Instance - child.@eval
}

/*
fact notEval {
  all n: Not |
    n.eval = Instance - n.child.eval
}*/

sig And extends Formula {
  left, right: Formula
} {
  eval = left.@eval & right.@eval
}
sig Or extends Formula {
  left, right: Formula
} {
  eval = left.@eval + right.@eval
}

fun subformulas[f: Formula]: set Formula {
  f.^(child + (And <: left) + (Or <: left)
            + (And <: right) + (Or <: right))
}

fact acyclic {
  all f: Formula | {
    f not in subformulas[f]
  }
}

run {#Var > 2} for 5 Formula, 4 Instance, 4 int

-- Syntax
-----------------------------------------
-- Semantics

sig Instance {
  val: set Var
}

-- Sadly, this won't work in Alloy.
-- We get no recursion!
/*
fun eval[f: Formula, i: Instance]: Bool {
  if f in Var => f in i.val
  if f in Not => not eval[f.child, i]
  if f in And => ...
}
*/