Skip to content

Commit

Permalink
Refactor: @truthtable macro (#25)
Browse files Browse the repository at this point in the history
* Refactor: macroutils

* Refactor: @truthtable macro

* Code style changes

* Move _kwarg function to macroutils.jl

* Fix tests

* Rename functions and variables

* Use 'map' instead of 'broadcast'

* Add more tests
  • Loading branch information
eliascarv authored Feb 5, 2023
1 parent d499a70 commit 1cf1a10
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 69 deletions.
31 changes: 10 additions & 21 deletions src/macro.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
function _kwarg(expr::Expr)::Bool
if expr.head == :(=) && expr.args[1] == :full
return expr.args[2]
end
throw(ArgumentError("Invalid kwarg expression"))
end

"""
@truthtable formula [full=false]
Expand Down Expand Up @@ -108,28 +101,24 @@ macro truthtable(expr)
end

function _truthtable(expr::Expr, full::Bool)
preprocess!(expr)

colnames = propnames(expr)
columns = gencolumns(length(colnames))
colexprs = Expr[]
colnames = _propnames(expr)
columns = _propcolumns(length(colnames))
colmap = Dict(zip(colnames, columns))

if full
subexprs = getsubexprs(expr)
subexprs = _subexprs(expr)
for subexpr in subexprs
nms = propnames(subexpr)
inds = indexin(nms, colnames)
colexpr = :(map(($(nms...),) -> $subexpr, $(columns[inds]...)))
push!(colnames, exprname(subexpr))
push!(colexprs, colexpr)
push!(colnames, _exprname(subexpr))
push!(colexprs, _colexpr(subexpr))
end
else
colexpr = :(map(($(colnames...),) -> $expr, $(columns...)))
push!(colnames, exprname(expr))
push!(colexprs, colexpr)
push!(colnames, _exprname(expr))
push!(colexprs, _colexpr(expr))
end

return quote
quote
local colmap = $colmap
TruthTable([$(columns...), $(colexprs...)], $colnames)
end
end
106 changes: 67 additions & 39 deletions src/macroutils.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
function gencolumns(n::Integer)
bools = [true, false]
outers = [2^x for x in 0:n-1]
inners = reverse(outers)
[repeat(bools; inner, outer) for (inner, outer) in zip(inners, outers)]
function _kwarg(expr::Expr)::Bool
if !Meta.isexpr(expr, :(=), 2) || expr.args[1] !== :full
throw(ArgumentError("Invalid kwarg expression"))
end
expr.args[2]
end

function _colexpr(expr::Expr)
colexpr = copy(expr)
_preprocess!(colexpr)
colexpr
end

const OPRS = (
Expand All @@ -15,66 +21,88 @@ const OPRS = (
:(===), :
)

function preprocess!(expr::Expr)
if expr.head == :(-->)
function _preprocess!(expr::Expr)
if expr.head === :(-->)
expr.head = :call
expr.args = [:(-->); expr.args]
pushfirst!(expr.args, :(-->))
end

if expr.head (:&&, :||)
args = expr.args
elseif expr.head == :call && expr.args[1] OPRS
args = expr.args[2:end]
else
if expr.head === :&&
expr.head = :call
pushfirst!(expr.args, :&)
end

if expr.head === :||
expr.head = :call
pushfirst!(expr.args, :|)
end

if expr.head !== :call || expr.args[1] OPRS
throw(ArgumentError("Expression with invalid operator"))
end

for arg in args
pushfirst!(expr.args, :map)

for i in 3:length(expr.args)
arg = expr.args[i]
if arg isa Symbol
expr.args[i] = :(colmap[$(QuoteNode(arg))])
end

if arg isa Expr
preprocess!(arg)
_preprocess!(arg)
end
end
end

_propname(x) = throw(ArgumentError("$x is not a valid proposition name"))
_propname(x::Symbol) = x
function _propcolumns(n::Integer)
bools = [true, false]
outers = [2^x for x in 0:n-1]
inners = reverse(outers)
[repeat(bools; inner, outer) for (inner, outer) in zip(inners, outers)]
end

function _propnames(expr::Expr)
names = Symbol[]
_propnames!(names, expr)
unique!(names)
names
end

function _propnames!(props::Vector{Symbol}, expr::Expr)
b = expr.head == :call ? 2 : 1
for arg in expr.args[b:end]
function _propnames!(names::Vector{Symbol}, expr::Expr)
b = expr.head === :call ? 2 : 1
for i in b:length(expr.args)
arg = expr.args[i]
if arg isa Expr
_propnames!(props, arg)
_propnames!(names, arg)
else
push!(props, _propname(arg))
push!(names, _propname(arg))
end
end
end

function propnames(expr::Expr)
props = Symbol[]
_propnames!(props, expr)
unique!(props)
return props
_propname(name) = throw(ArgumentError("$name is not a valid proposition name"))
_propname(name::Symbol) = name

function _subexprs(expr::Expr)
exprs = [expr]
_subexprs!(exprs, expr)
exprs
end

function _getsubexprs!(exprs::Vector{Expr}, expr::Expr)
b = expr.head == :call ? 2 : 1
for arg in expr.args[end:-1:b]
function _subexprs!(exprs::Vector{Expr}, expr::Expr)
b = expr.head === :call ? 2 : 1
for i in length(expr.args):-1:b
arg = expr.args[i]
if arg isa Expr
pushfirst!(exprs, arg)
_getsubexprs!(exprs, arg)
_subexprs!(exprs, arg)
end
end
end

function getsubexprs(expr::Expr)
exprs = [expr]
_getsubexprs!(exprs, expr)
return exprs
end

@static if VERSION < v"1.7"
function exprname(expr::Expr)
function _exprname(expr::Expr)
str = string(expr)
str = replace(str, r"&{2}|&" => "")
str = replace(str, r"\|{2}|\|" => "")
Expand All @@ -84,7 +112,7 @@ end
Symbol(replace(str, "===" => ""))
end
else
function exprname(expr::Expr)
function _exprname(expr::Expr)
str = string(expr)
str = replace(str,
r"&{2}|&" => "",
Expand Down
2 changes: 1 addition & 1 deletion src/truthtable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ struct TruthTable
end

function TruthTable(columns::Vector{Vector{Bool}}, colnames::Vector{Symbol})
colindex = Dict(k => i for (i, k) in pairs(colnames))
colindex = Dict(k => i for (i, k) in enumerate(colnames))
TruthTable(columns, colindex, colnames)
end

Expand Down
30 changes: 22 additions & 8 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ using TruthTables: ∧, ∨, -->, <-->, ¬, →, ↔, ⇒, ⇔
@test Tables.getcolumn(tt, :q) == Bool[1, 0, 1, 0]
@test Tables.getcolumn(tt, Symbol("p ∨ q")) == Bool[1, 1, 1, 0]

tt = @truthtable !(x || y) <--> (!x && !y) full = true
tt = @truthtable !(x || y) <--> (!x && !y) full=true
@test Tables.getcolumn(tt, 1) == Bool[1, 1, 0, 0]
@test Tables.getcolumn(tt, 2) == Bool[1, 0, 1, 0]
@test Tables.getcolumn(tt, 3) == Bool[1, 1, 1, 0]
Expand All @@ -72,15 +72,29 @@ using TruthTables: ∧, ∨, -->, <-->, ¬, →, ↔, ⇒, ⇔

# helper functions
expr = :(p && q --> r)
@test TruthTables.propnames(expr) == [:p, :q, :r]
@test TruthTables.getsubexprs(expr) == [:(p && q), :(p && q --> r)]
@test TruthTables.exprname(expr) == Symbol("p ∧ q --> r")
TruthTables.preprocess!(expr)
@test expr.head == :call && expr.args[1] == :(-->)
@test TruthTables._propnames(expr) == [:p, :q, :r]
@test TruthTables._subexprs(expr) == [:(p && q), :(p && q --> r)]
@test TruthTables._exprname(expr) == Symbol("p ∧ q --> r")
@test TruthTables._colexpr(expr) == :(map(-->, map(&, colmap[:p], colmap[:q]), colmap[:r]))

@test TruthTables._propcolumns(1) == [Bool[1, 0]]
@test TruthTables._propcolumns(2) == [Bool[1, 1, 0, 0], Bool[1, 0, 1, 0]]
@test TruthTables._propcolumns(3) == [
Bool[1, 1, 1, 1, 0, 0, 0, 0],
Bool[1, 1, 0, 0, 1, 1, 0, 0],
Bool[1, 0, 1, 0, 1, 0, 1, 0]
]
@test TruthTables._propcolumns(4) == [
Bool[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
Bool[1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0],
Bool[1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
Bool[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
]

@test_throws ArgumentError TruthTables._propname(1)
@test_throws ArgumentError TruthTables._kwarg(:(full => true))
@test_throws ArgumentError TruthTables.preprocess!(:(p + q))
@test_throws ArgumentError TruthTables._colexpr(:(p + q))
@test_throws ArgumentError TruthTables._colexpr(:(p ? q : r))
end

@testset "TruthTable show" begin
Expand Down Expand Up @@ -143,7 +157,7 @@ using TruthTables: ∧, ∨, -->, <-->, ¬, →, ↔, ⇒, ⇔

# show mode: :bool (default)
TruthTables.showmode!()
tt = @truthtable p && (q || r) full = true
tt = @truthtable p && (q || r) full=true
str = """
TruthTable
┌───────┬───────┬───────┬───────┬─────────────┐
Expand Down

0 comments on commit 1cf1a10

Please sign in to comment.